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 8b49a8c216a9a12825f061e3a345c3c6c5d29e75
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Wed Nov 23 16:00:26 2022 -0500

    Fix #532
---
 karavan-core/src/core/api/CamelDefinitionApiExt.ts |  22 ++-
 karavan-core/src/core/api/CamelDefinitionYaml.ts   |   7 +
 karavan-core/src/core/api/CamelUtil.ts             |  14 +-
 .../src/core/model/IntegrationDefinition.ts        |   2 +-
 karavan-core/test/errorHandler.spec.ts             |  35 +++++
 karavan-core/test/errorHandler1.yaml               |  18 +++
 karavan-designer/src/App.tsx                       |   6 +-
 .../src/{designer => }/DesignerPage.tsx            |  57 +++++--
 karavan-designer/src/designer/KaravanDesigner.tsx  |  10 +-
 .../src/designer/error/ErrorDesigner.tsx           |  74 ----------
 .../src/designer/error/ErrorHandlerCard.tsx        |  48 ++++++
 .../src/designer/error/ErrorHandlerDesigner.tsx    | 164 +++++++++++++++++++++
 karavan-designer/src/designer/karavan.css          |  12 +-
 .../designer/route/property/DslPropertyField.tsx   |   5 +-
 karavan-designer/src/designer/utils/CamelUi.tsx    |  13 +-
 15 files changed, 385 insertions(+), 102 deletions(-)

