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 2024/01/31 22:02:28 UTC

(camel-karavan) 04/04: #1091 in App

This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git

commit 7efc3af456fa0ecb65584a990b969a5e2b5f9363
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Wed Jan 31 16:57:44 2024 -0500

    #1091 in App
---
 .../src/main/webui/src/designer/DesignerStore.ts   | 16 ++++-
 .../main/webui/src/designer/KaravanDesigner.tsx    |  8 ++-
 .../webui/src/designer/property/DslProperties.css  |  7 +-
 .../webui/src/designer/property/DslProperties.tsx  | 10 ++-
 .../property/property/ComponentPropertyField.tsx   | 49 +++++++++----
 .../ComponentPropertyPlaceholderDropdown.css       | 24 +++++++
 .../ComponentPropertyPlaceholderDropdown.tsx       | 81 ++++++++++++++++++++++
 .../property/property/DslPropertyField.tsx         |  1 +
 .../src/main/webui/src/project/FileEditor.tsx      | 18 ++++-
 .../src/main/webui/src/util/StringUtils.ts         | 21 ++++++
 10 files changed, 205 insertions(+), 30 deletions(-)

diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts
index 407713a7..fe198a7f 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts
@@ -168,7 +168,8 @@ type DesignerState = {
     height: number,
     top: number,
     left: number,
-    moveElements: [string | undefined, string | undefined]
+    moveElements: [string | undefined, string | undefined],
+    propertyPlaceholders: string[];
 }
 
 const designerState: DesignerState = {
@@ -186,7 +187,8 @@ const designerState: DesignerState = {
     height: 0,
     top: 0,
     left: 0,
-    moveElements: [undefined, undefined]
+    moveElements: [undefined, undefined],
+    propertyPlaceholders: []
 };
 
 type DesignerAction = {
@@ -203,6 +205,7 @@ type DesignerAction = {
     reset: () => void;
     setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => void;
     setMoveElements: (moveElements: [string | undefined, string | undefined]) => void;
+    setPropertyPlaceholders: (propertyPlaceholders: string[]) => void;
 }
 
 export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({
@@ -257,5 +260,12 @@ export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAct
     },
     setMoveElements: (moveElements: [string | undefined, string | undefined]) => {
         set({moveElements: moveElements})
-    }
+    },
+    setPropertyPlaceholders: (propertyPlaceholders: string[]) => {
+        set((state: DesignerState) => {
+            state.propertyPlaceholders.length = 0;
+            state.propertyPlaceholders.push(...propertyPlaceholders);
+            return state;
+        })
+    },
 }), shallow)
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
index 626f984b..349ae548 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
@@ -41,7 +41,6 @@ import {BeansDesigner} from "./beans/BeansDesigner";
 import {CodeEditor} from "./editor/CodeEditor";
 import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon';
 import {KameletDesigner} from "./kamelet/KameletDesigner";
