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 2d4a90f6788bdbe887cdd5fac9b42eddda89b66e
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Thu Dec 5 15:16:08 2024 -0500

    ErrorBoundaryWrapper
---
 .../webui/src/designer/ErrorBoundaryWrapper.tsx    | 21 ++++++++++
 .../main/webui/src/designer/KaravanDesigner.tsx    | 45 +++++++++++++++++-----
 .../src/designer/ErrorBoundaryWrapper.tsx          | 21 ++++++++++
 karavan-space/src/designer/KaravanDesigner.tsx     | 45 +++++++++++++++++-----
 4 files changed, 112 insertions(+), 20 deletions(-)

diff --git a/karavan-app/src/main/webui/src/designer/ErrorBoundaryWrapper.tsx 
b/karavan-app/src/main/webui/src/designer/ErrorBoundaryWrapper.tsx
new file mode 100644
index 00000000..a8ef292d
--- /dev/null
+++ b/karavan-app/src/main/webui/src/designer/ErrorBoundaryWrapper.tsx
@@ -0,0 +1,21 @@
+import React, {ReactNode} from "react";
+
+export interface ErrorBoundaryState {
+    hasError: boolean;
+    error: Error | null;
+}
+
+export class ErrorBoundaryWrapper extends React.Component<{
+    children: ReactNode;
+    onError: (error: Error) => void;
+}> {
+    componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+        // Handle error (logging, etc.)
+        console.error("Error caught in ErrorBoundary:", error, errorInfo);
+        this.props.onError(error);
+    }
+
+    render() {
+        return this.props.children;
+    }
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx 
b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
index 511a2038..c4fa3c32 100644
--- a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
+++ b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
@@ -43,6 +43,7 @@ import BellIcon from 
'@patternfly/react-icons/dist/esm/icons/bell-icon';
 import {KameletDesigner} from "./kamelet/KameletDesigner";
 import {BeanFactoryDefinition} from "karavan-core/lib/model/CamelDefinition";
 import {VariableUtil} from "karavan-core/lib/api/VariableUtil";
