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