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/20 19:32:40 UTC

[camel-karavan] branch main updated: Fix #943

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 34353942 Fix #943
34353942 is described below

commit 34353942bbcfe2bbcbccde40256c48db26183959
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Fri Oct 20 15:32:32 2023 -0400

    Fix #943
---
 karavan-core/src/core/api/KameletApi.ts            |  1 +
 karavan-core/src/core/model/KameletModels.ts       |  1 +
 karavan-designer/public/example/demo.camel.yaml    |  5 +++
 .../route/property/KameletPropertyField.tsx        | 52 +++++++++++++++++++---
 .../route/property/KameletPropertyField.tsx        | 52 +++++++++++++++++++---
 karavan-vscode/webview/topology/CustomNode.tsx     |  7 ++-
 karavan-vscode/webview/topology/TopologyApi.tsx    | 31 ++++++++++---
 karavan-vscode/webview/topology/TopologyTab.tsx    |  1 -
 .../webui/src/designer/beans/BeanProperties.tsx    |  6 +--
 .../webui/src/designer/beans/BeansDesigner.tsx     |  2 +-
 .../webui/src/designer/kamelet/KameletDesigner.tsx |  2 +-
 .../route/property/KameletPropertyField.tsx        | 52 +++++++++++++++++++---
 12 files changed, 185 insertions(+), 27 deletions(-)

