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 a9d79f19 Route Template Parameters Placeholder
a9d79f19 is described below

commit a9d79f19edf0c350c568f0917dc5eaa51d2b4fa4
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Wed Nov 27 10:58:52 2024 -0500

    Route Template Parameters Placeholder
---
 .../src/main/webui/src/designer/DesignerStore.ts   | 10 ++++
 .../webui/src/designer/property/DslProperties.tsx  | 16 +++++-
 .../property/PropertyPlaceholderDropdown.css       |  5 ++
 .../property/PropertyPlaceholderDropdown.tsx       | 34 +++++++----
 .../src/main/webui/src/designer/utils/CamelUi.tsx  |  2 +-
 karavan-core/src/core/api/CamelDefinitionApiExt.ts | 17 ++++++
 karavan-core/src/core/api/CamelDefinitionYaml.ts   |  2 +-
 karavan-designer/public/example/demo.camel.yaml    | 65 ++++++++--------------
 karavan-designer/src/designer/DesignerStore.ts     | 10 ++++
 .../src/designer/property/DslProperties.tsx        | 16 +++++-
 .../property/PropertyPlaceholderDropdown.css       |  5 ++
 .../property/PropertyPlaceholderDropdown.tsx       | 34 +++++++----
 karavan-designer/src/designer/utils/CamelUi.tsx    |  2 +-
 karavan-space/src/designer/DesignerStore.ts        | 10 ++++
 .../src/designer/property/DslProperties.tsx        | 16 +++++-
 .../property/PropertyPlaceholderDropdown.css       |  5 ++
 .../property/PropertyPlaceholderDropdown.tsx       | 34 +++++++----
 karavan-space/src/designer/utils/CamelUi.tsx       |  2 +-
 18 files changed, 200 insertions(+), 85 deletions(-)

diff --git a/karavan-app/src/main/webui/src/designer/DesignerStore.ts 
b/karavan-app/src/main/webui/src/designer/DesignerStore.ts
index cd610c0f..7535b93b 100644
--- a/karavan-app/src/main/webui/src/designer/DesignerStore.ts
+++ b/karavan-app/src/main/webui/src/designer/DesignerStore.ts
@@ -213,6 +213,7 @@ type DesignerState = {
     left: number,
     moveElements: [string | undefined, string | undefined],
     propertyPlaceholders: string[],
+    parameterPlaceholders: [string, string][], // route template parameters
     beans: BeanFactoryDefinition[]
 }
 
@@ -232,6 +233,7 @@ const designerState: DesignerState = {
     left: 0,
     moveElements: [undefined, undefined],
     propertyPlaceholders: [],
+    parameterPlaceholders: [],
     beans: []
 };
 
@@ -249,6 +251,7 @@ type DesignerAction = {
     setNotification: (notificationBadge: boolean, notificationMessage: 
[string, string]) => void;
     setMoveElements: (moveElements: [string | undefined, string | undefined]) 
=> void;
     setPropertyPlaceholders: (propertyPlaceholders: string[]) => void;
+    setParameterPlaceholders: (parameterPlaceholders: [string, string][]) => 
void;
     setBeans: (beans: BeanFactoryDefinition[]) => void;
 }
 
@@ -309,6 +312,13 @@ export const useDesignerStore = 
createWithEqualityFn<DesignerState & DesignerAct
             return state;
         })
     },
