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