+import {ErrorBoundaryState, ErrorBoundaryWrapper} from 
"./ErrorBoundaryWrapper";
 
 interface Props {
     onSave: (filename: string, yaml: string, propertyOnly: boolean) => void
@@ -65,7 +66,7 @@ export function KaravanDesigner(props: Props) {
     const [tab, setTab] = useState<string>('routes');
     const [setDark, setSelectedStep, reset, badge, message, 
setPropertyPlaceholders, setBeans] =
         useDesignerStore((s) =>
-        [s.setDark, s.setSelectedStep, s.reset, s.notificationBadge, 
s.notificationMessage, s.setPropertyPlaceholders, s.setBeans], shallow)
+            [s.setDark, s.setSelectedStep, s.reset, s.notificationBadge, 
s.notificationMessage, s.setPropertyPlaceholders, s.setBeans], shallow)
     const [integration, setIntegration, resetFiles, setVariables] = 
useIntegrationStore((s) =>
         [s.integration, s.setIntegration, s.resetFiles, s.setVariables], 
shallow)
 
@@ -73,6 +74,7 @@ export function KaravanDesigner(props: Props) {
         const sub = EventBus.onIntegrationUpdate()?.subscribe((update: 
IntegrationUpdate) =>
             save(update.integration, update.propertyOnly));
         try {
+            resetErrorBoundary();
             InfrastructureAPI.setOnSaveCustomCode(props.onSaveCustomCode);
             InfrastructureAPI.setOnGetCustomCode(props.onGetCustomCode);
             InfrastructureAPI.setOnSave(props.onSave);
@@ -104,6 +106,8 @@ export function KaravanDesigner(props: Props) {
             sub?.unsubscribe();
             setSelectedStep(undefined);
             reset();
+            resetErrorBoundary();
+            setIntegration(Integration.createNew("demo"), false);
         };
     }, []);
 
@@ -141,7 +145,7 @@ export function KaravanDesigner(props: Props) {
         const counts = CamelUi.getFlowCounts(integration);
         const count = counts.has(icon) && counts.get(icon) ? counts.get(icon) 
: undefined;
         const showCount = count && count > 0;
-        const color= showBadge && badge ? "red" : "initial";
+        const color = showBadge && badge ? "red" : "initial";
         return (
             <div className="top-menu-item" style={{color: color}}>
                 <TabTitleIcon>{getDesignerIcon(icon)}</TabTitleIcon>
@@ -149,9 +153,9 @@ export function KaravanDesigner(props: Props) {
                 {showCount && <Badge isRead 
className="count">{counts.get(icon)}</Badge>}
                 {showBadge && badge &&
                     <Button variant="link"
-                         icon={<BellIcon color="red"/>}
-                         style={{visibility: (badge ? 'visible' : 'hidden'), 
padding: '0', margin: '0'}}
-                         onClick={event => EventBus.sendAlert(message[0], 
message[1], 'danger')}/>
+                            icon={<BellIcon color="red"/>}
+                            style={{visibility: (badge ? 'visible' : 
'hidden'), padding: '0', margin: '0'}}
+                            onClick={event => EventBus.sendAlert(message[0], 
message[1], 'danger')}/>
                 }
             </div>
         )
@@ -159,6 +163,25 @@ export function KaravanDesigner(props: Props) {
 
     const isKamelet = integration.type === 'kamelet';
 
+    const [state, setState] = useState<ErrorBoundaryState>({ hasError: false, 
error: null });
+
+    const resetErrorBoundary = () => {
+        setState({ hasError: false, error: null });
+    };
+
+    // Mimic `getDerivedStateFromError`
+    const handleError = (error: Error) => {
+        setState({ hasError: true, error });
+        setTab('code')
+        console.log(props.filename, error)
+    }
+
+    useEffect(() => {
+        if (state.hasError && state.error) {
+            EventBus.sendAlert(state.error.message, 
state.error?.stack?.toString()?.substring(0, 300) || '', 'danger');
+        }
+    }, [state]);
+
     return (
         <PageSection variant={props.dark ? PageSectionVariants.darker : 
PageSectionVariants.light}
                      className="page"
@@ -178,11 +201,13 @@ export function KaravanDesigner(props: Props) {
                     {props.showCodeTab && <Tab eventKey='code' 
title={getTab("YAML", "YAML Code", "code", true)}></Tab>}
                 </Tabs>
             </div>
-            {tab === 'kamelet' && <KameletDesigner/>}
-            {tab === 'routes' && <RouteDesigner/>}
-            {tab === 'rest' && <RestDesigner/>}
-            {tab === 'beans' && <BeansDesigner/>}
-            {tab === 'code' && <CodeEditor/>}
+            <ErrorBoundaryWrapper onError={handleError}>
+                {tab === 'kamelet' && <KameletDesigner/>}
+                {tab === 'routes' && <RouteDesigner/>}
+                {tab === 'rest' && <RestDesigner/>}
+                {tab === 'beans' && <BeansDesigner/>}
+                {tab === 'code' && <CodeEditor/>}
+            </ErrorBoundaryWrapper>
         </PageSection>
     )
 }
\ No newline at end of file
diff --git a/karavan-space/src/designer/ErrorBoundaryWrapper.tsx 
b/karavan-space/src/designer/ErrorBoundaryWrapper.tsx
new file mode 100644
index 00000000..a8ef292d
--- /dev/null
+++ b/karavan-space/src/designer/ErrorBoundaryWrapper.tsx
@@ -0,0 +1,21 @@
+import React, {ReactNode} from "react";
+
+export interface ErrorBoundaryState {
+    hasError: boolean;
+    error: Error | null;
+}
+
+export class ErrorBoundaryWrapper extends React.Component<{
+    children: ReactNode;
+    onError: (error: Error) => void;
+}> {
+    componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+        // Handle error (logging, etc.)
+        console.error("Error caught in ErrorBoundary:", error, errorInfo);
+        this.props.onError(error);
+    }
+
+    render() {
+        return this.props.children;
+    }
+}
\ No newline at end of file
diff --git a/karavan-space/src/designer/KaravanDesigner.tsx 
b/karavan-space/src/designer/KaravanDesigner.tsx
index 511a2038..c4fa3c32 100644
--- a/karavan-space/src/designer/KaravanDesigner.tsx
+++ b/karavan-space/src/designer/KaravanDesigner.tsx
@@ -43,6 +43,7 @@ import BellIcon from 
'@patternfly/react-icons/dist/esm/icons/bell-icon';
 import {KameletDesigner} from "./kamelet/KameletDesigner";
 import {BeanFactoryDefinition} from "karavan-core/lib/model/CamelDefinition";
 import {VariableUtil} from "karavan-core/lib/api/VariableUtil";
+import {ErrorBoundaryState, ErrorBoundaryWrapper} from 
"./ErrorBoundaryWrapper";
 
 interface Props {
     onSave: (filename: string, yaml: string, propertyOnly: boolean) => void
@@ -65,7 +66,7 @@ export function KaravanDesigner(props: Props) {
     const [tab, setTab] = useState<string>('routes');
     const [setDark, setSelectedStep, reset, badge, message, 
setPropertyPlaceholders, setBeans] =
         useDesignerStore((s) =>
-        [s.setDark, s.setSelectedStep, s.reset, s.notificationBadge, 
s.notificationMessage, s.setPropertyPlaceholders, s.setBeans], shallow)
+            [s.setDark, s.setSelectedStep, s.reset, s.notificationBadge, 
s.notificationMessage, s.setPropertyPlaceholders, s.setBeans], shallow)
     const [integration, setIntegration, resetFiles, setVariables] = 
useIntegrationStore((s) =>
         [s.integration, s.setIntegration, s.resetFiles, s.setVariables], 
shallow)
 
@@ -73,6 +74,7 @@ export function KaravanDesigner(props: Props) {
         const sub = EventBus.onIntegrationUpdate()?.subscribe((update: 
IntegrationUpdate) =>
             save(update.integration, update.propertyOnly));
         try {
+            resetErrorBoundary();
             InfrastructureAPI.setOnSaveCustomCode(props.onSaveCustomCode);
             InfrastructureAPI.setOnGetCustomCode(props.onGetCustomCode);
             InfrastructureAPI.setOnSave(props.onSave);
@@ -104,6 +106,8 @@ export function KaravanDesigner(props: Props) {
             sub?.unsubscribe();
             setSelectedStep(undefined);
             reset();
+            resetErrorBoundary();
+            setIntegration(Integration.createNew("demo"), false);
         };
     }, []);
 
@@ -141,7 +145,7 @@ export function KaravanDesigner(props: Props) {
         const counts = CamelUi.getFlowCounts(integration);
         const count = counts.has(icon) && counts.get(icon) ? counts.get(icon) 
: undefined;
         const showCount = count && count > 0;
-        const color= showBadge && badge ? "red" : "initial";
+        const color = showBadge && badge ? "red" : "initial";
         return (
             <div className="top-menu-item" style={{color: color}}>
                 <TabTitleIcon>{getDesignerIcon(icon)}</TabTitleIcon>
@@ -149,9 +153,9 @@ export function KaravanDesigner(props: Props) {
                 {showCount && <Badge isRead 
className="count">{counts.get(icon)}</Badge>}
                 {showBadge && badge &&
                     <Button variant="link"
-                         icon={<BellIcon color="red"/>}
-                         style={{visibility: (badge ? 'visible' : 'hidden'), 
padding: '0', margin: '0'}}
-                         onClick={event => EventBus.sendAlert(message[0], 
message[1], 'danger')}/>
+                            icon={<BellIcon color="red"/>}
+                            style={{visibility: (badge ? 'visible' : 
'hidden'), padding: '0', margin: '0'}}
+                            onClick={event => EventBus.sendAlert(message[0], 
message[1], 'danger')}/>
                 }
             </div>
         )
@@ -159,6 +163,25 @@ export function KaravanDesigner(props: Props) {
 
     const isKamelet = integration.type === 'kamelet';
 
+    const [state, setState] = useState<ErrorBoundaryState>({ hasError: false, 
error: null });
+
+    const resetErrorBoundary = () => {
+        setState({ hasError: false, error: null });
+    };
+
+    // Mimic `getDerivedStateFromError`
+    const handleError = (error: Error) => {
+        setState({ hasError: true, error });
+        setTab('code')
+        console.log(props.filename, error)
+    }
+
+    useEffect(() => {
+        if (state.hasError && state.error) {
+            EventBus.sendAlert(state.error.message, 
state.error?.stack?.toString()?.substring(0, 300) || '', 'danger');
+        }
+    }, [state]);
+
     return (
         <PageSection variant={props.dark ? PageSectionVariants.darker : 
PageSectionVariants.light}
                      className="page"
@@ -178,11 +201,13 @@ export function KaravanDesigner(props: Props) {
                     {props.showCodeTab && <Tab eventKey='code' 
title={getTab("YAML", "YAML Code", "code", true)}></Tab>}
                 </Tabs>
             </div>
-            {tab === 'kamelet' && <KameletDesigner/>}
-            {tab === 'routes' && <RouteDesigner/>}
-            {tab === 'rest' && <RestDesigner/>}
-            {tab === 'beans' && <BeansDesigner/>}
-            {tab === 'code' && <CodeEditor/>}
+            <ErrorBoundaryWrapper onError={handleError}>
+                {tab === 'kamelet' && <KameletDesigner/>}
+                {tab === 'routes' && <RouteDesigner/>}
+                {tab === 'rest' && <RestDesigner/>}
+                {tab === 'beans' && <BeansDesigner/>}
+                {tab === 'code' && <CodeEditor/>}
+            </ErrorBoundaryWrapper>
         </PageSection>
     )
 }
\ No newline at end of file

Reply via email to