-import {v4 as uuidv4} from "uuid";
 
 interface Props {
     onSave: (filename: string, yaml: string, propertyOnly: boolean) => void
@@ -53,13 +52,15 @@ interface Props {
     hideLogDSL?: boolean
     showCodeTab: boolean
     tab?: "routes" | "rest" | "beans"
+    propertyPlaceholders: string[]
 }
 
 export function KaravanDesigner(props: Props) {
 
     const [tab, setTab] = useState<string>('routes');
-    const [setDark, hideLogDSL, setHideLogDSL, setSelectedStep, reset, badge, message] = useDesignerStore((s) =>
-        [s.setDark, s.hideLogDSL, s.setHideLogDSL, s.setSelectedStep, s.reset, s.notificationBadge, s.notificationMessage], shallow)
+    const [setDark, hideLogDSL, setHideLogDSL, setSelectedStep, reset, badge, message, setPropertyPlaceholders] =
+        useDesignerStore((s) =>
+        [s.setDark, s.hideLogDSL, s.setHideLogDSL, s.setSelectedStep, s.reset, s.notificationBadge, s.notificationMessage, s.setPropertyPlaceholders], shallow)
     const [integration, setIntegration] = useIntegrationStore((s) =>
         [s.integration, s.setIntegration], shallow)
 
@@ -83,6 +84,7 @@ export function KaravanDesigner(props: Props) {
         setTab(designerTab || 'routes')
         reset();
         setDark(props.dark);
+        setPropertyPlaceholders(props.propertyPlaceholders)
         setHideLogDSL(props.hideLogDSL === true);
         return () => {
             sub?.unsubscribe();
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.css b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.css
index dd7cb199..43562bcb 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.css
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.css
@@ -248,10 +248,15 @@
     min-height: 6px;
 }
 
+.karavan .properties .header-menu-toggle {
+    padding-left: 6px;
+    padding-right: 0;
+}
+
 .karavan .properties .component-headers {
     margin-left: 24px;
 }
 
 .karavan .properties .component-headers .pf-v5-c-clipboard-copy.pf-m-inline {
     background-color: transparent;
-}
\ No newline at end of file
+}
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
index 6169d656..ed56d2eb 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
@@ -27,7 +27,7 @@ import {
     MenuToggleElement,
     MenuToggle,
     DropdownList,
-    DropdownItem, Label, Flex, LabelGroup, Popover, FlexItem, Badge, ClipboardCopy, ClipboardCopyAction,
+    DropdownItem, Flex, Popover, FlexItem, Badge, ClipboardCopy,
 } from '@patternfly/react-core';
 import '../karavan.css';
 import './DslProperties.css';
@@ -85,18 +85,16 @@ export function DslProperties(props: Props) {
         const showMenu = hasSteps || targetDsl !== undefined;
         return showMenu ?
             <Dropdown
-                style={{inset: "0px auto auto -70px important!"}}
-                className={"xxx"}
+                popperProps={{position: "end"}}
                 isOpen={isMenuOpen}
                 onSelect={() => {
                 }}
                 onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
                 toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
                     <MenuToggle
-                        style={{width: "240px", display: "flex", flexDirection: "row", justifyContent: "end"}}
-                        className={"zzzz"}
+                        className="header-menu-toggle"
                         ref={toggleRef}
-                        aria-label="kebab dropdown toggle"
+                        aria-label="menu"
                         variant="plain"
                         onClick={() => setMenuOpen(!isMenuOpen)}
                         isExpanded={isMenuOpen}
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx
index ae63e323..a25acafc 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx
@@ -21,10 +21,13 @@ import {
     Popover,
     Switch,
     InputGroup,
-    TextArea,
     Tooltip,
     Button,
-    capitalize, InputGroupItem, TextInputGroup, TextVariants, Text
+    capitalize,
+    InputGroupItem,
+    TextInputGroup,
+    TextVariants,
+    Text,
 } from '@patternfly/react-core';
 import {
     Select,
@@ -51,6 +54,7 @@ import {shallow} from "zustand/shallow";
 import {KubernetesIcon} from "../../icons/ComponentIcons";
 import EditorIcon from "@patternfly/react-icons/dist/js/icons/code-icon";
 import {ModalEditor} from "./ModalEditor";
+import {ComponentPropertyPlaceholderDropdown} from "./ComponentPropertyPlaceholderDropdown";
 
 const prefix = "parameters";
 const beanPrefix = "#bean:";
@@ -67,16 +71,18 @@ export function ComponentPropertyField(props: Props) {
     const {onParametersChange, getInternalComponentName} = usePropertiesHook();
 
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
-    const [dark, setSelectedStep] = useDesignerStore((s) => [s.dark, s.setSelectedStep], shallow)
+    const [dark, setSelectedStep, propertyPlaceholders] = useDesignerStore((s) =>
+        [s.dark, s.setSelectedStep, s.propertyPlaceholders], shallow)
 
     const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>());
     const [showEditor, setShowEditor] = useState<boolean>(false);
     const [showPassword, setShowPassword] = useState<boolean>(false);
     const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false);
     const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined);
-
     const [id, setId] = useState<string>(prefix + "-" + props.property.name);
+
     const ref = useRef<any>(null);
+    const [isOpenPlaceholdersDropdown, setOpenPlaceholdersDropdown] = useState<boolean>(false);
 
 
     function parametersChanged(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) {
@@ -262,20 +268,30 @@ export function ComponentPropertyField(props: Props) {
                     </Button>
                 </Tooltip>
             }
+            <InputGroupItem>
+                <ComponentPropertyPlaceholderDropdown property={property}/>
+            </InputGroupItem>
         </InputGroup>
     }
 
-    function getTextInput(property: ComponentProperty, value: any) {
+    function getSpecialStringInput(property: ComponentProperty, value: any) {
         return (
-            <TextInput
-                className="text-field" isRequired
-                type={(property.secret ? "password" : "text")}
-                id={id} name={id}
-                value={value !== undefined ? value : property.defaultValue}
-                customIcon={<Text component={TextVariants.p}>{property.type}</Text>}
-                onChange={(_, value) => {
-                    parametersChanged(property.name, value, property.kind === 'path')
-                }}/>
+            <InputGroup>
+                <InputGroupItem isFill>
+                    <TextInput
+                        className="text-field" isRequired
+                        type={(property.secret ? "password" : "text")}
+                        id={id} name={id}
+                        value={value !== undefined ? value : property.defaultValue}
+                        customIcon={<Text component={TextVariants.p}>{property.type}</Text>}
+                        onChange={(_, value) => {
+                            parametersChanged(property.name, value, property.kind === 'path')
+                        }}/>
+                </InputGroupItem>
+                <InputGroupItem>
+                    <ComponentPropertyPlaceholderDropdown property={property}/>
+                </InputGroupItem>
+            </InputGroup>
         )
     }
 
@@ -330,6 +346,9 @@ export function ComponentPropertyField(props: Props) {
                         onChange={(_, v) => parametersChanged(property.name, v)}
                     />
                 </InputGroupItem>
+                <InputGroupItem>
+                    <ComponentPropertyPlaceholderDropdown property={property}/>
+                </InputGroupItem>
             </TextInputGroup>
         )
     }
@@ -362,7 +381,7 @@ export function ComponentPropertyField(props: Props) {
             {property.type === 'string' && property.enum === undefined && !canBeInternalUri(property)
                 && getStringInput(property, value)}
             {['duration', 'integer', 'int', 'number'].includes(property.type) && property.enum === undefined && !canBeInternalUri(property)
-                && getTextInput(property, value)}
+                && getSpecialStringInput(property, value)}
             {['object'].includes(property.type) && !property.enum
                 && getSelectBean(property, value)}
             {['string', 'object'].includes(property.type) && property.enum
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css
new file mode 100644
index 00000000..7d5d2d43
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.karavan .properties .property-placeholder-toggle {
+    padding-left: 6px;
+    padding-right: 6px;
+}
+.karavan .properties .property-placeholder-toggle .pf-v5-c-menu-toggle__controls {
+    display: none;
+}
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx
new file mode 100644
index 00000000..270f9549
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React, {useState} from 'react';
+import {
+    Dropdown,
+    MenuToggleElement,
+    MenuToggle,
+    DropdownList, DropdownItem
+} from '@patternfly/react-core';
+import '../../karavan.css';
+import './ComponentPropertyPlaceholderDropdown.css';
+import "@patternfly/patternfly/patternfly.css";
+import {ComponentProperty} from "karavan-core/lib/model/ComponentModels";
+import {RouteToCreate} from "../../utils/CamelUi";
+import {usePropertiesHook} from "../usePropertiesHook";
+import {useDesignerStore} from "../../DesignerStore";
+import {shallow} from "zustand/shallow";
+import EllipsisVIcon from "@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon";
+
+
+interface Props {
+    property: ComponentProperty,
+}
+
+export function ComponentPropertyPlaceholderDropdown(props: Props) {
+
+    const {onParametersChange} = usePropertiesHook();
+    const [propertyPlaceholders] = useDesignerStore((s) => [s.propertyPlaceholders], shallow)
+    const [isOpenPlaceholdersDropdown, setOpenPlaceholdersDropdown] = useState<boolean>(false);
+
+    function parametersChanged(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) {
+        onParametersChange(parameter, value, pathParameter, newRoute);
+    }
+
+    const property: ComponentProperty = props.property;
+    return (
+        propertyPlaceholders && propertyPlaceholders.length > 0 ?
+            <Dropdown
+                popperProps={{position: "end"}}
+                isOpen={isOpenPlaceholdersDropdown}
+                onSelect={(_, value) => {
+                    parametersChanged(property.name, `{{${value}}}`, property.kind === 'path')
+                    setOpenPlaceholdersDropdown(false);
+                }}
+                onOpenChange={(isOpen: boolean) => setOpenPlaceholdersDropdown(isOpen)}
+                toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+                    <MenuToggle className="property-placeholder-toggle"
+                                ref={toggleRef}
+                                aria-label="placeholder menu"
+                                variant="default"
+                                onClick={() => setOpenPlaceholdersDropdown(!isOpenPlaceholdersDropdown)}
+                                isExpanded={isOpenPlaceholdersDropdown}
+                    >
+                        <EllipsisVIcon/>
+                    </MenuToggle>
+                )}
+                shouldFocusToggleOnSelect
+            >
+                <DropdownList>
+                    {propertyPlaceholders.map((pp, index) =>
+                        <DropdownItem value={pp} key={index}>{pp}</DropdownItem>
+                    )}
+                </DropdownList>
+            </Dropdown>
+            : <></>
+    )
+}
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx
index aa093618..59066667 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx
@@ -74,6 +74,7 @@ import {
 import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
 import {KubernetesIcon} from "../../icons/ComponentIcons";
 import {BeanProperties} from "./BeanProperties";
+import {ComponentPropertyPlaceholderDropdown} from "./ComponentPropertyPlaceholderDropdown";
 
 interface Props {
     property: PropertyMeta,
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx b/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx
index 7193243a..6fe00b39 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx
@@ -14,15 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
+import React, {useEffect, useState} from 'react';
 import '../designer/karavan.css';
 import Editor from "@monaco-editor/react";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
-import {ProjectFile} from "../api/ProjectModels";
+import {Project, ProjectFile} from "../api/ProjectModels";
 import {useFilesStore, useFileStore} from "../api/ProjectStore";
 import {KaravanDesigner} from "../designer/KaravanDesigner";
 import {ProjectService} from "../api/ProjectService";
 import {shallow} from "zustand/shallow";
+import {KaravanApi} from "../api/KaravanApi";
+import {getPropertyPlaceholders} from "../util/StringUtils";
 
 interface Props {
     projectId: string
@@ -37,6 +39,17 @@ const languages = new Map<string, string>([
 export function FileEditor (props: Props) {
 
     const [file, designerTab] = useFileStore((s) => [s.file, s.designerTab], shallow )
+    const [files] = useFilesStore((s) => [s.files], shallow);
+    const [propertyPlaceholders, setPropertyPlaceholders] = useState<string[]>([]);
+
+    useEffect(() => {
+        const pp = getPropertyPlaceholders(files);
+        setPropertyPlaceholders(prevState => {
+            prevState.length = 0;
+            prevState.push(...pp);
+            return prevState;
+        })
+    }, []);
 
     function save (name: string, code: string) {
         if (file) {
@@ -63,6 +76,7 @@ export function FileEditor (props: Props) {
                 onSaveCustomCode={(name, code) =>
                     ProjectService.updateFile(new ProjectFile(name + ".java", props.projectId, code, Date.now()), false)}
                 onGetCustomCode={onGetCustomCode}
+                propertyPlaceholders={propertyPlaceholders}
             />
         )
     }
diff --git a/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts b/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts
index 407778f1..6b8a8986 100644
--- a/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts
@@ -1,3 +1,24 @@
+import {ProjectFile} from "../api/ProjectModels";
+
 export function isEmpty(str: string) {
     return !str?.trim();
+}
+
+export function getPropertyPlaceholders(files: ProjectFile[]): string[] {
+    const result: string[] = []
+    const file = files.filter(f => f.name === 'application.properties')?.at(0);
+    if (file) {
+        const code = file.code;
+        const lines = code.split('\n').map((line) => line.trim());
+        lines
+            .filter(line => !line.startsWith("camel.") && !line.startsWith("jkube.") && !line.startsWith("jib."))
+            .filter(line => line !== undefined && line !== null && line.length > 0)
+            .forEach(line => {
+            const parts = line.split("=");
+            if (parts.length > 0) {
+                result.push(parts[0]);
+            }
+        })
+    }
+    return result;
 }
\ No newline at end of file