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 062e344ac57ab322d7f21c6c3d8aa5fc3250bb2d Author: Marat Gubaidullin <[email protected]> AuthorDate: Fri Apr 19 15:40:31 2024 -0400 Kamelet for project --- .../src/project/files/CreateIntegrationModal.tsx | 38 +++++++----- .../src/main/webui/src/project/files/FilesTab.tsx | 13 +--- .../main/webui/src/project/files/FilesToolbar.tsx | 9 ++- .../test/avro-serialize-action.kamelet.yaml | 70 ++++++++++++++++++++++ karavan-core/test/integrationToYaml.spec.ts | 60 +++++++++++++++++++ 5 files changed, 163 insertions(+), 27 deletions(-) diff --git a/karavan-app/src/main/webui/src/project/files/CreateIntegrationModal.tsx b/karavan-app/src/main/webui/src/project/files/CreateIntegrationModal.tsx index 34647d0c..c6a91b0d 100644 --- a/karavan-app/src/main/webui/src/project/files/CreateIntegrationModal.tsx +++ b/karavan-app/src/main/webui/src/project/files/CreateIntegrationModal.tsx @@ -27,7 +27,7 @@ import { import '../../designer/karavan.css'; import {KameletTypes} from "karavan-core/lib/model/IntegrationDefinition"; import {useFileStore, useProjectStore} from "../../api/ProjectStore"; -import {getProjectFileTypeName, ProjectFile, ProjectFileTypes} from "../../api/ProjectModels"; +import {getProjectFileTypeName, ProjectFile} from "../../api/ProjectModels"; import {ProjectService} from "../../api/ProjectService"; import {shallow} from "zustand/shallow"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; @@ -62,6 +62,8 @@ export function CreateIntegrationModal() { reset(new ProjectFile('', project.projectId, '', 0)); setBackendError(undefined); setReset(true); + setSelectedKamelet(undefined); + setKameletType('source') }, [reset, operation]); React.useEffect(() => { @@ -85,8 +87,8 @@ export function CreateIntegrationModal() { }) } - function onSuccess (file: ProjectFile) { - EventBus.sendAlert( "Success", "File successfully created", "success"); + function onSuccess(file: ProjectFile) { + EventBus.sendAlert("Success", "File successfully created", "success"); ProjectService.refreshProjectData(project.projectId); if (file.code) { setFile('select', file, designerTab); @@ -103,12 +105,14 @@ export function CreateIntegrationModal() { const isKamelet = designerTab === 'kamelet'; - const listOfValues: Value[] = KameletApi.getKamelets() - .filter(k => k.metadata.labels["camel.apache.org/kamelet.type"] === kameletType) - .map(k => { - const v: Value = {value: k.metadata.name, children: k.spec.definition.title} - return v; - }) + function listOfValues(type: KameletTypes): Value[] { + return KameletApi.getKamelets() + .filter(k => k.metadata.labels["camel.apache.org/kamelet.type"] === type) + .map(k => { + const v: Value = {value: k.metadata.name, children: k.spec.definition.title} + return v; + }) + } function getFileExtension() { return designerTab === 'kamelet' ? '.kamelet.yaml' : '.camel.yaml'; @@ -152,19 +156,21 @@ export function CreateIntegrationModal() { })} </ToggleGroup> </FormGroup>} - {getTextFieldSuffix('name', 'Name', getFileSuffix(), true, { + {getTextFieldSuffix('name', 'Name', getFileSuffix(), true, { regex: v => isValidFileName(v) || 'Only characters, numbers and dashes allowed', length: v => v.length > 3 || 'File name should be longer that 3 characters', name: v => !['templates', 'kamelets', 'karavan'].includes(v) || "'templates', 'kamelets', 'karavan' can't be used as project", })} - {isKamelet && <FormGroup label="Copy from" fieldId="kamelet"> - <TypeaheadSelect listOfValues={listOfValues} onSelect={value => { - setSelectedKamelet(value) - }}/> - </FormGroup>} + {isKamelet && + <FormGroup label="Copy from" fieldId="kamelet"> + <TypeaheadSelect key={kameletType} listOfValues={listOfValues(kameletType)} onSelect={value => { + setSelectedKamelet(value) + }}/> + </FormGroup> + } {backendError && <FormAlert> - <Alert variant="danger" title={backendError} aria-live="polite" isInline /> + <Alert variant="danger" title={backendError} aria-live="polite" isInline/> </FormAlert> } </Form> diff --git a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx index 681b67bc..f5868cd8 100644 --- a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx +++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx @@ -39,7 +39,6 @@ import {useFilesStore, useFileStore, useProjectStore} from "../../api/ProjectSto import { getProjectFileTypeTitle, ProjectFile, - ProjectFileTypes } from "../../api/ProjectModels"; import {FileToolbar} from "./FilesToolbar"; import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon"; @@ -48,6 +47,7 @@ import {CreateFileModal} from "./CreateFileModal"; import {DeleteFileModal} from "./DeleteFileModal"; import {UploadFileModal} from "./UploadFileModal"; import {shallow} from "zustand/shallow"; +import {CreateIntegrationModal} from "./CreateIntegrationModal"; export function FilesTab () { @@ -76,10 +76,6 @@ export function FilesTab () { } } - function isBuildIn(): boolean { - return ['kamelets', 'templates', 'services'].includes(project.projectId); - } - function canDeleteFiles(): boolean { return !['templates', 'services'].includes(project.projectId); } @@ -88,10 +84,6 @@ export function FilesTab () { return project.projectId === 'kamelets'; } - const types = isBuildIn() - ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES']) - : ProjectFileTypes.filter(p => !['PROPERTIES', 'KAMELET'].includes(p.name)).map(p => p.name); - return ( <PageSection className="project-tab-panel" padding={{default: "padding"}}> <Panel> @@ -163,7 +155,8 @@ export function FilesTab () { </Table> </div> <UploadFileModal/> - <CreateFileModal/> + {!isKameletsProject() && <CreateFileModal/>} + {isKameletsProject() && <CreateIntegrationModal/>} <DeleteFileModal /> </PageSection> ) diff --git a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx index bfcfa645..7f50fa10 100644 --- a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx +++ b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx @@ -129,6 +129,9 @@ export function FileToolbar () { return project ? files.filter(f => f.lastUpdate > project.lastCommitTimestamp).length > 0 : false; } + function isKameletsProject(): boolean { + return project.projectId === 'kamelets'; + } function getDate(lastUpdate: number): string { if (lastUpdate) { @@ -193,10 +196,14 @@ export function FileToolbar () { </Button> </Tooltip> </FlexItem> - {canAddFiles() && <FlexItem> + {canAddFiles() && !isKameletsProject() && <FlexItem> <Button className="dev-action-button" size="sm" variant={"primary"} icon={<PlusIcon/>} onClick={e => setFile("create")}>Create</Button> </FlexItem>} + {canAddFiles() && isKameletsProject() && <FlexItem> + <Button className="dev-action-button" size="sm" variant={"primary"} icon={<PlusIcon/>} + onClick={e => setFile("create", undefined, 'kamelet')}>Create</Button> + </FlexItem>} {canAddFiles() && <FlexItem> <Button className="dev-action-button" size="sm" variant="secondary" icon={<UploadIcon/>} onClick={e => setFile("upload")}>Upload</Button> diff --git a/karavan-core/test/avro-serialize-action.kamelet.yaml b/karavan-core/test/avro-serialize-action.kamelet.yaml new file mode 100644 index 00000000..71a80e1a --- /dev/null +++ b/karavan-core/test/avro-serialize-action.kamelet.yaml @@ -0,0 +1,70 @@ +# --------------------------------------------------------------------------- +# 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. +# --------------------------------------------------------------------------- + +apiVersion: camel.apache.org/v1 +kind: Kamelet +metadata: + name: avro-serialize-action + annotations: + camel.apache.org/kamelet.support.level: "Stable" + camel.apache.org/catalog.version: "4.6.0-SNAPSHOT" + camel.apache.org/kamelet.icon: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG [...] + camel.apache.org/provider: "Apache Software Foundation" + camel.apache.org/kamelet.group: "Actions" + camel.apache.org/kamelet.namespace: "Transformation" + labels: + camel.apache.org/kamelet.type: "action" +spec: + definition: + title: "Avro Serialize Action" + description: "Serialize payload to Avro" + type: object + properties: + schema: + title: Schema + description: The Avro schema to use during serialization (as single-line, using JSON format) + type: string + example: '{"type": "record", "namespace": "com.example", "name": "FullName", "fields": [{"name": "first", "type": "string"},{"name": "last", "type": "string"}]}' + validate: + title: Validate + description: Indicates if the content must be validated against the schema + type: boolean + default: true + dependencies: + - "camel:kamelet" + - "camel:core" + - "camel:jackson-avro" + template: + beans: + - name: schemaResolver + type: "#class:org.apache.camel.component.jackson.avro.transform.AvroSchemaResolver" + property: + - key: validate + value: '{{validate}}' + - key: schema + value: '{{schema:}}' + from: + uri: kamelet:source + steps: + - marshal: + avro: + library: Jackson + unmarshalType: com.fasterxml.jackson.databind.JsonNode + schemaResolver: "#bean:{{schemaResolver}}" + - setHeader: + name: "Content-Type" + constant: "application/avro" \ No newline at end of file diff --git a/karavan-core/test/integrationToYaml.spec.ts b/karavan-core/test/integrationToYaml.spec.ts new file mode 100644 index 00000000..3369a717 --- /dev/null +++ b/karavan-core/test/integrationToYaml.spec.ts @@ -0,0 +1,60 @@ +/* + * 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"; +import {FilterDefinition, ToDefinition} from "../src/core/model/CamelDefinition"; +import { RouteDefinition} from "../src/core/model/CamelDefinition"; + +describe('CRD YAML to Integration', () => { + + + + it('YAML <-> Object 1', () => { + const yaml = fs.readFileSync('test/integration1.yaml',{encoding:'utf8', flag:'r'}); + const i = CamelDefinitionYaml.yamlToIntegration("test1.yaml", yaml); + expect(i.metadata.name).to.equal('test1.yaml'); + expect(i.kind).to.equal('Integration'); + expect(i.spec.flows?.length).to.equal(1); + expect(i.type).to.equal('crd'); + if (i.spec.flows){ + const f:FilterDefinition = (i.spec.flows[0] as RouteDefinition).from.steps[1]; + const t:ToDefinition = <ToDefinition> (f.steps ? f.steps[0] : undefined); + expect(t.uri).to.equal("log"); + expect(t.parameters.level).to.equal("OFF"); + } + console.log(CamelDefinitionYaml.integrationToYaml(i)) + }); + + it('YAML <-> Object 2', () => { + const yaml = fs.readFileSync('test/integration2.yaml',{encoding:'utf8', flag:'r'}); + const i = CamelDefinitionYaml.yamlToIntegration("test1.yaml", yaml); + expect(i.metadata.name).to.equal('test1.yaml'); + expect(i.kind).to.equal('Integration'); + expect(i.spec.flows?.length).to.equal(1); + expect(i.type).to.equal('crd'); + + if (i.spec.flows){ + const f:FilterDefinition = (i.spec.flows[0] as RouteDefinition).from.steps[1]; + const t:ToDefinition = <ToDefinition> (f.steps ? f.steps[0] : undefined); + expect(t.uri).to.equal("log"); + expect(t.parameters.level).to.equal("OFF"); + } + }); + +}); \ No newline at end of file