diff --git a/karavan-core/src/core/api/CamelDefinitionApiExt.ts 
b/karavan-core/src/core/api/CamelDefinitionApiExt.ts
index fcd867f..3317253 100644
--- a/karavan-core/src/core/api/CamelDefinitionApiExt.ts
+++ b/karavan-core/src/core/api/CamelDefinitionApiExt.ts
@@ -19,7 +19,7 @@ import {ComponentApi} from "./ComponentApi";
 import {CamelUtil} from "./CamelUtil";
 import {
     NamedBeanDefinition,
-    ExpressionDefinition, RouteDefinition, RestDefinition, 
RestConfigurationDefinition
+    ExpressionDefinition, RouteDefinition, RestDefinition, 
RestConfigurationDefinition, ErrorHandlerDefinition
 } from "../model/CamelDefinition";
 import {
     Beans,
@@ -253,6 +253,26 @@ export class CamelDefinitionApiExt {
         return integration;
     }
 
+    static addErrorHandlerToIntegration = (integration: Integration, 
errorHandler: ErrorHandlerDefinition): Integration => {
+        const flows: any[] = [];
+        if (integration.spec.flows?.filter(flow => flow.dslName === 
'ErrorHandlerDefinition').length === 0) {
+            flows.push(...integration.spec.flows);
+            flows.push(errorHandler)
+        } else {
+            flows.push(...integration.spec.flows?.filter(flow => flow.dslName 
!== 'ErrorHandlerDefinition') || []);
+            flows.push(errorHandler)
+        }
+        integration.spec.flows = flows;
+        return integration;
+    }
+
+    static deleteErrorHandlerFromIntegration = (integration: Integration): 
Integration => {
+        const flows: any[] = [];
+        flows.push(...integration.spec.flows?.filter(flow => flow.dslName !== 
'ErrorHandlerDefinition') || []);
+        integration.spec.flows = flows;
+        return integration;
+    }
+
     static addRestToIntegration = (integration: Integration, rest: 
RestDefinition): Integration => {
         const flows: any[] = [];
         integration.spec.flows?.push(rest)
diff --git a/karavan-core/src/core/api/CamelDefinitionYaml.ts 
b/karavan-core/src/core/api/CamelDefinitionYaml.ts
index 769dc39..2da9985 100644
--- a/karavan-core/src/core/api/CamelDefinitionYaml.ts
+++ b/karavan-core/src/core/api/CamelDefinitionYaml.ts
@@ -126,6 +126,11 @@ export class CamelDefinitionYaml {
                 || stepName === 'doFinally'
                 || stepName === 'resilience4jConfiguration'
                 || stepName === 'faultToleranceConfiguration'
+                || stepName === 'deadLetterChannel'
+                || stepName === 'defaultErrorHandler'
+                || stepName === 'jtaTransactionErrorHandler'
+                || stepName === 'noErrorHandler'
+                || stepName === 'springTransactionErrorHandler'
                 || key === 'from') {
                 delete newValue.inArray;
                 delete newValue.inSteps;
@@ -184,6 +189,8 @@ export class CamelDefinitionYaml {
             .forEach((f: any) =>  
result.push(CamelDefinitionYamlStep.readRouteDefinition(new 
RouteDefinition({from: f.from}))));
         flows.filter((e: any) => e.hasOwnProperty('beans'))
             .forEach((b: any) => 
result.push(CamelDefinitionYaml.readBeanDefinition(b)));
+        flows.filter((e: any) => e.hasOwnProperty('errorHandler'))
+            .forEach((e: any) => 
result.push(CamelDefinitionYamlStep.readErrorHandlerDefinition(e.errorHandler)));
         return result;
     }
 
diff --git a/karavan-core/src/core/api/CamelUtil.ts 
b/karavan-core/src/core/api/CamelUtil.ts
index 46a3217..762e83b 100644
--- a/karavan-core/src/core/api/CamelUtil.ts
+++ b/karavan-core/src/core/api/CamelUtil.ts
@@ -19,7 +19,7 @@ import {
     CamelElement, Beans
 } from "../model/IntegrationDefinition";
 import {CamelDefinitionApi} from "./CamelDefinitionApi";
-import {KameletDefinition, NamedBeanDefinition, ToDefinition} from 
"../model/CamelDefinition";
+import {ErrorHandlerDefinition, KameletDefinition, NamedBeanDefinition, 
ToDefinition} from "../model/CamelDefinition";
 import {KameletApi} from "./KameletApi";
 import {KameletModel, Property} from "../model/KameletModels";
 import {ComponentProperty} from "../model/ComponentModels";
@@ -41,6 +41,11 @@ export class CamelUtil {
                 (beans as Beans).beans.forEach(b => 
newBeans.beans.push(CamelUtil.cloneBean(b)));
                 flows.push(newBeans);
             });
+        int.spec.flows?.filter((e: any) => e.dslName === 'ErrorHandler')
+            .forEach(errorHandler => {
+                const newErrorHandler = 
CamelUtil.cloneErrorHandler(errorHandler);
+                flows.push(newErrorHandler);
+            });
         int.spec.flows = flows;
         return int;
     }
@@ -57,6 +62,13 @@ export class CamelUtil {
         return newBean;
     }
 
+    static cloneErrorHandler = (error: ErrorHandlerDefinition): 
ErrorHandlerDefinition => {
+        const clone = JSON.parse(JSON.stringify(error));
+        const newError = new ErrorHandlerDefinition(clone);
+        newError.uuid = error.uuid;
+        return newError;
+    }
+
     static capitalizeName = (name: string) => {
         try {
             return name[0].toUpperCase() + name.substring(1);
diff --git a/karavan-core/src/core/model/IntegrationDefinition.ts 
b/karavan-core/src/core/model/IntegrationDefinition.ts
index 433d97d..03fb89c 100644
--- a/karavan-core/src/core/model/IntegrationDefinition.ts
+++ b/karavan-core/src/core/model/IntegrationDefinition.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 import {v4 as uuidv4} from 'uuid';
-import {NamedBeanDefinition} from "./CamelDefinition";
+import {ErrorHandlerDefinition, NamedBeanDefinition} from "./CamelDefinition";
 
 
 export class Spec {
diff --git a/karavan-core/test/errorHandler.spec.ts 
b/karavan-core/test/errorHandler.spec.ts
new file mode 100644
index 0000000..051d3ff
--- /dev/null
+++ b/karavan-core/test/errorHandler.spec.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 {expect} from 'chai';
+import * as fs from 'fs';
+import 'mocha';
+import {CamelDefinitionYaml} from "../src/core/api/CamelDefinitionYaml";
+
+
+describe('Global Error Handler', () => {
+
+    it('Read Global Error Handler from plain YAML', () => {
+        const yaml = fs.readFileSync('test/errorHandler1.yaml', {encoding: 
'utf8', flag: 'r'});
+        const i = CamelDefinitionYaml.yamlToIntegration("errorHandler1.yaml", 
yaml);
+        expect(i.metadata.name).to.equal('errorHandler1.yaml');
+        expect(i.spec.flows?.length).to.equal(2);
+        expect(i.type).to.equal('plain');
+        
expect(i.spec.flows?.[1].deadLetterChannel.deadLetterUri).to.equal('log:dlq');
+        
expect(i.spec.flows?.[1].deadLetterChannel.useOriginalMessage).to.equal(true);
+        expect(i.spec.flows?.[1].deadLetterChannel.level).to.equal('TRACE');
+    });
+});
\ No newline at end of file
diff --git a/karavan-core/test/errorHandler1.yaml 
b/karavan-core/test/errorHandler1.yaml
new file mode 100644
index 0000000..29eabd7
--- /dev/null
+++ b/karavan-core/test/errorHandler1.yaml
@@ -0,0 +1,18 @@
+- errorHandler:
+    deadLetterChannel:
+      deadLetterUri: log:dlq
+      level: TRACE
+      useOriginalMessage: true
+- route:
+    from:
+      uri: kamelet:timer-source
+      steps:
+        - setBody:
+            expression:
+              groovy:
+                expression: 1000 / 0
+        - log:
+            message: $[body}
+      parameters:
+        period: 1000
+        message: '1'
\ No newline at end of file
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index 10f2615..7249532 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -32,7 +32,7 @@ import EipIcon from 
"@patternfly/react-icons/dist/js/icons/topology-icon";
 import ComponentsIcon from "@patternfly/react-icons/dist/js/icons/module-icon";
 import {KaravanIcon} from "./designer/utils/KaravanIcons";
 import './designer/karavan.css';
-import {DesignerPage} from "./designer/DesignerPage";
+import {DesignerPage} from "./DesignerPage";
 
 class ToastMessage {
     id: string = ''
@@ -161,7 +161,7 @@ class App extends React.Component<Props, State> {
         </Flex>)
     }
 
-    getDesigner() {
+    getPage() {
         const {key, name, yaml, pageId} = this.state;
         const dark = document.body.className.includes('vscode-dark');
         switch (pageId) {
@@ -209,7 +209,7 @@ class App extends React.Component<Props, State> {
                         </FlexItem>
                         <FlexItem flex={{default: "flex_2"}} style={{height: 
"100%"}}>
                             {loaded !== true && this.getSpinner()}
-                            {loaded === true && this.getDesigner()}
+                            {loaded === true && this.getPage()}
                         </FlexItem>
                     </Flex>
                 </>
diff --git a/karavan-designer/src/designer/DesignerPage.tsx 
b/karavan-designer/src/DesignerPage.tsx
similarity index 68%
rename from karavan-designer/src/designer/DesignerPage.tsx
rename to karavan-designer/src/DesignerPage.tsx
index baee350..ced4614 100644
--- a/karavan-designer/src/designer/DesignerPage.tsx
+++ b/karavan-designer/src/DesignerPage.tsx
@@ -19,12 +19,13 @@ import {
     Toolbar,
     ToolbarContent,
     ToolbarItem,
-    PageSection, TextContent, Text, PageSectionVariants, Flex, FlexItem, 
Badge, Button, Tooltip
+    PageSection, TextContent, Text, PageSectionVariants, Flex, FlexItem, 
Badge, Button, Tooltip, ToggleGroup, ToggleGroupItem
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
+import './designer/karavan.css';
 import DownloadIcon from 
"@patternfly/react-icons/dist/esm/icons/download-icon";
 import DownloadImageIcon from 
"@patternfly/react-icons/dist/esm/icons/image-icon";
-import {KaravanDesigner} from "./KaravanDesigner";
+import {KaravanDesigner} from "./designer/KaravanDesigner";
+import Editor from "@monaco-editor/react";
 
 interface Props {
     name: string,
@@ -35,12 +36,13 @@ interface Props {
 
 interface State {
     karavanDesignerRef: any,
+    mode: "design" | "code",
 }
 
 export class DesignerPage extends React.Component<Props, State> {
 
     public state: State = {
-
+        mode: 'design',
         karavanDesignerRef: React.createRef(),
     };
 
@@ -67,8 +69,39 @@ export class DesignerPage extends React.Component<Props, 
State> {
         }
     }
 
+    getDesigner = () => {
+        const {name, yaml} = this.props;
+        return (
+            <KaravanDesigner
+                dark={this.props.dark}
+                ref={this.state.karavanDesignerRef}
+                filename={name}
+                yaml={yaml}
+                onSave={(filename, yaml, propertyOnly) => this.save(filename, 
yaml, propertyOnly)}/>
+        )
+    }
+
+    getEditor = () => {
+        const {name, yaml} = this.props;
+        return (
+            <Editor
+                height="100vh"
+                defaultLanguage="yaml"
+                theme={'light'}
+                value={yaml}
+                className={'code-editor'}
+                onChange={(value, ev) => {
+                    if (value) {
+                        this.save(name, value, false)
+                    }
+                }}
+            />
+        )
+    }
+
     render() {
         const {name, yaml} = this.props;
+        const {mode} = this.state;
         return (
             <PageSection className="kamelet-section designer-page" 
padding={{default: 'noPadding'}}>
                 <PageSection className="tools-section" padding={{default: 
'noPadding'}}
@@ -82,6 +115,14 @@ export class DesignerPage extends React.Component<Props, 
State> {
                         <FlexItem>
                             <Toolbar id="toolbar-group-types">
                                 <ToolbarContent>
+                                    <ToolbarItem>
+                                        <ToggleGroup>
+                                            <ToggleGroupItem text="Design" 
buttonId="design" isSelected={mode === "design"}
+                                                             onChange={s => 
this.setState({mode: "design"})} />
+                                            <ToggleGroupItem text="Code" 
buttonId="code" isSelected={mode === "code"}
+                                                             onChange={s => 
this.setState({mode: "code"})} />
+                                        </ToggleGroup>
+                                    </ToolbarItem>
                                     <ToolbarItem>
                                         <Tooltip content="Download YAML" 
position={"bottom"}>
                                             <Button variant="primary" 
icon={<DownloadIcon/>} onClick={e => this.download()}>
@@ -101,12 +142,8 @@ export class DesignerPage extends React.Component<Props, 
State> {
                         </FlexItem>
                     </Flex>
                 </PageSection>
-                <KaravanDesigner
-                    dark={this.props.dark}
-                    ref={this.state.karavanDesignerRef}
-                    filename={name}
-                    yaml={yaml}
-                    onSave={(filename, yaml, propertyOnly) => 
this.save(filename, yaml, propertyOnly)}/>
+                {mode === 'design' && this.getDesigner()}
+                {mode === 'code'  && this.getEditor()}
             </PageSection>
         );
     }
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx 
b/karavan-designer/src/designer/KaravanDesigner.tsx
index 6333b37..dd271bc 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -27,7 +27,7 @@ import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelUi} from "./utils/CamelUi";
 import {BeansDesigner} from "./beans/BeansDesigner";
 import {RestDesigner} from "./rest/RestDesigner";
-import {ErrorDesigner} from "./error/ErrorDesigner";
+import {ErrorHandlerDesigner} from "./error/ErrorHandlerDesigner";
 import {ExceptionDesigner} from "./exception/ExceptionDesigner";
 import {getDesignerIcon} from "./utils/KaravanIcons";
 
@@ -112,7 +112,7 @@ export class KaravanDesigner extends React.Component<Props, 
State> {
                     <Tab eventKey='routes' title={this.getTab("Routes", 
"Integration flows", "routes")}></Tab>
                     <Tab eventKey='rest' title={this.getTab("REST", "REST 
services", "rest")}></Tab>
                     <Tab eventKey='beans' title={this.getTab("Beans", "Beans 
Configuration", "beans")}></Tab>
-                    <Tab eventKey='error' title={this.getTab("Error", "Error 
Handler", "error")}></Tab>
+                    <Tab eventKey='error' title={this.getTab("Error Handler", 
"Global Error Handler", "error")}></Tab>
                 </Tabs>
                 {tab === 'routes' && <RouteDesigner 
integration={this.state.integration}
                                                     onSave={(integration, 
propertyOnly) => this.save(integration, propertyOnly)}
@@ -124,9 +124,9 @@ export class KaravanDesigner extends React.Component<Props, 
State> {
                 {tab === 'beans' && <BeansDesigner 
integration={this.state.integration}
                                                    onSave={(integration, 
propertyOnly) => this.save(integration, propertyOnly)}
                                                    dark={this.props.dark}/>}
-                {tab === 'error' && <ErrorDesigner 
integration={this.state.integration}
-                                                   onSave={(integration, 
propertyOnly) => this.save(integration, propertyOnly)}
-                                                   dark={this.props.dark}/>}
+                {tab === 'error' && <ErrorHandlerDesigner 
integration={this.state.integration}
+                                                          
onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
+                                                          
dark={this.props.dark}/>}
             </PageSection>
         )
     }
diff --git a/karavan-designer/src/designer/error/ErrorDesigner.tsx 
b/karavan-designer/src/designer/error/ErrorDesigner.tsx
deleted file mode 100644
index ef11836..0000000
--- a/karavan-designer/src/designer/error/ErrorDesigner.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 from 'react';
-import {
-    EmptyState, EmptyStateBody, EmptyStateIcon,
-    PageSection, Title
-} from '@patternfly/react-core';
-import '../karavan.css';
-import {Integration, CamelElement} from 
"karavan-core/lib/model/IntegrationDefinition";
-import CubesIcon from '@patternfly/react-icons/dist/esm/icons/cubes-icon';
-
-interface Props {
-    onSave?: (integration: Integration, propertyOnly: boolean) => void
-    integration: Integration
-    dark: boolean
-}
-
-interface State {
-    integration: Integration
-    selectedStep?: CamelElement
-    key: string
-    propertyOnly: boolean
-}
-
-export class ErrorDesigner extends React.Component<Props, State> {
-
-    public state: State = {
-        integration: this.props.integration,
-        key: "",
-        propertyOnly: false
-    };
-
-    componentDidUpdate = (prevProps: Readonly<Props>, prevState: 
Readonly<State>, snapshot?: any) => {
-        if (prevState.key !== this.state.key) {
-            this.props.onSave?.call(this, this.state.integration, 
this.state.propertyOnly);
-        }
-    }
-
-    onIntegrationUpdate = (i: Integration) => {
-        this.setState({integration: i, key: Math.random().toString()});
-    }
-
-    render() {
-        return (
-            <PageSection className="error-page" isFilled padding={{default: 
'noPadding'}}>
-                <div className="error-page-columns">
-                    <EmptyState>
-                        <EmptyStateIcon icon={CubesIcon} />
-                        <Title headingLevel="h4" size="lg">
-                            Error handler
-                        </Title>
-                        <EmptyStateBody>
-                            Error handler not implemented yet
-                        </EmptyStateBody>
-                    </EmptyState>
-                </div>
-            </PageSection>
-        );
-    }
-}
diff --git a/karavan-designer/src/designer/error/ErrorHandlerCard.tsx 
b/karavan-designer/src/designer/error/ErrorHandlerCard.tsx
new file mode 100644
index 0000000..f0d534c
--- /dev/null
+++ b/karavan-designer/src/designer/error/ErrorHandlerCard.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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 from 'react';
+import {
+    Button
+} from '@patternfly/react-core';
+import '../karavan.css';
+import {ErrorHandlerDefinition} from "karavan-core/lib/model/CamelDefinition";
+import DeleteIcon from 
"@patternfly/react-icons/dist/js/icons/times-circle-icon";
+
+interface Props {
+    errorHandler: ErrorHandlerDefinition
+    deleteElement: (element: ErrorHandlerDefinition) => void
+}
+
+export class ErrorHandlerCard extends React.Component<Props, any> {
+
+    delete = (evt: React.MouseEvent) => {
+        evt.stopPropagation();
+        this.props.deleteElement.call(this, this.props.errorHandler);
+    }
+
+    render() {
+        return (
+            <div className="rest-card rest-card-selected">
+                <div className="header">
+                    <div className="title">Error Handler</div>
+                    <div className="description">Global error handler for the 
RouteBuilder</div>
+                    <Button variant="link" className="delete-button" 
onClick={e => this.delete(e)}><DeleteIcon/></Button>
+                </div>
+            </div>
+        );
+    }
+}
diff --git a/karavan-designer/src/designer/error/ErrorHandlerDesigner.tsx 
b/karavan-designer/src/designer/error/ErrorHandlerDesigner.tsx
new file mode 100644
index 0000000..ba35ccc
--- /dev/null
+++ b/karavan-designer/src/designer/error/ErrorHandlerDesigner.tsx
@@ -0,0 +1,164 @@
+/*
+ * 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 from 'react';
+import {
+    Button, Drawer, DrawerContent, DrawerContentBody, DrawerPanelContent, 
Modal, PageSection
+} from '@patternfly/react-core';
+import '../karavan.css';
+import {ErrorHandlerDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {CamelElement, Integration} from 
"karavan-core/lib/model/IntegrationDefinition";
+import {CamelUi} from "../utils/CamelUi";
+import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
+import {CamelDefinitionApiExt} from 
"karavan-core/lib/api/CamelDefinitionApiExt";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {ErrorHandlerCard} from "./ErrorHandlerCard";
+import {DslProperties} from "../route/DslProperties";
+
+interface Props {
+    onSave?: (integration: Integration, propertyOnly: boolean) => void
+    integration: Integration
+    dark: boolean
+}
+
+interface State {
+    integration: Integration
+    showDeleteConfirmation: boolean
+    errorHandler?: ErrorHandlerDefinition
+    key: string
+    propertyOnly: boolean
+}
+
+export class ErrorHandlerDesigner extends React.Component<Props, State> {
+
+    public state: State = {
+        integration: this.props.integration,
+        showDeleteConfirmation: false,
+        key: "",
+        propertyOnly: false
+    }
+
+    componentDidMount() {
+        this.setState({key: Math.random().toString()})
+    }
+
+    componentDidUpdate = (prevProps: Readonly<Props>, prevState: 
Readonly<State>, snapshot?: any) => {
+        if (prevState.key !== this.state.key) {
+            this.props.onSave?.call(this, this.state.integration, 
this.state.propertyOnly);
+        }
+    }
+
+    showDeleteConfirmation = (errorHandler: ErrorHandlerDefinition) => {
+        this.setState({errorHandler: errorHandler, showDeleteConfirmation: 
true});
+    }
+
+    onIntegrationUpdate = (i: Integration) => {
+        this.setState({integration: i, propertyOnly: false, 
showDeleteConfirmation: false, key: Math.random().toString()});
+    }
+
+    deleteErrorHandler = () => {
+        const i = 
CamelDefinitionApiExt.deleteErrorHandlerFromIntegration(this.state.integration);
+        this.setState({
+            integration: i,
+            showDeleteConfirmation: false,
+            key: Math.random().toString(),
+            errorHandler: undefined,
+            propertyOnly: false
+        });
+    }
+
+    changeErrorHandler = (errorHandler: ErrorHandlerDefinition) => {
+        const clone = CamelUtil.cloneIntegration(this.state.integration);
+        const i = CamelDefinitionApiExt.addErrorHandlerToIntegration(clone, 
errorHandler);
+        this.setState({integration: i, propertyOnly: false, key: 
Math.random().toString(), errorHandler: errorHandler});
+    }
+
+    getDeleteConfirmation() {
+        return (<Modal
+            className="modal-delete"
+            title="Confirmation"
+            isOpen={this.state.showDeleteConfirmation}
+            onClose={() => this.setState({showDeleteConfirmation: false})}
+            actions={[
+                <Button key="confirm" variant="primary" onClick={e => 
this.deleteErrorHandler()}>Delete</Button>,
+                <Button key="cancel" variant="link"
+                        onClick={e => this.setState({showDeleteConfirmation: 
false})}>Cancel</Button>
+            ]}
+            onEscapePress={e => this.setState({showDeleteConfirmation: 
false})}>
+            <div>
+                Delete Global Error Handler from integration?
+            </div>
+        </Modal>)
+    }
+
+    createErrorHandlerErrorHandle = () => {
+        this.changeErrorHandler(new ErrorHandlerDefinition());
+    }
+
+    onPropertyUpdate = (element: CamelElement) => {
+        const clone = CamelUtil.cloneIntegration(this.state.integration);
+        const i = CamelDefinitionApiExt.addErrorHandlerToIntegration(clone, 
element);
+        this.setState({integration: i, propertyOnly: true, key: 
Math.random().toString()});
+    }
+
+    getPropertiesPanel(errorHandler?: ErrorHandlerDefinition) {
+        return (
+            <DrawerPanelContent isResizable hasNoBorder defaultSize={'400px'} 
maxSize={'800px'} minSize={'300px'}>
+                <DslProperties
+                    integration={this.props.integration}
+                    step={errorHandler}
+                    onIntegrationUpdate={this.onIntegrationUpdate}
+                    onPropertyUpdate={this.onPropertyUpdate}
+                    clipboardStep={undefined}
+                    isRouteDesigner={false}
+                    onClone={element => {}}
+                    dark={this.props.dark}/>
+            </DrawerPanelContent>
+        )
+    }
+
+    render() {
+        const errorHandler = CamelUi.getErrorHandler(this.state.integration);
+        return (
+            <PageSection className="rest-page" isFilled padding={{default: 
'noPadding'}}>
+                <div className="rest-page-columns">
+                    <Drawer isExpanded isInline>
+                        <DrawerContent 
panelContent={this.getPropertiesPanel(errorHandler)}>
+                            <DrawerContentBody>
+                                <div className="graph" data-click="REST">
+                                    <div className="flows">
+                                        {errorHandler && <ErrorHandlerCard 
key={errorHandler.uuid + this.state.key}
+                                                                           
errorHandler={errorHandler}
+                                                                           
deleteElement={this.showDeleteConfirmation}/>}
+                                        <div className="add-rest">
+                                            {errorHandler === undefined && 
<Button
+                                                variant="primary"
+                                                data-click="ADD_REST"
+                                                icon={<PlusIcon/>}
+                                                onClick={e => 
this.createErrorHandlerErrorHandle()}>Create new error handler
+                                            </Button>}
+                                        </div>
+                                    </div>
+                                </div>
+                            </DrawerContentBody>
+                        </DrawerContent>
+                    </Drawer>
+                </div>
+                {this.getDeleteConfirmation()}
+            </PageSection>
+        )
+    }
+}
diff --git a/karavan-designer/src/designer/karavan.css 
b/karavan-designer/src/designer/karavan.css
index 17b9f6b..2ae90aa 100644
--- a/karavan-designer/src/designer/karavan.css
+++ b/karavan-designer/src/designer/karavan.css
@@ -468,7 +468,7 @@
     width: 350px;
 }
 
-.karavan .properties .add-button {
+.karavan .properties .change-button {
     font-size: 15px;
     height: 15px;
     line-height: 1;
@@ -478,10 +478,18 @@
     background: transparent;
 }
 
-.karavan .properties .add-button svg {
+.karavan .properties .change-button svg {
     margin-right: 6px;
 }
 
+.karavan .properties .add-button {
+    color: var(--pf-global--active-color--100);
+}
+
+.karavan .properties .delete-button {
+    color:  #909090;
+}
+
 .karavan .properties .pf-c-expandable-section__toggle {
     margin: 0;
     padding: 0;
diff --git a/karavan-designer/src/designer/route/property/DslPropertyField.tsx 
b/karavan-designer/src/designer/route/property/DslPropertyField.tsx
index 9f4504e..067545f 100644
--- a/karavan-designer/src/designer/route/property/DslPropertyField.tsx
+++ b/karavan-designer/src/designer/route/property/DslPropertyField.tsx
@@ -148,13 +148,14 @@ export class DslPropertyField extends 
React.Component<Props, State> {
     getLabel = (property: PropertyMeta, value: any) => {
         if (!this.isMultiValueField(property) && property.isObject && 
!property.isArray && !["ExpressionDefinition"].includes(property.type)) {
             const tooltip = value ? "Delete " + property.name : "Add " + 
property.name;
+            const className = value ? "change-button delete-button" : 
"change-button add-button";
             const x = value ? undefined : 
CamelDefinitionApi.createStep(property.type, {});
             const icon = value ? (<DeleteIcon noVerticalAlign/>) : (<AddIcon 
noVerticalAlign/>);
             return (
                 <div style={{display: "flex"}}>
                     <Text>{property.displayName} </Text>
-                    <Tooltip position={"bottom"} 
content={<div>{tooltip}</div>}>
-                        <button className="add-button" onClick={e => 
this.props.onChange?.call(this, property.name, x)} aria-label="Add element">
+                    <Tooltip position={"top"} content={<div>{tooltip}</div>}>
+                        <button className={className} onClick={e => 
this.props.onChange?.call(this, property.name, x)} aria-label="Add element">
                             {icon}
                         </button>
                     </Tooltip>
diff --git a/karavan-designer/src/designer/utils/CamelUi.tsx 
b/karavan-designer/src/designer/utils/CamelUi.tsx
index 5c98f13..766a166 100644
--- a/karavan-designer/src/designer/utils/CamelUi.tsx
+++ b/karavan-designer/src/designer/utils/CamelUi.tsx
@@ -21,7 +21,7 @@ import {ComponentApi} from 
"karavan-core/lib/api/ComponentApi";
 import {CamelMetadataApi} from "karavan-core/lib/model/CamelMetadata";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelDefinitionApiExt} from 
"karavan-core/lib/api/CamelDefinitionApiExt";
-import {NamedBeanDefinition, RouteDefinition, SagaDefinition, ToDefinition} 
from "karavan-core/lib/model/CamelDefinition";
+import {ErrorHandlerDefinition, NamedBeanDefinition, RouteDefinition, 
SagaDefinition, ToDefinition} from "karavan-core/lib/model/CamelDefinition";
 import {CamelElement, Integration} from 
"karavan-core/lib/model/IntegrationDefinition";
 import {AggregateIcon, ChoiceIcon, FilterIcon, SagaIcon, SortIcon, SplitIcon, 
TransformIcon} from "./KaravanIcons";
 import React from "react";
@@ -35,8 +35,7 @@ const StepElements: string[] = [
     "ConvertBodyDefinition",
     "DynamicRouterDefinition",
     "EnrichDefinition",
-    // "ErrorHandlerBuilderDeserializer",
-    // "ErrorHandlerDefinition",
+    "ErrorHandlerDefinition",
     "FilterDefinition",
     "LogDefinition",
     "LoopDefinition",
@@ -426,6 +425,8 @@ export class CamelUi {
                 return "data:image/svg+xml,%3Csvg 
xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' 
version='1.1' id='Layer_1' x='0px' y='0px' viewBox='0 0 305.001 305.001' 
style='enable-background:new 0 0 305.001 305.001;' xml:space='preserve'%3E%3Cg 
id='XMLID_7_'%3E%3Cpath id='XMLID_8_' 
d='M150.99,56.513c-14.093,9.912-30.066,21.147-38.624,39.734c-14.865,32.426,30.418,67.798,32.353,69.288
 c0.45,0.347,0.988,0.519,1.525,0.519c0.57,0,1.141-0.195,1.605-0.583c0.89 [...]
             case "ProcessDefinition":
                 return "data:image/svg+xml,%3Csvg 
xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' 
version='1.1' id='Capa_1' x='0px' y='0px' width='235.506px' height='235.506px' 
viewBox='0 0 235.506 235.506' style='enable-background:new 0 0 235.506 
235.506;' xml:space='preserve'%3E%3Cg%3E%3Cpath 
d='M234.099,29.368c-3.025-4.861-10.303-7.123-22.915-7.123c-13.492,0-28.304,2.661-28.625,2.733
 c-20.453,2.098-27.254,26.675-32.736,46.436c-1.924,6.969-3.755,13.549-5.8 [...]
+            case "ErrorHandlerDefinition":
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+d2FybmluZzwvdGl0bGU+PHBhdGggZD0iTTE2LDJBMTQsMTQsMCwxLDAsMzAsMTYsMTQsMTQsMCwwLDAsMTYsMlptMCwyNkExMiwxMiwwLDEsMSwyOCwxNiwxMiwxMiwwLDAsMSwxNiwyOFoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgMCkiLz48cmVjdCB4PSIxNSIgeT0iOCIgd2lkdGg9IjIiIGhlaWdodD0iMTEiLz48cG
 [...]
             default:
                 return camelIcon;
         }
@@ -486,6 +487,7 @@ export class CamelUi {
         const result = new Map<string, number>();
         result.set('routes', i.spec.flows?.filter((e: any) => e.dslName === 
'RouteDefinition').length || 0);
         result.set('rest', i.spec.flows?.filter((e: any) => e.dslName === 
'RestDefinition').length || 0);
+        result.set('error', i.spec.flows?.filter((e: any) => e.dslName === 
'ErrorHandlerDefinition').length || 0);
         const beans = i.spec.flows?.filter((e: any) => e.dslName === 'Beans');
         if (beans && beans.length > 0 && beans[0].beans && 
beans[0].beans.length > 0){
             result.set('beans', Array.from(beans[0].beans).length);
@@ -508,4 +510,9 @@ export class CamelUi {
         }
         return result;
     }
+
+    static getErrorHandler = (integration: Integration): 
ErrorHandlerDefinition | undefined => {
+        const errorHandler = integration.spec.flows?.filter((e: any) => 
e.dslName === 'ErrorHandlerDefinition').at(0);
+        return errorHandler;
+    }
 }
\ No newline at end of file


Reply via email to