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 b495b175d6165ea3b909c61570c1d5b0f6ea6ccb Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Wed Feb 14 12:05:08 2024 -0500 Incoming navigation for #1109 --- .../src/main/webui/src/designer/DesignerStore.ts | 17 ++++- .../main/webui/src/designer/KaravanDesigner.tsx | 10 +-- .../webui/src/designer/route/DslConnections.tsx | 74 ++++++++++++++++++---- .../webui/src/designer/utils/InfrastructureAPI.ts | 4 +- .../src/main/webui/src/project/FileEditor.tsx | 71 +++++++++++++-------- .../src/main/webui/src/project/ProjectPanel.tsx | 8 +-- .../src/main/webui/src/topology/TopologyApi.tsx | 2 +- .../src/main/webui/src/topology/TopologyStore.ts | 9 --- .../src/main/webui/src/topology/TopologyTab.tsx | 5 +- 9 files changed, 136 insertions(+), 64 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 ef95b8d4..7a6aab41 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 @@ -20,6 +20,7 @@ import {DslPosition, EventBus} from "./utils/EventBus"; import {createWithEqualityFn} from "zustand/traditional"; import {shallow} from "zustand/shallow"; import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition"; +import {IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition"; interface IntegrationState { integration: Integration; @@ -27,6 +28,9 @@ interface IntegrationState { setIntegration: (integration: Integration, propertyOnly: boolean) => void; propertyOnly: boolean; reset: () => void; + files: IntegrationFile [] + setFiles: (files: IntegrationFile []) => void + resetFiles: (files: IntegrationFile []) => void } export const useIntegrationStore = createWithEqualityFn<IntegrationState>((set) => ({ @@ -46,7 +50,18 @@ export const useIntegrationStore = createWithEqualityFn<IntegrationState>((set) }, reset: () => { set({integration: Integration.createNew("demo", "plain"), json: '{}', propertyOnly: false}); - } + }, + files: [], + setFiles: (files: IntegrationFile []) => { + set((state: IntegrationState) => { + return {files: files}; + }); + }, + resetFiles: (files: IntegrationFile []) => { + set((state: IntegrationState) => { + return {files: [...files]}; + }); + }, }), shallow) 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 05831a38..58dbbf6d 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 @@ -28,7 +28,7 @@ import { import './karavan.css'; import {RouteDesigner} from "./route/RouteDesigner"; import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml"; -import {Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {Integration, IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {CamelUi} from "./utils/CamelUi"; import {useDesignerStore, useIntegrationStore} from "./DesignerStore"; @@ -48,7 +48,7 @@ interface Props { onSaveCustomCode: (name: string, code: string) => void onGetCustomCode: (name: string, javaType: string) => Promise<string | undefined> onSavePropertyPlaceholder: (key: string, value: string) => void - onInternalConsumerClick: (uri: string, name: string) => void + onInternalConsumerClick: (uri: string, name: string, direction: 'from' | 'to') => void filename: string yaml: string dark: boolean @@ -57,6 +57,7 @@ interface Props { tab?: "routes" | "rest" | "beans" propertyPlaceholders: string[] beans: RegistryBeanDefinition[] + files: IntegrationFile[] } export function KaravanDesigner(props: Props) { @@ -65,8 +66,8 @@ export function KaravanDesigner(props: Props) { const [setDark, hideLogDSL, setHideLogDSL, setSelectedStep, reset, badge, message, setPropertyPlaceholders, setBeans] = useDesignerStore((s) => [s.setDark, s.hideLogDSL, s.setHideLogDSL, s.setSelectedStep, s.reset, s.notificationBadge, s.notificationMessage, s.setPropertyPlaceholders, s.setBeans], shallow) - const [integration, setIntegration] = useIntegrationStore((s) => - [s.integration, s.setIntegration], shallow) + const [integration, setIntegration, resetFiles] = useIntegrationStore((s) => + [s.integration, s.setIntegration, s.resetFiles], shallow) useEffect(() => { const sub = EventBus.onIntegrationUpdate()?.subscribe((update: IntegrationUpdate) => @@ -92,6 +93,7 @@ export function KaravanDesigner(props: Props) { setDark(props.dark); setPropertyPlaceholders(props.propertyPlaceholders) setBeans(props.beans) + resetFiles(props.files) setHideLogDSL(props.hideLogDSL === true); return () => { sub?.unsubscribe(); diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx index ebb89933..072aaac9 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx @@ -24,28 +24,45 @@ import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt" import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils"; import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition"; import {v4 as uuidv4} from "uuid"; -import {DeleteElementIcon} from "../utils/ElementIcons"; -import {Button} from "@patternfly/react-core"; +import {Button, Tooltip} from "@patternfly/react-core"; import {InfrastructureAPI} from "../utils/InfrastructureAPI"; +import {getIntegrations} from "../../topology/TopologyApi"; +import {ComponentApi} from "karavan-core/lib/api/ComponentApi"; const overlapGap: number = 40; export function DslConnections() { - const [integration] = useIntegrationStore((state) => [state.integration], shallow) + const [integration, files] = useIntegrationStore((s) => [s.integration, s.files], shallow) const [width, height, top, left, hideLogDSL] = useDesignerStore((s) => [s.width, s.height, s.top, s.left, s.hideLogDSL], shallow) const [steps, addStep, deleteStep, clearSteps] = useConnectionsStore((s) => [s.steps, s.addStep, s.deleteStep, s.clearSteps], shallow) const [svgKey, setSvgKey] = useState<string>('svgKey'); + const [tons, setTons] = useState<Map<string, string[]>>(new Map<string, string[]>()); useEffect(() => { + const integrations = getIntegrations(files); + setTons(prevState => { + const data = new Map<string, string[]>(); + TopologyUtils.findTopologyOutgoingNodes(integrations).forEach(t => { + const key = (t.step as any)?.uri + ':' + (t.step as any)?.parameters.name; + if (data.has(key)) { + const list = data.get(key) || []; + list.push(t.routeId); + data.set(key, list); + } else { + data.set(key, [t.routeId]); + } + }); + return data; + }); const sub1 = EventBus.onPosition()?.subscribe((evt: DslPosition) => setPosition(evt)); return () => { sub1?.unsubscribe(); }; - }); + }, [files]); useEffect(() => { const toDelete1: string[] = Array.from(steps.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined); @@ -63,11 +80,17 @@ export function DslConnections() { } } + function isElementInternalComponent (element: CamelElement): boolean { + const uri = (element as any).uri; + const component = ComponentApi.findByName(uri); + return component !== undefined && (TopologyUtils.isComponentInternal(component.component.label)); + } + function getIncomings() { let outs: [string, number][] = Array.from(steps.values()) .filter(pos => ["FromDefinition"].includes(pos.step.dslName)) - .filter(pos => !TopologyUtils.isElementInternalComponent(pos.step)) - .filter(pos => !(pos.step.dslName === 'FromDefinition' && TopologyUtils.hasInternalUri(pos.step))) + .filter(pos => !isElementInternalComponent(pos.step)) + // .filter(pos => !(pos.step.dslName === 'FromDefinition' && TopologyUtils.hasInternalUri(pos.step))) .filter(pos => !(pos.step.dslName === 'FromDefinition' && (pos.step as any).uri === 'kamelet:source')) .sort((pos1: DslPosition, pos2: DslPosition) => { const y1 = pos1.headerRect.y + pos1.headerRect.height / 2; @@ -104,9 +127,22 @@ export function DslConnections() { } } + // function getToDirectSteps(name: string) { + // return Array.from(steps.values()) + // .filter(s => s.step.dslName === 'ToDefinition') + // .filter(s => ['direct','seda'].includes((s.step as any)?.uri)) + // .filter(s => (s.step as any)?.parameters?.name === name) + // } + function getIncomingIcons(data: [string, number]) { const pos = steps.get(data[0]); if (pos) { + const step = (pos.step as any); + const uri = step?.uri; + const directOrSeda: boolean = step && uri && step?.dslName === 'FromDefinition' && ['direct','seda'].includes(uri); + const name: string = directOrSeda ? (step?.parameters?.name) : undefined; + const routes = directOrSeda ? tons.get(uri + ':' +name) || [] : []; + // const localDirects = getToDirectSteps(name); const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top; const r = pos.headerRect.height / 2; const incomingX = 20; @@ -116,6 +152,16 @@ export function DslConnections() { <div key={pos.step.uuid + "-icon"} style={{display: "block", position: "absolute", top: imageY, left: imageX}}> {CamelUi.getConnectionIcon(pos.step)} + {routes.map((routeId, index) => + <Tooltip key={`${routeId}:${index}`} content={`Go to route:${routeId}`} position={"right"}> + <Button style={{position: 'absolute', left: 27, top: (index * 16) + (12), whiteSpace: 'nowrap', zIndex: 300, padding: 0}} + variant={'link'} + aria-label="Goto" + onClick={_ => InfrastructureAPI.onInternalConsumerClick(uri, name, 'to')}> + {routeId} + </Button> + </Tooltip> + )} </div> ) } @@ -204,13 +250,15 @@ export function DslConnections() { <div key={pos.step.uuid + "-icon"} style={{display: "block", position: "absolute", top: imageY, left: imageX}}> {CamelUi.getConnectionIcon(pos.step)} - {directOrSeda && - <Button style={{position: 'absolute', right: 27, top: -12, whiteSpace: 'nowrap', zIndex: 300, padding: 0}} - variant={'link'} - aria-label="Goto" - onClick={_ => InfrastructureAPI.onInternalConsumerClick(uri, name)}> - {name} - </Button> + {name !== undefined && + <Tooltip content={`Go to ${uri}:${name}`} position={"left"}> + <Button style={{position: 'absolute', right: 27, top: -12, whiteSpace: 'nowrap', zIndex: 300, padding: 0}} + variant={'link'} + aria-label="Goto" + onClick={_ => InfrastructureAPI.onInternalConsumerClick(uri, name, 'from')}> + {name} + </Button> + </Tooltip> } </div> ) diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/utils/InfrastructureAPI.ts b/karavan-web/karavan-app/src/main/webui/src/designer/utils/InfrastructureAPI.ts index 77ef1bd3..2acd5d54 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/utils/InfrastructureAPI.ts +++ b/karavan-web/karavan-app/src/main/webui/src/designer/utils/InfrastructureAPI.ts @@ -21,7 +21,7 @@ export class InfrastructureAPI { static onSaveCustomCode: (name: string, code: string) => void; static onSave: (filename: string, yaml: string, propertyOnly: boolean) => void; static onSavePropertyPlaceholder: (key: string, value: string) => void; - static onInternalConsumerClick: (uri: string, name: string) => void; + static onInternalConsumerClick: (uri: string, name: string, direction: 'from' | 'to') => void; static setOnGetCustomCode(onGetCustomCode: (name: string, javaType: string) => Promise<string | undefined>){ this.onGetCustomCode = onGetCustomCode @@ -39,7 +39,7 @@ export class InfrastructureAPI { this.onSavePropertyPlaceholder = onSavePropertyPlaceholder } - static setOnInternalConsumerClick(onInternalConsumerClick:(uri: string, name: string) => void){ + static setOnInternalConsumerClick(onInternalConsumerClick:(uri: string, name: string, direction: 'from' | 'to') => void){ this.onInternalConsumerClick = onInternalConsumerClick } 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 2be7acf6..ce4504fb 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 @@ -26,6 +26,7 @@ import {shallow} from "zustand/shallow"; import {CodeUtils} from "../util/CodeUtils"; import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition"; import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils"; +import {IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition"; interface Props { projectId: string @@ -37,9 +38,9 @@ const languages = new Map<string, string>([ ['properties', 'ini'] ]) -export function FileEditor (props: Props) { +export function FileEditor(props: Props) { - const [file, designerTab, setFile] = useFileStore((s) => [s.file, s.designerTab, s.setFile], shallow ) + const [file, designerTab, setFile] = useFileStore((s) => [s.file, s.designerTab, s.setFile], shallow) const [files] = useFilesStore((s) => [s.files], shallow); const [propertyPlaceholders, setPropertyPlaceholders] = useState<string[]>([]); const [beans, setBeans] = useState<RegistryBeanDefinition[]>([]); @@ -60,18 +61,18 @@ export function FileEditor (props: Props) { }) }, []); - function save (name: string, code: string) { + function save(name: string, code: string) { if (file) { file.code = code; ProjectService.updateFile(file, true); } } - function onGetCustomCode (name: string, javaType: string): Promise<string | undefined> { + function onGetCustomCode(name: string, javaType: string): Promise<string | undefined> { return new Promise<string | undefined>(resolve => resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code)); } - function onSavePropertyPlaceholder (key: string, value: string) { + function onSavePropertyPlaceholder(key: string, value: string) { const file = files.filter(f => f.name === 'application.properties')?.at(0); const code = file?.code?.concat('\n').concat(key).concat('=').concat(value); if (file && code) { @@ -80,43 +81,57 @@ export function FileEditor (props: Props) { } } - function internalConsumerClick(uri: string, name: string) { + function internalConsumerClick(uri: string, name: string, direction: 'from' | 'to') { const integrations = files.filter(f => f.name.endsWith(".camel.yaml")) .map(f => CamelDefinitionYaml.yamlToIntegration(f.name, f.code)); - const routes = TopologyUtils.findTopologyRouteNodes(integrations); - for (const route of routes) { - if (route?.from?.uri === uri && route?.from?.parameters?.name === name) { - const switchToFile = files.filter(f => f.name === route.fileName).at(0); - if (switchToFile){ - setFile('select', switchToFile); - setKey(Math.random().toString()) + if (direction === 'from') { + const routes = TopologyUtils.findTopologyRouteNodes(integrations); + for (const route of routes) { + if (route?.from?.uri === uri && route?.from?.parameters?.name === name) { + const switchToFile = files.filter(f => f.name === route.fileName).at(0); + if (switchToFile) { + setFile('select', switchToFile); + setKey(Math.random().toString()) + } + } + } + } else { + const nodes = TopologyUtils.findTopologyOutgoingNodes(integrations).filter(t => t.type === 'internal'); + for (const node of nodes) { + if ((node?.step as any)?.uri === uri && (node?.step as any)?.parameters?.name === name) { + const switchToFile = files.filter(f => f.name === node.fileName).at(0); + if (switchToFile) { + setFile('select', switchToFile); + setKey(Math.random().toString()) + } } } } } - function getDesigner () { + function getDesigner() { return ( file !== undefined && <KaravanDesigner key={key} - showCodeTab={true} - dark={false} - filename={file.name} - yaml={file.code} - tab={designerTab} - onSave={(name, yaml) => save(name, yaml)} - onSaveCustomCode={(name, code) => - ProjectService.updateFile(new ProjectFile(name + ".java", props.projectId, code, Date.now()), false)} - onGetCustomCode={onGetCustomCode} - propertyPlaceholders={propertyPlaceholders} - onSavePropertyPlaceholder={onSavePropertyPlaceholder} - beans={beans} - onInternalConsumerClick={internalConsumerClick} + showCodeTab={true} + dark={false} + filename={file.name} + yaml={file.code} + tab={designerTab} + onSave={(name, yaml) => save(name, yaml)} + onSaveCustomCode={(name, code) => + ProjectService.updateFile(new ProjectFile(name + ".java", props.projectId, code, Date.now()), false)} + onGetCustomCode={onGetCustomCode} + propertyPlaceholders={propertyPlaceholders} + onSavePropertyPlaceholder={onSavePropertyPlaceholder} + beans={beans} + onInternalConsumerClick={internalConsumerClick} + files={files.map(f => new IntegrationFile(f.name, f.code))} /> ) } - function getEditor () { + function getEditor() { const extension = file?.name.split('.').pop(); const language = extension && languages.has(extension) ? languages.get(extension) : extension; return ( diff --git a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx index 52d14df8..238d6b47 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx @@ -15,10 +15,10 @@ * limitations under the License. */ -import React, {useEffect, useState} from 'react'; +import React, {useEffect} from 'react'; import { Flex, - FlexItem, Modal, ModalVariant, PageSection + FlexItem, PageSection } from '@patternfly/react-core'; import '../designer/karavan.css'; import {FilesTab} from "./files/FilesTab"; @@ -30,7 +30,7 @@ import {ProjectService} from "../api/ProjectService"; import {shallow} from "zustand/shallow"; import {ImagesPanel} from "./builder/ImagesPanel"; import {ProjectContainerTab} from "./container/ProjectContainerTab"; -import {IntegrationFile} from "../topology/TopologyStore"; +import {IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition"; import {TopologyTab} from "../topology/TopologyTab"; import {Buffer} from "buffer"; import {CreateFileModal} from "./files/CreateFileModal"; @@ -53,7 +53,7 @@ export function ProjectPanel() { function onRefresh() { if (project.projectId) { ProjectService.refreshProjectData(project.projectId); - setTab(project.type === ProjectType.normal ? 'topology' : 'files') + setTab(project.type === ProjectType.normal ? 'topology' : 'files'); } } diff --git a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyApi.tsx b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyApi.tsx index c60c197b..a640d508 100644 --- a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyApi.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyApi.tsx @@ -39,7 +39,7 @@ import { TopologyRouteNode } from "karavan-core/lib/model/TopologyDefinition"; import CustomEdge from "./CustomEdge"; -import {IntegrationFile} from "./TopologyStore"; +import {IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition"; import CustomGroup from "./CustomGroup"; const NODE_DIAMETER = 60; diff --git a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyStore.ts b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyStore.ts index 4e9ee643..517e2794 100644 --- a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyStore.ts +++ b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyStore.ts @@ -18,15 +18,6 @@ import {createWithEqualityFn} from "zustand/traditional"; import {shallow} from "zustand/shallow"; -export class IntegrationFile { - name: string = ''; - code: string = ''; - - constructor(name: string, code: string) { - this.name = name; - this.code = code; - } -} interface TopologyState { selectedIds: string [] diff --git a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyTab.tsx b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyTab.tsx index 1f2d165e..694eeb64 100644 --- a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyTab.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyTab.tsx @@ -31,10 +31,11 @@ import { } from '@patternfly/react-topology'; import {customComponentFactory, getModel} from "./TopologyApi"; import {shallow} from "zustand/shallow"; -import {IntegrationFile, useTopologyStore} from "./TopologyStore"; +import {useTopologyStore} from "./TopologyStore"; import {TopologyPropertiesPanel} from "./TopologyPropertiesPanel"; import {TopologyToolbar} from "./TopologyToolbar"; import {useDesignerStore} from "../designer/DesignerStore"; +import {IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition"; interface Props { files: IntegrationFile[], @@ -137,7 +138,7 @@ export function TopologyTab(props: Props) { }); }, [ranker, controller, setRanker]); - return ( + return ( <TopologyView className="topology-panel" contextToolbar={!props.hideToolbar