+    setParameterPlaceholders: (parameterPlaceholders: [string, string][]) => {
+        set((state: DesignerState) => {
+            state.parameterPlaceholders.length = 0;
+            state.parameterPlaceholders.push(...parameterPlaceholders);
+            return state;
+        })
+    },
     setBeans: (beans: BeanFactoryDefinition[]) => {
         set((state: DesignerState) => {
             return {beans: [...beans]};
diff --git a/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx 
b/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
index dd2376ee..52b36ef7 100644
--- a/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
+++ b/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
@@ -47,6 +47,7 @@ import {PropertiesHeader} from "./PropertiesHeader";
 import {PropertyUtil} from "./property/PropertyUtil";
 import {usePropertiesStore} from "./PropertyStore";
 import TimesIcon from "@patternfly/react-icons/dist/esm/icons/times-icon";
+import {RouteTemplateDefinition} from "karavan-core/lib/model/CamelDefinition";
 
 interface Props {
     designerType: 'routes' | 'rest' | 'beans'
@@ -65,8 +66,8 @@ export function DslProperties(props: Props) {
     } =
         usePropertiesHook(props.designerType);
 
-    const [selectedStep, dark]
-        = useDesignerStore((s) => [s.selectedStep, s.dark], shallow)
+    const [selectedStep, dark, setParameterPlaceholders]
+        = useDesignerStore((s) => [s.selectedStep, s.dark, 
s.setParameterPlaceholders], shallow)
 
     const [propertyFilter, changedOnly, requiredOnly, setChangedOnly, 
sensitiveOnly, setSensitiveOnly, setPropertyFilter, setRequiredOnly]
         = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, 
s.requiredOnly, s.setChangedOnly, s.sensitiveOnly, s.setSensitiveOnly, 
s.setPropertyFilter, s.setRequiredOnly], shallow)
@@ -78,8 +79,19 @@ export function DslProperties(props: Props) {
         setChangedOnly(false);
         setSensitiveOnly(false);
         setPropertyFilter('');
+        getRouteTemplateParameters()
     }, [selectedStep?.uuid])
 
+    function getRouteTemplateParameters() {
+        if (selectedStep) {
+            const root = 
CamelDefinitionApiExt.findTopRouteElement(integration, selectedStep.uuid);
+            if ('RouteTemplateDefinition' === root?.dslName) {
+                const paramPlaceholders: [string, string][] = (root as 
RouteTemplateDefinition).parameters?.map(p => [p.name, p.description ||'']) || 
[];
+                setParameterPlaceholders(paramPlaceholders);
+            }
+        }
+    }
+
     function getClonableElementHeader(): React.JSX.Element {
         const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep);
         const description = selectedStep?.dslName ? 
CamelMetadataApi.getCamelModelMetadataByClassName(selectedStep?.dslName)?.description
 : title;
diff --git 
a/karavan-app/src/main/webui/src/designer/property/property/PropertyPlaceholderDropdown.css
 
b/karavan-app/src/main/webui/src/designer/property/property/PropertyPlaceholderDropdown.css
index 43991634..3984ae02 100644
--- 
a/karavan-app/src/main/webui/src/designer/property/property/PropertyPlaceholderDropdown.css
+++ 
b/karavan-app/src/main/webui/src/designer/property/property/PropertyPlaceholderDropdown.css
@@ -28,6 +28,11 @@
     display: none;
 }
 
+.karavan .properties .property-placeholder-dropdown .pf-v5-c-menu__group-title 
{
+    font-weight: bold;
+    font-size: 13px;
+}
+
 .pf-v5-c-popover .property-placeholder-toggle-form {
     width: 300px;
 }
diff --git 
a/karavan-app/src/main/webui/src/designer/property/property/PropertyPlaceholderDropdown.tsx
 