diff --git a/karavan-core/src/core/api/KameletApi.ts b/karavan-core/src/core/api/KameletApi.ts
index d8bdef4d..62ecb4f2 100644
--- a/karavan-core/src/core/api/KameletApi.ts
+++ b/karavan-core/src/core/api/KameletApi.ts
@@ -49,6 +49,7 @@ export class KameletApi {
                     prop.format = value.format;
                     prop.example = value.example;
                     prop.type = value.type;
+                    prop.enum = value.enum;
                     if (value.default) prop.value = value.default;
                     prop['x-descriptors'] = value['x-descriptors'];
                     properties.push(prop);
diff --git a/karavan-core/src/core/model/KameletModels.ts b/karavan-core/src/core/model/KameletModels.ts
index b6066320..e5c9855e 100644
--- a/karavan-core/src/core/model/KameletModels.ts
+++ b/karavan-core/src/core/model/KameletModels.ts
@@ -24,6 +24,7 @@ export class Property {
     example: string = '';
     'x-descriptors': string = '';
     value: string | number | boolean = '';
+    enum?: string[];
 }
 
 export class Definition {
diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml
index 69ff1495..82e33ec2 100644
--- a/karavan-designer/public/example/demo.camel.yaml
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -1,3 +1,8 @@
+- route:
+    id: route-612d
+    from:
+      uri: kamelet:aws-s3-cdc-source
+      id: from-e37e
 - route:
     id: route-605c
     from:
diff --git a/karavan-designer/src/designer/route/property/KameletPropertyField.tsx b/karavan-designer/src/designer/route/property/KameletPropertyField.tsx
index 69446142..0bac1c32 100644
--- a/karavan-designer/src/designer/route/property/KameletPropertyField.tsx
+++ b/karavan-designer/src/designer/route/property/KameletPropertyField.tsx
@@ -34,6 +34,8 @@ import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon";
 import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon";
 import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon";
 import {usePropertiesHook} from "../usePropertiesHook";
+import {CamelUi} from "../../utils/CamelUi";
+import {Select, SelectDirection, SelectOption, SelectVariant} from "@patternfly/react-core/deprecated";
 
 interface Props {
     property: Property,
@@ -50,12 +52,22 @@ export function KameletPropertyField(props: Props) {
     const [showPassword, setShowPassword] = useState<boolean>(false);
     const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false);
     const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined);
+    const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>());
 
     const ref = useRef<any>(null);
 
     function parametersChanged (parameter: string, value: string | number | boolean | any, pathParameter?: boolean)  {
         onParametersChange(parameter, value, pathParameter);
         setSelectIsOpen(false);
+        setSelectStatus(new Map<string, boolean>([[parameter, false]]))
+    }
+
+    function openSelect(propertyName: string, isExpanded: boolean) {
+        setSelectStatus(new Map<string, boolean>([[propertyName, isExpanded]]))
+    }
+
+    function isSelectOpen(propertyName: string): boolean {
+        return selectStatus.has(propertyName) && selectStatus.get(propertyName) === true;
     }
 
     function selectInfrastructure (value: string)  {
@@ -98,7 +110,13 @@ export function KameletPropertyField(props: Props) {
         const inInfrastructure = InfrastructureAPI.infrastructure !== 'local';
         const noInfraSelectorButton = ["uri", "id", "description", "group"].includes(property.id);
         const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? <KubernetesIcon/> : <DockerIcon/>
-        const showInfraSelectorButton = inInfrastructure && !showEditor && !noInfraSelectorButton
+        const showInfraSelectorButton = inInfrastructure && !showEditor && !noInfraSelectorButton;
+        const selectFromList: boolean = property.enum !== undefined && property?.enum?.length > 0;
+        const selectOptions: JSX.Element[] = [];
+        if (selectFromList && property.enum) {
+            selectOptions.push(...property.enum.map((value: string) =>
+                <SelectOption key={value} value={value ? value.trim() : value}/>));
+        }
         return <InputGroup>
             {showInfraSelectorButton  &&
                 <Tooltip position="bottom-end" content={"Select from " + capitalize(InfrastructureAPI.infrastructure)}>
@@ -106,21 +124,45 @@ export function KameletPropertyField(props: Props) {
                         {icon}
                     </Button>
                 </Tooltip>}
-            {(!showEditor || property.format === "password") &&
+            {selectFromList &&
+                <Select
+                    id={id} name={id}
+                    placeholderText="Select or type an URI"
+                    variant={SelectVariant.typeahead}
+                    aria-label={property.id}
+                    onToggle={(_event, isExpanded) => {
+                        openSelect(property.id, isExpanded)
+                    }}
+                    onSelect={(e, value, isPlaceholder) => {
+                        parametersChanged(property.id, value);
+                    }}
+                    selections={value}
+                    isOpen={isSelectOpen(property.id)}
+                    isCreatable={true}
+                    createText=""
+                    isInputFilterPersisted={true}
+                    aria-labelledby={property.id}
+                    direction={SelectDirection.down}>
+                    {selectOptions}
+                </Select>
+            }
+            {((!selectFromList && !showEditor) || property.format === "password") &&
                 <TextInput
                     ref={ref}
                     className="text-field" isRequired
                     type={property.format && !showPassword ? "password" : "text"}
                     id={id} name={id}
                     value={value}
-                    onChange={(e, value) => parametersChanged(property.id, value)}/>}
-            {showEditor && property.format !== "password" &&
+                    onChange={(e, value) => parametersChanged(property.id, value)}/>
+            }
+            {(!selectFromList && showEditor) && property.format !== "password" &&
                 <TextArea autoResize={true}
                           className="text-field" isRequired
                           type="text"
                           id={id} name={id}
                           value={value}
-                          onChange={(e, value) => parametersChanged(property.id, value)}/>}
+                          onChange={(e, value) => parametersChanged(property.id, value)}/>
+            }
             {property.format !== "password" &&
                 <Tooltip position="bottom-end" content={showEditor ? "Change to TextField" : "Change to Text Area"}>
                     <Button variant="control" onClick={e => setShowEditor(!showEditor)}>
diff --git a/karavan-space/src/designer/route/property/KameletPropertyField.tsx b/karavan-space/src/designer/route/property/KameletPropertyField.tsx
index 69446142..0bac1c32 100644
--- a/karavan-space/src/designer/route/property/KameletPropertyField.tsx
+++ b/karavan-space/src/designer/route/property/KameletPropertyField.tsx
@@ -34,6 +34,8 @@ import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon";
 import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon";
 import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon";
 import {usePropertiesHook} from "../usePropertiesHook";
+import {CamelUi} from "../../utils/CamelUi";
+import {Select, SelectDirection, SelectOption, SelectVariant} from "@patternfly/react-core/deprecated";
 
 interface Props {
     property: Property,
@@ -50,12 +52,22 @@ export function KameletPropertyField(props: Props) {
     const [showPassword, setShowPassword] = useState<boolean>(false);
     const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false);
     const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined);
+    const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>());
 
     const ref = useRef<any>(null);
 
     function parametersChanged (parameter: string, value: string | number | boolean | any, pathParameter?: boolean)  {
         onParametersChange(parameter, value, pathParameter);
         setSelectIsOpen(false);
+        setSelectStatus(new Map<string, boolean>([[parameter, false]]))
+    }
+
+    function openSelect(propertyName: string, isExpanded: boolean) {
+        setSelectStatus(new Map<string, boolean>([[propertyName, isExpanded]]))
+    }
+
+    function isSelectOpen(propertyName: string): boolean {
+        return selectStatus.has(propertyName) && selectStatus.get(propertyName) === true;
     }
 
     function selectInfrastructure (value: string)  {
@@ -98,7 +110,13 @@ export function KameletPropertyField(props: Props) {
         const inInfrastructure = InfrastructureAPI.infrastructure !== 'local';
         const noInfraSelectorButton = ["uri", "id", "description", "group"].includes(property.id);
         const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? <KubernetesIcon/> : <DockerIcon/>
-        const showInfraSelectorButton = inInfrastructure && !showEditor && !noInfraSelectorButton
+        const showInfraSelectorButton = inInfrastructure && !showEditor && !noInfraSelectorButton;
+        const selectFromList: boolean = property.enum !== undefined && property?.enum?.length > 0;
+        const selectOptions: JSX.Element[] = [];
+        if (selectFromList && property.enum) {
+            selectOptions.push(...property.enum.map((value: string) =>
+                <SelectOption key={value} value={value ? value.trim() : value}/>));
+        }
         return <InputGroup>
             {showInfraSelectorButton  &&
                 <Tooltip position="bottom-end" content={"Select from " + capitalize(InfrastructureAPI.infrastructure)}>
@@ -106,21 +124,45 @@ export function KameletPropertyField(props: Props) {
                         {icon}
                     </Button>
                 </Tooltip>}
-            {(!showEditor || property.format === "password") &&
+            {selectFromList &&
+                <Select
+                    id={id} name={id}
+                    placeholderText="Select or type an URI"
+                    variant={SelectVariant.typeahead}
+                    aria-label={property.id}
+                    onToggle={(_event, isExpanded) => {
+                        openSelect(property.id, isExpanded)
+                    }}
+                    onSelect={(e, value, isPlaceholder) => {
+                        parametersChanged(property.id, value);
+                    }}
+                    selections={value}
+                    isOpen={isSelectOpen(property.id)}
+                    isCreatable={true}
+                    createText=""
+                    isInputFilterPersisted={true}
+                    aria-labelledby={property.id}
+                    direction={SelectDirection.down}>
+                    {selectOptions}
+                </Select>
+            }
+            {((!selectFromList && !showEditor) || property.format === "password") &&
                 <TextInput
                     ref={ref}
                     className="text-field" isRequired
                     type={property.format && !showPassword ? "password" : "text"}
                     id={id} name={id}
                     value={value}
-                    onChange={(e, value) => parametersChanged(property.id, value)}/>}
-            {showEditor && property.format !== "password" &&
+                    onChange={(e, value) => parametersChanged(property.id, value)}/>
+            }
+            {(!selectFromList && showEditor) && property.format !== "password" &&
                 <TextArea autoResize={true}
                           className="text-field" isRequired
                           type="text"
                           id={id} name={id}
                           value={value}
-                          onChange={(e, value) => parametersChanged(property.id, value)}/>}
+                          onChange={(e, value) => parametersChanged(property.id, value)}/>
+            }
             {property.format !== "password" &&
                 <Tooltip position="bottom-end" content={showEditor ? "Change to TextField" : "Change to Text Area"}>
                     <Button variant="control" onClick={e => setShowEditor(!showEditor)}>
diff --git a/karavan-vscode/webview/topology/CustomNode.tsx b/karavan-vscode/webview/topology/CustomNode.tsx
index fb517ae9..d683d04f 100644
--- a/karavan-vscode/webview/topology/CustomNode.tsx
+++ b/karavan-vscode/webview/topology/CustomNode.tsx
@@ -43,11 +43,16 @@ function getIcon(data: any) {
 const CustomNode: React.FC<any> = observer(({ element, ...rest }) => {
 
     const data = element.getData();
+    const badge:string = data.badge?.substring(0,1).toUpperCase();
 
     return (
         <DefaultNode
+            badge={badge}
+            showStatusDecorator
             className="common-node"
-            element={element} {...rest}
+            scaleLabel={false}
+            element={element}
+            {...rest}
         >
             {getIcon(data)}
         </DefaultNode>
diff --git a/karavan-vscode/webview/topology/TopologyApi.tsx b/karavan-vscode/webview/topology/TopologyApi.tsx
index 9655968d..edff33e8 100644
--- a/karavan-vscode/webview/topology/TopologyApi.tsx
+++ b/karavan-vscode/webview/topology/TopologyApi.tsx
@@ -38,9 +38,9 @@ import {
     TopologyRestNode,
     TopologyRouteNode
 } from "core/model/TopologyDefinition";
-import CustomGroup from "./CustomGroup";
 import CustomEdge from "./CustomEdge";
 import {IntegrationFile} from "./TopologyStore";
+import CustomGroup from "./CustomGroup";
 
 const NODE_DIAMETER = 60;
 
@@ -62,7 +62,7 @@ export function getIncomingNodes(tins: TopologyIncomingNode[]): NodeModel[] {
             status: NodeStatus.default,
             data: {
                 isAlternate: false,
-                badge: tin.type,
+                badge: tin.connectorType,
                 icon: 'element',
                 type: 'step',
                 step: tin.from,
@@ -110,7 +110,7 @@ export function getOutgoingNodes(tons: TopologyOutgoingNode[]): NodeModel[] {
                 icon: 'element',
                 type: 'step',
                 step: tin.step,
-                badge: tin.type,
+                badge: tin.connectorType,
                 fileName: tin.fileName
             }
         }
@@ -146,6 +146,26 @@ export function getOutgoingEdges(tons: TopologyOutgoingNode[]): EdgeModel[] {
     });
 }
 
+export function getExternalEdges(tons: TopologyOutgoingNode[], tins: TopologyIncomingNode[]): EdgeModel[] {
+    const result: EdgeModel[]= [];
+    tons.filter(ton => ton.type === 'external').forEach((ton, index) => {
+        const uniqueUri = ton.uniqueUri;
+        if (uniqueUri) {
+            const target = TopologyUtils.getNodeIdByUniqueUri(tins, uniqueUri);
+            const node: EdgeModel = {
+                id: 'external-' + ton.id + '-' + index,
+                type: 'edge',
+                source: ton.id,
+                target: target,
+                edgeStyle: EdgeStyle.dotted,
+                animationSpeed: EdgeAnimationSpeed.slow
+            }
+            if (target) result.push(node);
+        }
+    });
+    return result;
+}
+
 export function getRestNodes(tins: TopologyRestNode[]): NodeModel[] {
     return tins.map(tin => {
         return {
@@ -217,8 +237,8 @@ export function getModel(files: IntegrationFile[]): Model {
     const nodes: NodeModel[] = [];
     const groups: NodeModel[] = troutes.map(r => {
         const children = [r.id]
-        children.push(... tins.filter(i => i.routeId === r.routeId && i.type === 'external').map(i => i.id));
-        children.push(... tons.filter(i => i.routeId === r.routeId && i.type === 'external').map(i => i.id));
+        children.push(...tins.filter(i => i.routeId === r.routeId && i.type === 'external').map(i => i.id));
+        children.push(...tons.filter(i => i.routeId === r.routeId && i.type === 'external').map(i => i.id));
         return   {
             id: 'group-' + r.routeId,
             children: children,
@@ -242,6 +262,7 @@ export function getModel(files: IntegrationFile[]): Model {
     edges.push(...getOutgoingEdges(tons));
     edges.push(...getRestEdges(trestns, tins));
     edges.push(...getInternalEdges(tons, tins));
+    edges.push(...getExternalEdges(tons,tins));
 
     return {nodes: nodes, edges: edges, graph: {id: 'g1', type: 'graph', layout: 'Dagre'}};
 }
diff --git a/karavan-vscode/webview/topology/TopologyTab.tsx b/karavan-vscode/webview/topology/TopologyTab.tsx
index 306ca8fb..520bf8d2 100644
--- a/karavan-vscode/webview/topology/TopologyTab.tsx
+++ b/karavan-vscode/webview/topology/TopologyTab.tsx
@@ -68,7 +68,6 @@ export function TopologyTab (props: Props) {
     }
 
     const controller = React.useMemo(() => {
-        console.log(props.files)
         const model = getModel(props.files);
         const newController = new Visualization();
         newController.registerLayoutFactory((_, graph) => new DagreLayout(graph));
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/beans/BeanProperties.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/beans/BeanProperties.tsx
index fdb73d2a..455291c4 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/beans/BeanProperties.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/beans/BeanProperties.tsx
@@ -73,7 +73,7 @@ export function BeanProperties (props: Props) {
 
     function onBeanPropertyUpdate ()  {
         if (selectedStep) {
-            const bean = CamelUtil.cloneBean(selectedStep);
+            const bean = CamelUtil.cloneBean(selectedStep as RegistryBeanDefinition);
             const beanProperties: any = {};
             properties.forEach((p: any) => beanProperties[p[0]] = p[1]);
             bean.properties = beanProperties;
@@ -83,7 +83,7 @@ export function BeanProperties (props: Props) {
 
     function beanFieldChanged (fieldId: string, value: string) {
         if (selectedStep) {
-            const bean = CamelUtil.cloneBean(selectedStep);
+            const bean = CamelUtil.cloneBean(selectedStep as RegistryBeanDefinition);
             (bean as any)[fieldId] = value;
             props.onChange(bean);
         }
@@ -137,7 +137,7 @@ export function BeanProperties (props: Props) {
 
     function cloneBean ()  {
         if (selectedStep) {
-            const bean = CamelUtil.cloneBean(selectedStep);
+            const bean = CamelUtil.cloneBean(selectedStep as RegistryBeanDefinition);
             bean.uuid = uuidv4();
             props.onClone(bean);
         }
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/beans/BeansDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/beans/BeansDesigner.tsx
index 64bac96f..8cd99f25 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/beans/BeansDesigner.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/beans/BeansDesigner.tsx
@@ -49,7 +49,7 @@ export function BeansDesigner() {
     }
 
     function deleteBean() {
-        const i = CamelDefinitionApiExt.deleteBeanFromIntegration(integration, selectedStep);
+        const i = CamelDefinitionApiExt.deleteBeanFromIntegration(integration, selectedStep as RegistryBeanDefinition);
         setIntegration(i, false);
         setShowDeleteConfirmation(false);
         setSelectedStep(undefined);
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/kamelet/KameletDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/kamelet/KameletDesigner.tsx
index dd889b92..c235ef9b 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/kamelet/KameletDesigner.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/kamelet/KameletDesigner.tsx
@@ -52,7 +52,7 @@ export function KameletDesigner() {
     }
 
     function deleteBean() {
-        const i = CamelDefinitionApiExt.deleteBeanFromIntegration(integration, selectedStep);
+        const i = CamelDefinitionApiExt.deleteBeanFromIntegration(integration, selectedStep as RegistryBeanDefinition);
         setIntegration(i, false);
         setShowDeleteConfirmation(false);
         setSelectedStep(undefined);
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/KameletPropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/KameletPropertyField.tsx
index 69446142..0bac1c32 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/KameletPropertyField.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/KameletPropertyField.tsx
@@ -34,6 +34,8 @@ import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon";
 import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon";
 import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon";
 import {usePropertiesHook} from "../usePropertiesHook";
+import {CamelUi} from "../../utils/CamelUi";
+import {Select, SelectDirection, SelectOption, SelectVariant} from "@patternfly/react-core/deprecated";
 
 interface Props {
     property: Property,
@@ -50,12 +52,22 @@ export function KameletPropertyField(props: Props) {
     const [showPassword, setShowPassword] = useState<boolean>(false);
     const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false);
     const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined);
+    const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>());
 
     const ref = useRef<any>(null);
 
     function parametersChanged (parameter: string, value: string | number | boolean | any, pathParameter?: boolean)  {
         onParametersChange(parameter, value, pathParameter);
         setSelectIsOpen(false);
+        setSelectStatus(new Map<string, boolean>([[parameter, false]]))
+    }
+
+    function openSelect(propertyName: string, isExpanded: boolean) {
+        setSelectStatus(new Map<string, boolean>([[propertyName, isExpanded]]))
+    }
+
+    function isSelectOpen(propertyName: string): boolean {
+        return selectStatus.has(propertyName) && selectStatus.get(propertyName) === true;
     }
 
     function selectInfrastructure (value: string)  {
@@ -98,7 +110,13 @@ export function KameletPropertyField(props: Props) {
         const inInfrastructure = InfrastructureAPI.infrastructure !== 'local';
         const noInfraSelectorButton = ["uri", "id", "description", "group"].includes(property.id);
         const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? <KubernetesIcon/> : <DockerIcon/>
-        const showInfraSelectorButton = inInfrastructure && !showEditor && !noInfraSelectorButton
+        const showInfraSelectorButton = inInfrastructure && !showEditor && !noInfraSelectorButton;
+        const selectFromList: boolean = property.enum !== undefined && property?.enum?.length > 0;
+        const selectOptions: JSX.Element[] = [];
+        if (selectFromList && property.enum) {
+            selectOptions.push(...property.enum.map((value: string) =>
+                <SelectOption key={value} value={value ? value.trim() : value}/>));
+        }
         return <InputGroup>
             {showInfraSelectorButton  &&
                 <Tooltip position="bottom-end" content={"Select from " + capitalize(InfrastructureAPI.infrastructure)}>
@@ -106,21 +124,45 @@ export function KameletPropertyField(props: Props) {
                         {icon}
                     </Button>
                 </Tooltip>}
-            {(!showEditor || property.format === "password") &&
+            {selectFromList &&
+                <Select
+                    id={id} name={id}
+                    placeholderText="Select or type an URI"
+                    variant={SelectVariant.typeahead}
+                    aria-label={property.id}
+                    onToggle={(_event, isExpanded) => {
+                        openSelect(property.id, isExpanded)
+                    }}
+                    onSelect={(e, value, isPlaceholder) => {
+                        parametersChanged(property.id, value);
+                    }}
+                    selections={value}
+                    isOpen={isSelectOpen(property.id)}
+                    isCreatable={true}
+                    createText=""
+                    isInputFilterPersisted={true}
+                    aria-labelledby={property.id}
+                    direction={SelectDirection.down}>
+                    {selectOptions}
+                </Select>
+            }
+            {((!selectFromList && !showEditor) || property.format === "password") &&
                 <TextInput
                     ref={ref}
                     className="text-field" isRequired
                     type={property.format && !showPassword ? "password" : "text"}
                     id={id} name={id}
                     value={value}
-                    onChange={(e, value) => parametersChanged(property.id, value)}/>}
-            {showEditor && property.format !== "password" &&
+                    onChange={(e, value) => parametersChanged(property.id, value)}/>
+            }
+            {(!selectFromList && showEditor) && property.format !== "password" &&
                 <TextArea autoResize={true}
                           className="text-field" isRequired
                           type="text"
                           id={id} name={id}
                           value={value}
-                          onChange={(e, value) => parametersChanged(property.id, value)}/>}
+                          onChange={(e, value) => parametersChanged(property.id, value)}/>
+            }
             {property.format !== "password" &&
                 <Tooltip position="bottom-end" content={showEditor ? "Change to TextField" : "Change to Text Area"}>
                     <Button variant="control" onClick={e => setShowEditor(!showEditor)}>