b/karavan-app/src/main/webui/src/designer/property/property/PropertyPlaceholderDropdown.tsx
index e920ce0a..ec532b55 100644
--- 
a/karavan-app/src/main/webui/src/designer/property/property/PropertyPlaceholderDropdown.tsx
+++ 
b/karavan-app/src/main/webui/src/designer/property/property/PropertyPlaceholderDropdown.tsx
@@ -59,8 +59,8 @@ interface Props {
 
 export function PropertyPlaceholderDropdown(props: Props) {
 
-    const [propertyPlaceholders, setPropertyPlaceholders] = 
useDesignerStore((s) =>
-        [s.propertyPlaceholders, s.setPropertyPlaceholders], shallow)
+    const [propertyPlaceholders, setPropertyPlaceholders, 
parameterPlaceholders] = useDesignerStore((s) =>
+        [s.propertyPlaceholders, s.setPropertyPlaceholders, 
s.parameterPlaceholders], shallow)
     const [isOpenPlaceholdersDropdown, setOpenPlaceholdersDropdown] = 
useState<boolean>(false);
     const [propValue, setPropValue] = useState<string>('');
     const [isVisible, setIsVisible] = React.useState(false);
@@ -72,13 +72,15 @@ export function PropertyPlaceholderDropdown(props: Props) {
     const {property, value} = props;
     const valueIsPlaceholder: boolean = value && 
value.toString().startsWith('{{') && value.toString().endsWith('}}');
     const placeholderValue = valueIsPlaceholder ? 
value.toString().replace('{{', '').replace('}}', '') : undefined;
+    const isRouteTemplateParameter = parameterPlaceholders.map(p => 
p[0]).includes(placeholderValue);
     const showAddButton = valueIsPlaceholder
+        && !isRouteTemplateParameter
         && !propertyPlaceholders.includes(placeholderValue)
-        && !SYNTAX_EXAMPLES.map(se=> 
se.value).includes(removeBrackets(placeholderValue))
-        && SYNTAX_EXAMPLES.findIndex(se=> 
removeBrackets(placeholderValue).startsWith(se.key)) === -1;
+        && !SYNTAX_EXAMPLES.map(se => 
se.value).includes(removeBrackets(placeholderValue))
+        && SYNTAX_EXAMPLES.findIndex(se => 
removeBrackets(placeholderValue).startsWith(se.key)) === -1;
     const popoverId = "popover-selector-" + property.hasOwnProperty('name') ? 
(property as any).name : (property as any).id;
 
-    const hasPlaceholders = (propertyPlaceholders && 
propertyPlaceholders.length > 0 );
+    const hasPlaceholders = (propertyPlaceholders && 
propertyPlaceholders.length > 0);
 
     function parametersChanged(value: string | number | boolean | any) {
         if (property instanceof ComponentProperty) {
@@ -167,13 +169,23 @@ export function PropertyPlaceholderDropdown(props: Props) 
{
             shouldFocusToggleOnSelect
         >
             <DropdownList>
-                {hasPlaceholders && <DropdownGroup label="Application 
Properties">
-                    {propertyPlaceholders.map((pp, index) =>
-                        <DropdownItem value={pp} 
key={index}>{pp}</DropdownItem>
-                    )}
-                </DropdownGroup>}
+                {parameterPlaceholders &&
+                    <DropdownGroup label="Template Parameters" 
className='property-placeholder-dropdown'>
+                        {parameterPlaceholders.map((pp, index) =>
+                            <DropdownItem value={pp[0]} key={index} 
description={pp[1]}>{pp[0]}</DropdownItem>
+                        )}
+                    </DropdownGroup>
+                }
+                {parameterPlaceholders && <Divider component="li"/>}
+                {hasPlaceholders &&
+                    <DropdownGroup label="Application Properties" 
className='property-placeholder-dropdown'>
+                        {propertyPlaceholders.map((pp, index) =>
+                            <DropdownItem value={pp} 
key={index}>{pp}</DropdownItem>
+                        )}
+                    </DropdownGroup>
+                }
                 {hasPlaceholders && <Divider component="li"/>}
-                <DropdownGroup label="Syntax examples">
+                <DropdownGroup label="Syntax examples" 
className='property-placeholder-dropdown'>
                     {SYNTAX_EXAMPLES.map(se =>
                         <DropdownItem value={se.value} key={se.key} 
description={se.description}>
                             {se.value}
diff --git a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx 
b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
index f89c8e85..a12e19d6 100644
--- a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
+++ b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
@@ -821,7 +821,7 @@ export class CamelUi {
 
     static getFlowCounts = (i: Integration): Map<string, number> => {
         const result = new Map<string, number>();
-        result.set('routes', i.spec.flows?.filter((e: any) => e.dslName === 
'RouteDefinition').length || 0);
+        result.set('routes', i.spec.flows?.filter((e: any) => 
['RouteDefinition', 'RouteTemplateDefinition'].includes(e.dslName)).length || 
0);
         result.set('rest', i.spec.flows?.filter((e: any) => e.dslName === 
'RestDefinition').length || 0);
         result.set('routeConfiguration', i.spec.flows?.filter((e: any) => 
e.dslName === 'RouteConfigurationDefinition').length || 0);
         const beans = i.spec.flows?.filter((e: any) => e.dslName === 'Beans');
diff --git a/karavan-core/src/core/api/CamelDefinitionApiExt.ts 
b/karavan-core/src/core/api/CamelDefinitionApiExt.ts
index f542253d..36c2032b 100644
--- a/karavan-core/src/core/api/CamelDefinitionApiExt.ts
+++ b/karavan-core/src/core/api/CamelDefinitionApiExt.ts
@@ -174,6 +174,23 @@ export class CamelDefinitionApiExt {
         return result;
     };
 
+    static findTopRouteElement = (integration: Integration, uuid: string): 
CamelElement | undefined => {
+        const result: string[] = [];
+        let meta = 
CamelDefinitionApiExt.findElementMetaInIntegration(integration, uuid);
+        if (meta) {
+            while (meta.parentUuid !== undefined) {
+                if (meta.parentUuid) {
+                    result.push(meta.parentUuid);
+                    meta = 
CamelDefinitionApiExt.findElementMetaInIntegration(integration, 
meta.parentUuid);
+                } else {
+                    break;
+                }
+            }
+        }
+        const last = result.at(-1);
+        return last ? 
CamelDefinitionApiExt.findElementInIntegration(integration, last): undefined
+    };
+
     static findElementInElements = (steps: CamelElement[] | undefined, uuid: 
string, result: CamelElementMeta = new CamelElementMeta(undefined, undefined, 
undefined),
                                     parentUuid?: string,): CamelElementMeta => 
{
         if (result?.step !== undefined) {
diff --git a/karavan-core/src/core/api/CamelDefinitionYaml.ts 
b/karavan-core/src/core/api/CamelDefinitionYaml.ts
index d82020fb..93ca86f3 100644
--- a/karavan-core/src/core/api/CamelDefinitionYaml.ts
+++ b/karavan-core/src/core/api/CamelDefinitionYaml.ts
@@ -19,7 +19,7 @@ import { Beans, CamelElement, Integration } from 
'../model/IntegrationDefinition
 import {
     BeanFactoryDefinition,
     RouteConfigurationDefinition,
-    RouteDefinition, RouteTemplateDefinition,
+    RouteDefinition,
 } from '../model/CamelDefinition';
 import { CamelUtil } from './CamelUtil';
 import { CamelDefinitionYamlStep } from './CamelDefinitionYamlStep';
diff --git a/karavan-designer/public/example/demo.camel.yaml 
b/karavan-designer/public/example/demo.camel.yaml
index b60f1af7..6c655d36 100644
--- a/karavan-designer/public/example/demo.camel.yaml
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -1,24 +1,9 @@
-- routeTemplate:
-    id: routeFileReaderTemplate
-    description: File reader
-    route:
-      id: routeFileReader
-      description: File reader
-      from:
-        id: from-4101
-        uri: sftp
-        steps:
-          - to:
-              id: to-1234
-              uri: sql
-    parameters:
-      - name: folderName
 - routeTemplate:
     id: routeFileReaderTemplate2
-    description: File reader 2
+    description: File reader template
     route:
       id: routeFileReader 2
-      description: File reader 2
+      description: File reader route
       from:
         id: from-4101
         uri: sftp
@@ -26,29 +11,25 @@
           - to:
               id: to-1234
               uri: sql
+              parameters:
+                query: "{{folderName}}"
+          - loadBalance:
+              id: loadBalance-8172
+          - log:
+              id: log-7fa8
+              message: ${body}
+          - loop:
+              id: loop-6068
+              expression:
+                groovy:
+                  id: groovy-4e44
+              steps:
+                - marshal:
+                    id: marshal-930d
+                    json:
+                      id: json-135e
     parameters:
-      - name: folderName
-- route:
-    id: route-dummy
-    description: Dummy route
-    autoStartup: false
-    from:
-      id: from-b6a5
-      uri: kamelet:aws-kinesis-source
-      parameters:
-        secretKey: asasdasd
-        accessKey: asdasd
-      steps:
-        - log:
-            id: log-b47b
-            message: DUMMY
-- route:
-    id: route-file
-    description: File consumer
-    from:
-      id: from-file
-      uri: file
-      steps:
-        - log:
-            id: log-file
-            message: DUMMY
+      - description: Folder with files
+        name: folderName
+      - description: Server Hostname
+        name: serverName
diff --git a/karavan-designer/src/designer/DesignerStore.ts 
b/karavan-designer/src/designer/DesignerStore.ts
index cd610c0f..7535b93b 100644
--- a/karavan-designer/src/designer/DesignerStore.ts
+++ b/karavan-designer/src/designer/DesignerStore.ts
@@ -213,6 +213,7 @@ type DesignerState = {
     left: number,
     moveElements: [string | undefined, string | undefined],
     propertyPlaceholders: string[],
+    parameterPlaceholders: [string, string][], // route template parameters
     beans: BeanFactoryDefinition[]
 }
 
@@ -232,6 +233,7 @@ const designerState: DesignerState = {
     left: 0,
     moveElements: [undefined, undefined],
     propertyPlaceholders: [],
+    parameterPlaceholders: [],
     beans: []
 };
 
@@ -249,6 +251,7 @@ type DesignerAction = {
     setNotification: (notificationBadge: boolean, notificationMessage: 
[string, string]) => void;
     setMoveElements: (moveElements: [string | undefined, string | undefined]) 
=> void;
     setPropertyPlaceholders: (propertyPlaceholders: string[]) => void;
+    setParameterPlaceholders: (parameterPlaceholders: [string, string][]) => 
void;
     setBeans: (beans: BeanFactoryDefinition[]) => void;
 }
 
@@ -309,6 +312,13 @@ export const useDesignerStore = 
createWithEqualityFn<DesignerState & DesignerAct
             return state;
         })
     },
+    setParameterPlaceholders: (parameterPlaceholders: [string, string][]) => {
+        set((state: DesignerState) => {
+            state.parameterPlaceholders.length = 0;
+            state.parameterPlaceholders.push(...parameterPlaceholders);
+            return state;
+        })
+    },
     setBeans: (beans: BeanFactoryDefinition[]) => {
         set((state: DesignerState) => {
             return {beans: [...beans]};
diff --git a/karavan-designer/src/designer/property/DslProperties.tsx 
b/karavan-designer/src/designer/property/DslProperties.tsx
index dd2376ee..52b36ef7 100644
--- a/karavan-designer/src/designer/property/DslProperties.tsx
+++ b/karavan-designer/src/designer/property/DslProperties.tsx
@@ -47,6 +47,7 @@ import {PropertiesHeader} from "./PropertiesHeader";
 import {PropertyUtil} from "./property/PropertyUtil";
 import {usePropertiesStore} from "./PropertyStore";
 import TimesIcon from "@patternfly/react-icons/dist/esm/icons/times-icon";
+import {RouteTemplateDefinition} from "karavan-core/lib/model/CamelDefinition";
 
 interface Props {
     designerType: 'routes' | 'rest' | 'beans'
@@ -65,8 +66,8 @@ export function DslProperties(props: Props) {
     } =
         usePropertiesHook(props.designerType);
 
-    const [selectedStep, dark]
-        = useDesignerStore((s) => [s.selectedStep, s.dark], shallow)
+    const [selectedStep, dark, setParameterPlaceholders]
+        = useDesignerStore((s) => [s.selectedStep, s.dark, 
s.setParameterPlaceholders], shallow)
 
     const [propertyFilter, changedOnly, requiredOnly, setChangedOnly, 
sensitiveOnly, setSensitiveOnly, setPropertyFilter, setRequiredOnly]
         = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, 
s.requiredOnly, s.setChangedOnly, s.sensitiveOnly, s.setSensitiveOnly, 
s.setPropertyFilter, s.setRequiredOnly], shallow)
@@ -78,8 +79,19 @@ export function DslProperties(props: Props) {
         setChangedOnly(false);
         setSensitiveOnly(false);
         setPropertyFilter('');
+        getRouteTemplateParameters()
     }, [selectedStep?.uuid])
 
+    function getRouteTemplateParameters() {
+        if (selectedStep) {
+            const root = 
CamelDefinitionApiExt.findTopRouteElement(integration, selectedStep.uuid);
+            if ('RouteTemplateDefinition' === root?.dslName) {
+                const paramPlaceholders: [string, string][] = (root as 
RouteTemplateDefinition).parameters?.map(p => [p.name, p.description ||'']) || 
[];
+                setParameterPlaceholders(paramPlaceholders);
+            }
+        }
+    }
+
     function getClonableElementHeader(): React.JSX.Element {
         const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep);
         const description = selectedStep?.dslName ? 
CamelMetadataApi.getCamelModelMetadataByClassName(selectedStep?.dslName)?.description
 : title;
diff --git 
a/karavan-designer/src/designer/property/property/PropertyPlaceholderDropdown.css
 
b/karavan-designer/src/designer/property/property/PropertyPlaceholderDropdown.css
index 43991634..3984ae02 100644
--- 
a/karavan-designer/src/designer/property/property/PropertyPlaceholderDropdown.css
+++ 
b/karavan-designer/src/designer/property/property/PropertyPlaceholderDropdown.css
@@ -28,6 +28,11 @@
     display: none;
 }
 
+.karavan .properties .property-placeholder-dropdown .pf-v5-c-menu__group-title 
{
+    font-weight: bold;
+    font-size: 13px;
+}
+
 .pf-v5-c-popover .property-placeholder-toggle-form {
     width: 300px;
 }
diff --git 
a/karavan-designer/src/designer/property/property/PropertyPlaceholderDropdown.tsx
 
b/karavan-designer/src/designer/property/property/PropertyPlaceholderDropdown.tsx
index e920ce0a..ec532b55 100644
--- 
a/karavan-designer/src/designer/property/property/PropertyPlaceholderDropdown.tsx
+++ 
b/karavan-designer/src/designer/property/property/PropertyPlaceholderDropdown.tsx
@@ -59,8 +59,8 @@ interface Props {
 
 export function PropertyPlaceholderDropdown(props: Props) {
 
-    const [propertyPlaceholders, setPropertyPlaceholders] = 
useDesignerStore((s) =>
-        [s.propertyPlaceholders, s.setPropertyPlaceholders], shallow)
+    const [propertyPlaceholders, setPropertyPlaceholders, 
parameterPlaceholders] = useDesignerStore((s) =>
+        [s.propertyPlaceholders, s.setPropertyPlaceholders, 
s.parameterPlaceholders], shallow)
     const [isOpenPlaceholdersDropdown, setOpenPlaceholdersDropdown] = 
useState<boolean>(false);
     const [propValue, setPropValue] = useState<string>('');
     const [isVisible, setIsVisible] = React.useState(false);
@@ -72,13 +72,15 @@ export function PropertyPlaceholderDropdown(props: Props) {
     const {property, value} = props;
     const valueIsPlaceholder: boolean = value && 
value.toString().startsWith('{{') && value.toString().endsWith('}}');
     const placeholderValue = valueIsPlaceholder ? 
value.toString().replace('{{', '').replace('}}', '') : undefined;
+    const isRouteTemplateParameter = parameterPlaceholders.map(p => 
p[0]).includes(placeholderValue);
     const showAddButton = valueIsPlaceholder
+        && !isRouteTemplateParameter
         && !propertyPlaceholders.includes(placeholderValue)
-        && !SYNTAX_EXAMPLES.map(se=> 
se.value).includes(removeBrackets(placeholderValue))
-        && SYNTAX_EXAMPLES.findIndex(se=> 
removeBrackets(placeholderValue).startsWith(se.key)) === -1;
+        && !SYNTAX_EXAMPLES.map(se => 
se.value).includes(removeBrackets(placeholderValue))
+        && SYNTAX_EXAMPLES.findIndex(se => 
removeBrackets(placeholderValue).startsWith(se.key)) === -1;
     const popoverId = "popover-selector-" + property.hasOwnProperty('name') ? 
(property as any).name : (property as any).id;
 
-    const hasPlaceholders = (propertyPlaceholders && 
propertyPlaceholders.length > 0 );
+    const hasPlaceholders = (propertyPlaceholders && 
propertyPlaceholders.length > 0);
 
     function parametersChanged(value: string | number | boolean | any) {
         if (property instanceof ComponentProperty) {
@@ -167,13 +169,23 @@ export function PropertyPlaceholderDropdown(props: Props) 
{
             shouldFocusToggleOnSelect
         >
             <DropdownList>
-                {hasPlaceholders && <DropdownGroup label="Application 
Properties">
-                    {propertyPlaceholders.map((pp, index) =>
-                        <DropdownItem value={pp} 
key={index}>{pp}</DropdownItem>
-                    )}
-                </DropdownGroup>}
+                {parameterPlaceholders &&
+                    <DropdownGroup label="Template Parameters" 
className='property-placeholder-dropdown'>
+                        {parameterPlaceholders.map((pp, index) =>
+                            <DropdownItem value={pp[0]} key={index} 
description={pp[1]}>{pp[0]}</DropdownItem>
+                        )}
+                    </DropdownGroup>
+                }
+                {parameterPlaceholders && <Divider component="li"/>}
+                {hasPlaceholders &&
+                    <DropdownGroup label="Application Properties" 
className='property-placeholder-dropdown'>
+                        {propertyPlaceholders.map((pp, index) =>
+                            <DropdownItem value={pp} 
key={index}>{pp}</DropdownItem>
+                        )}
+                    </DropdownGroup>
+                }
                 {hasPlaceholders && <Divider component="li"/>}
-                <DropdownGroup label="Syntax examples">
+                <DropdownGroup label="Syntax examples" 
className='property-placeholder-dropdown'>
                     {SYNTAX_EXAMPLES.map(se =>
                         <DropdownItem value={se.value} key={se.key} 
description={se.description}>
                             {se.value}
diff --git a/karavan-designer/src/designer/utils/CamelUi.tsx 
b/karavan-designer/src/designer/utils/CamelUi.tsx
index f89c8e85..a12e19d6 100644
--- a/karavan-designer/src/designer/utils/CamelUi.tsx
+++ b/karavan-designer/src/designer/utils/CamelUi.tsx
@@ -821,7 +821,7 @@ export class CamelUi {
 
     static getFlowCounts = (i: Integration): Map<string, number> => {
         const result = new Map<string, number>();
-        result.set('routes', i.spec.flows?.filter((e: any) => e.dslName === 
'RouteDefinition').length || 0);
+        result.set('routes', i.spec.flows?.filter((e: any) => 
['RouteDefinition', 'RouteTemplateDefinition'].includes(e.dslName)).length || 
0);
         result.set('rest', i.spec.flows?.filter((e: any) => e.dslName === 
'RestDefinition').length || 0);
         result.set('routeConfiguration', i.spec.flows?.filter((e: any) => 
e.dslName === 'RouteConfigurationDefinition').length || 0);
         const beans = i.spec.flows?.filter((e: any) => e.dslName === 'Beans');
diff --git a/karavan-space/src/designer/DesignerStore.ts 
b/karavan-space/src/designer/DesignerStore.ts
index cd610c0f..7535b93b 100644
--- a/karavan-space/src/designer/DesignerStore.ts
+++ b/karavan-space/src/designer/DesignerStore.ts
@@ -213,6 +213,7 @@ type DesignerState = {
     left: number,
     moveElements: [string | undefined, string | undefined],
     propertyPlaceholders: string[],
+    parameterPlaceholders: [string, string][], // route template parameters
     beans: BeanFactoryDefinition[]
 }
 
@@ -232,6 +233,7 @@ const designerState: DesignerState = {
     left: 0,
     moveElements: [undefined, undefined],
     propertyPlaceholders: [],
+    parameterPlaceholders: [],
     beans: []
 };
 
@@ -249,6 +251,7 @@ type DesignerAction = {
     setNotification: (notificationBadge: boolean, notificationMessage: 
[string, string]) => void;
     setMoveElements: (moveElements: [string | undefined, string | undefined]) 
=> void;
     setPropertyPlaceholders: (propertyPlaceholders: string[]) => void;
+    setParameterPlaceholders: (parameterPlaceholders: [string, string][]) => 
void;
     setBeans: (beans: BeanFactoryDefinition[]) => void;
 }
 
@@ -309,6 +312,13 @@ export const useDesignerStore = 
createWithEqualityFn<DesignerState & DesignerAct
             return state;
         })
     },
+    setParameterPlaceholders: (parameterPlaceholders: [string, string][]) => {
+        set((state: DesignerState) => {
+            state.parameterPlaceholders.length = 0;
+            state.parameterPlaceholders.push(...parameterPlaceholders);
+            return state;
+        })
+    },
     setBeans: (beans: BeanFactoryDefinition[]) => {
         set((state: DesignerState) => {
             return {beans: [...beans]};
diff --git a/karavan-space/src/designer/property/DslProperties.tsx 
b/karavan-space/src/designer/property/DslProperties.tsx
index dd2376ee..52b36ef7 100644
--- a/karavan-space/src/designer/property/DslProperties.tsx
+++ b/karavan-space/src/designer/property/DslProperties.tsx
@@ -47,6 +47,7 @@ import {PropertiesHeader} from "./PropertiesHeader";
 import {PropertyUtil} from "./property/PropertyUtil";
 import {usePropertiesStore} from "./PropertyStore";
 import TimesIcon from "@patternfly/react-icons/dist/esm/icons/times-icon";
+import {RouteTemplateDefinition} from "karavan-core/lib/model/CamelDefinition";
 
 interface Props {
     designerType: 'routes' | 'rest' | 'beans'
@@ -65,8 +66,8 @@ export function DslProperties(props: Props) {
     } =
         usePropertiesHook(props.designerType);
 
-    const [selectedStep, dark]
-        = useDesignerStore((s) => [s.selectedStep, s.dark], shallow)
+    const [selectedStep, dark, setParameterPlaceholders]
+        = useDesignerStore((s) => [s.selectedStep, s.dark, 
s.setParameterPlaceholders], shallow)
 
     const [propertyFilter, changedOnly, requiredOnly, setChangedOnly, 
sensitiveOnly, setSensitiveOnly, setPropertyFilter, setRequiredOnly]
         = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, 
s.requiredOnly, s.setChangedOnly, s.sensitiveOnly, s.setSensitiveOnly, 
s.setPropertyFilter, s.setRequiredOnly], shallow)
@@ -78,8 +79,19 @@ export function DslProperties(props: Props) {
         setChangedOnly(false);
         setSensitiveOnly(false);
         setPropertyFilter('');
+        getRouteTemplateParameters()
     }, [selectedStep?.uuid])
 
+    function getRouteTemplateParameters() {
+        if (selectedStep) {
+            const root = 
CamelDefinitionApiExt.findTopRouteElement(integration, selectedStep.uuid);
+            if ('RouteTemplateDefinition' === root?.dslName) {
+                const paramPlaceholders: [string, string][] = (root as 
RouteTemplateDefinition).parameters?.map(p => [p.name, p.description ||'']) || 
[];
+                setParameterPlaceholders(paramPlaceholders);
+            }
+        }
+    }
+
     function getClonableElementHeader(): React.JSX.Element {
         const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep);
         const description = selectedStep?.dslName ? 
CamelMetadataApi.getCamelModelMetadataByClassName(selectedStep?.dslName)?.description
 : title;
diff --git 
a/karavan-space/src/designer/property/property/PropertyPlaceholderDropdown.css 
b/karavan-space/src/designer/property/property/PropertyPlaceholderDropdown.css
index 43991634..3984ae02 100644
--- 
a/karavan-space/src/designer/property/property/PropertyPlaceholderDropdown.css
+++ 
b/karavan-space/src/designer/property/property/PropertyPlaceholderDropdown.css
@@ -28,6 +28,11 @@
     display: none;
 }
 
+.karavan .properties .property-placeholder-dropdown .pf-v5-c-menu__group-title 
{
+    font-weight: bold;
+    font-size: 13px;
+}
+
 .pf-v5-c-popover .property-placeholder-toggle-form {
     width: 300px;
 }
diff --git 
a/karavan-space/src/designer/property/property/PropertyPlaceholderDropdown.tsx 
b/karavan-space/src/designer/property/property/PropertyPlaceholderDropdown.tsx
index e920ce0a..ec532b55 100644
--- 
a/karavan-space/src/designer/property/property/PropertyPlaceholderDropdown.tsx
+++ 
b/karavan-space/src/designer/property/property/PropertyPlaceholderDropdown.tsx
@@ -59,8 +59,8 @@ interface Props {
 
 export function PropertyPlaceholderDropdown(props: Props) {
 
-    const [propertyPlaceholders, setPropertyPlaceholders] = 
useDesignerStore((s) =>
-        [s.propertyPlaceholders, s.setPropertyPlaceholders], shallow)
+    const [propertyPlaceholders, setPropertyPlaceholders, 
parameterPlaceholders] = useDesignerStore((s) =>
+        [s.propertyPlaceholders, s.setPropertyPlaceholders, 
s.parameterPlaceholders], shallow)
     const [isOpenPlaceholdersDropdown, setOpenPlaceholdersDropdown] = 
useState<boolean>(false);
     const [propValue, setPropValue] = useState<string>('');
     const [isVisible, setIsVisible] = React.useState(false);
@@ -72,13 +72,15 @@ export function PropertyPlaceholderDropdown(props: Props) {
     const {property, value} = props;
     const valueIsPlaceholder: boolean = value && 
value.toString().startsWith('{{') && value.toString().endsWith('}}');
     const placeholderValue = valueIsPlaceholder ? 
value.toString().replace('{{', '').replace('}}', '') : undefined;
+    const isRouteTemplateParameter = parameterPlaceholders.map(p => 
p[0]).includes(placeholderValue);
     const showAddButton = valueIsPlaceholder
+        && !isRouteTemplateParameter
         && !propertyPlaceholders.includes(placeholderValue)
-        && !SYNTAX_EXAMPLES.map(se=> 
se.value).includes(removeBrackets(placeholderValue))
-        && SYNTAX_EXAMPLES.findIndex(se=> 
removeBrackets(placeholderValue).startsWith(se.key)) === -1;
+        && !SYNTAX_EXAMPLES.map(se => 
se.value).includes(removeBrackets(placeholderValue))
+        && SYNTAX_EXAMPLES.findIndex(se => 
removeBrackets(placeholderValue).startsWith(se.key)) === -1;
     const popoverId = "popover-selector-" + property.hasOwnProperty('name') ? 
(property as any).name : (property as any).id;
 
-    const hasPlaceholders = (propertyPlaceholders && 
propertyPlaceholders.length > 0 );
+    const hasPlaceholders = (propertyPlaceholders && 
propertyPlaceholders.length > 0);
 
     function parametersChanged(value: string | number | boolean | any) {
         if (property instanceof ComponentProperty) {
@@ -167,13 +169,23 @@ export function PropertyPlaceholderDropdown(props: Props) 
{
             shouldFocusToggleOnSelect
         >
             <DropdownList>
-                {hasPlaceholders && <DropdownGroup label="Application 
Properties">
-                    {propertyPlaceholders.map((pp, index) =>
-                        <DropdownItem value={pp} 
key={index}>{pp}</DropdownItem>
-                    )}
-                </DropdownGroup>}
+                {parameterPlaceholders &&
+                    <DropdownGroup label="Template Parameters" 
className='property-placeholder-dropdown'>
+                        {parameterPlaceholders.map((pp, index) =>
+                            <DropdownItem value={pp[0]} key={index} 
description={pp[1]}>{pp[0]}</DropdownItem>
+                        )}
+                    </DropdownGroup>
+                }
+                {parameterPlaceholders && <Divider component="li"/>}
+                {hasPlaceholders &&
+                    <DropdownGroup label="Application Properties" 
className='property-placeholder-dropdown'>
+                        {propertyPlaceholders.map((pp, index) =>
+                            <DropdownItem value={pp} 
key={index}>{pp}</DropdownItem>
+                        )}
+                    </DropdownGroup>
+                }
                 {hasPlaceholders && <Divider component="li"/>}
-                <DropdownGroup label="Syntax examples">
+                <DropdownGroup label="Syntax examples" 
className='property-placeholder-dropdown'>
                     {SYNTAX_EXAMPLES.map(se =>
                         <DropdownItem value={se.value} key={se.key} 
description={se.description}>
                             {se.value}
diff --git a/karavan-space/src/designer/utils/CamelUi.tsx 
b/karavan-space/src/designer/utils/CamelUi.tsx
index f89c8e85..a12e19d6 100644
--- a/karavan-space/src/designer/utils/CamelUi.tsx
+++ b/karavan-space/src/designer/utils/CamelUi.tsx
@@ -821,7 +821,7 @@ export class CamelUi {
 
     static getFlowCounts = (i: Integration): Map<string, number> => {
         const result = new Map<string, number>();
-        result.set('routes', i.spec.flows?.filter((e: any) => e.dslName === 
'RouteDefinition').length || 0);
+        result.set('routes', i.spec.flows?.filter((e: any) => 
['RouteDefinition', 'RouteTemplateDefinition'].includes(e.dslName)).length || 
0);
         result.set('rest', i.spec.flows?.filter((e: any) => e.dslName === 
'RestDefinition').length || 0);
         result.set('routeConfiguration', i.spec.flows?.filter((e: any) => 
e.dslName === 'RouteConfigurationDefinition').length || 0);
         const beans = i.spec.flows?.filter((e: any) => e.dslName === 'Beans');


Reply via email to