From 29a822cd3fa02608f28f5b008f248f0fc0fc291a Mon Sep 17 00:00:00 2001 From: Tomohisa Igarashi Date: Wed, 1 Nov 2023 20:55:17 -0400 Subject: [PATCH] feat: Support configuring expression in Canvas form Fixes: #270 --- .../PipeErrorHandlerEditor.test.tsx | 16 +- .../Visualization/Canvas/CanvasForm.tsx | 11 +- .../Canvas/ExpressionEditor.test.tsx | 74 ++++ .../Visualization/Canvas/ExpressionEditor.tsx | 126 +++++++ .../ExpressionEditor.test.tsx.snap | 340 ++++++++++++++++++ .../Canvas/expression.service.test.ts | 130 +++++++ .../Canvas/expression.service.ts | 146 ++++++++ packages/ui/src/models/camel-catalog-index.ts | 11 +- .../src/models/camel-dataformats-catalog.ts | 25 ++ .../ui/src/models/camel-languages-catalog.ts | 29 ++ packages/ui/src/models/catalog-kind.ts | 2 + packages/ui/src/models/index.ts | 2 + .../flows/camel-catalog.service.ts | 11 + .../support/camel-component-schema.service.ts | 6 +- .../ui/src/providers/catalog.provider.tsx | 12 +- packages/ui/src/stubs/PipeErrorHandler.ts | 111 ------ 16 files changed, 926 insertions(+), 126 deletions(-) create mode 100644 packages/ui/src/components/Visualization/Canvas/ExpressionEditor.test.tsx create mode 100644 packages/ui/src/components/Visualization/Canvas/ExpressionEditor.tsx create mode 100644 packages/ui/src/components/Visualization/Canvas/__snapshots__/ExpressionEditor.test.tsx.snap create mode 100644 packages/ui/src/components/Visualization/Canvas/expression.service.test.ts create mode 100644 packages/ui/src/components/Visualization/Canvas/expression.service.ts create mode 100644 packages/ui/src/models/camel-dataformats-catalog.ts create mode 100644 packages/ui/src/models/camel-languages-catalog.ts delete mode 100644 packages/ui/src/stubs/PipeErrorHandler.ts diff --git a/packages/ui/src/components/MetadataEditor/PipeErrorHandlerEditor.test.tsx b/packages/ui/src/components/MetadataEditor/PipeErrorHandlerEditor.test.tsx index 6a91e6b69..d4b274c58 100644 --- a/packages/ui/src/components/MetadataEditor/PipeErrorHandlerEditor.test.tsx +++ b/packages/ui/src/components/MetadataEditor/PipeErrorHandlerEditor.test.tsx @@ -1,7 +1,7 @@ import { PipeErrorHandlerEditor } from './PipeErrorHandlerEditor'; import { within } from '@testing-library/dom'; import { fireEvent, render, screen } from '@testing-library/react'; -import { pipeErrorHandlerJson } from '../../stubs/PipeErrorHandler'; +import * as pipeErrorHandlerSchema from '@kaoto-next/camel-catalog/PipeErrorHandler.json'; describe('PipeErrorHandlerEditor', () => { it('should render', () => { @@ -13,8 +13,8 @@ describe('PipeErrorHandlerEditor', () => { }, }, }; - render( {}} schema={pipeErrorHandlerJson} />); - const element = screen.getByTestId('metadata-editor-form-Log ErrorHandler'); + render( {}} schema={pipeErrorHandlerSchema} />); + const element = screen.getByTestId('metadata-editor-form-Log Pipe ErrorHandler'); expect(element).toBeTruthy(); const inputs = screen.getAllByTestId('num-field'); expect(inputs.length).toBe(2); @@ -23,8 +23,8 @@ describe('PipeErrorHandlerEditor', () => { }); it('should not render a form if model is empty', () => { - render( {}} schema={pipeErrorHandlerJson} />); - const element = screen.queryByTestId('metadata-editor-form-Log ErrorHandler'); + render( {}} schema={pipeErrorHandlerSchema} />); + const element = screen.queryByTestId('metadata-editor-form-Log Pipe ErrorHandler'); expect(element).toBeFalsy(); }); @@ -36,19 +36,19 @@ describe('PipeErrorHandlerEditor', () => { onChangeModel={(m) => { model = m; }} - schema={pipeErrorHandlerJson} + schema={pipeErrorHandlerSchema} />, ); const button = screen.getByRole('button'); fireEvent(button!, new MouseEvent('click', { bubbles: true })); const options = screen.getAllByTestId(/pipe-error-handler-select-option.*/); options.forEach((option) => { - if (option.innerHTML.includes('Log ErrorHandler')) { + if (option.innerHTML.includes('Log Pipe ErrorHandler')) { const button = within(option).getByRole('option'); fireEvent(button, new MouseEvent('click', { bubbles: true })); } }); - const element = screen.getByTestId('metadata-editor-form-Log ErrorHandler'); + const element = screen.getByTestId('metadata-editor-form-Log Pipe ErrorHandler'); expect(element).toBeTruthy(); expect(model.log).toBeTruthy(); }); diff --git a/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx b/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx index 9034b616b..bd02e4e0a 100644 --- a/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx +++ b/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx @@ -1,11 +1,12 @@ import { AutoFields, AutoForm, ErrorsField } from '@kaoto-next/uniforms-patternfly'; import { Title } from '@patternfly/react-core'; -import { FunctionComponent, useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { FunctionComponent, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { ErrorBoundary } from '../../ErrorBoundary'; import { SchemaService } from '../../Form'; import { CustomAutoField } from '../../Form/CustomAutoField'; import { CanvasNode } from './canvas.models'; import { EntitiesContext } from '../../../providers/entities.provider'; +import { ExpressionEditor } from './ExpressionEditor'; interface CanvasFormProps { selectedNode: CanvasNode; @@ -38,12 +39,16 @@ export const CanvasForm: FunctionComponent = (props) => { [entitiesContext, props.selectedNode.data?.vizNode], ); + const isExpressionAwareStep = useMemo(() => { + return schema?.schema?.properties?.expression !== undefined; + }, [schema]); + return schema?.schema === undefined ? null : ( This node cannot be configured yet

}> {componentName} - + {isExpressionAwareStep && } - +
diff --git a/packages/ui/src/components/Visualization/Canvas/ExpressionEditor.test.tsx b/packages/ui/src/components/Visualization/Canvas/ExpressionEditor.test.tsx new file mode 100644 index 000000000..1d5631a5c --- /dev/null +++ b/packages/ui/src/components/Visualization/Canvas/ExpressionEditor.test.tsx @@ -0,0 +1,74 @@ +import * as yamlDslSchema from '@kaoto-next/camel-catalog/camelYamlDsl.json'; +import * as languageCatalog from '@kaoto-next/camel-catalog/camel-catalog-aggregate-languages.json'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { ExpressionEditor } from './ExpressionEditor'; +import { CamelCatalogService } from '../../../models/visualization/flows'; +import { CatalogKind, ICamelLanguageDefinition } from '../../../models'; +import { CanvasNode } from './canvas.models'; +import { JSONSchemaType } from 'ajv'; +import { IVisualizationNode, VisualComponentSchema } from '../../../models/visualization/base-visual-entity'; +import { useSchemasStore } from '../../../store'; +import { act } from 'react-dom/test-utils'; + +describe('ExpressionEditor', () => { + let mockNode: CanvasNode; + beforeAll(() => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + delete (yamlDslSchema as any).default; + delete (languageCatalog as any).default; + CamelCatalogService.setCatalogKey( + CatalogKind.Language, + languageCatalog as unknown as Record, + ); + + act(() => { + useSchemasStore.setState({ + schemas: { + camelYamlDsl: { + name: 'camelYamlDsl', + tags: ['camel'], + version: '1.0.0', + uri: '', + schema: yamlDslSchema as unknown as Record, + }, + }, + }); + }); + + const visualComponentSchema: VisualComponentSchema = { + title: 'My Node', + schema: { + type: 'object', + properties: { + name: { + type: 'string', + }, + }, + } as unknown as JSONSchemaType, + definition: { + name: 'my node', + }, + }; + + mockNode = { + id: '1', + type: 'node', + data: { + vizNode: { + getComponentSchema: () => visualComponentSchema, + updateModel: (_value: unknown) => {}, + } as IVisualizationNode, + }, + }; + }); + + it('should render', () => { + render(); + const buttons = screen.getAllByRole('button'); + fireEvent.click(buttons[1]); + const jsonpath = screen.getByTestId('expression-dropdownitem-jsonpath'); + fireEvent.click(jsonpath.getElementsByTagName('button')[0]); + const form = screen.getByTestId('metadata-editor-form-expression'); + expect(form.innerHTML).toContain('Suppress Exceptions'); + }); +}); diff --git a/packages/ui/src/components/Visualization/Canvas/ExpressionEditor.tsx b/packages/ui/src/components/Visualization/Canvas/ExpressionEditor.tsx new file mode 100644 index 000000000..8dad105fd --- /dev/null +++ b/packages/ui/src/components/Visualization/Canvas/ExpressionEditor.tsx @@ -0,0 +1,126 @@ +import { FunctionComponent, Ref, useCallback, useContext, useMemo, useState } from 'react'; +import { + Card, + CardBody, + CardExpandableContent, + CardHeader, + CardTitle, + Dropdown, + DropdownItem, + DropdownList, + MenuToggle, + MenuToggleElement, +} from '@patternfly/react-core'; +import { MetadataEditor } from '../../MetadataEditor'; +import { EntitiesContext } from '../../../providers'; +import { CanvasNode } from './canvas.models'; +import { ExpressionService } from './expression.service'; +import { useSchemasStore } from '../../../store'; + +interface ExpressionEditorProps { + selectedNode: CanvasNode; +} + +export const ExpressionEditor: FunctionComponent = (props) => { + const entitiesContext = useContext(EntitiesContext); + const [isOpen, setIsOpen] = useState(false); + const [isExpanded, setIsExpanded] = useState(true); + const camelYamlDslSchema = useSchemasStore((state) => state.schemas['camelYamlDsl'].schema) as Record< + string, + unknown + >; + const languageCatalogMap = useMemo(() => { + return ExpressionService.getLanguageMap(camelYamlDslSchema); + }, [camelYamlDslSchema]); + + const visualComponentSchema = props.selectedNode.data?.vizNode?.getComponentSchema(); + if (visualComponentSchema) { + if (!visualComponentSchema.definition) { + visualComponentSchema.definition = {}; + } + } + const model = visualComponentSchema?.definition; + const { language, model: expressionModel } = ExpressionService.parseExpressionModel(languageCatalogMap, model); + const languageSchema = useMemo(() => { + return ExpressionService.getLanguageSchema(language!); + }, [language]); + + const onToggleClick = useCallback(() => { + setIsOpen(!isOpen); + }, [isOpen]); + + const handleOnChange = useCallback( + (selectedLanguage: string, newExpressionModel: Record) => { + if (!model) return; + ExpressionService.setExpressionModel(languageCatalogMap, model, selectedLanguage, newExpressionModel); + props.selectedNode.data?.vizNode?.updateModel(model); + entitiesContext?.updateCodeFromEntities(); + }, + [entitiesContext, languageCatalogMap, model, props.selectedNode.data?.vizNode], + ); + + const onSelect = useCallback( + (_event: React.MouseEvent | undefined, value: string | number | undefined) => { + setIsOpen(false); + if (value === language!.language.modelName) return; + handleOnChange(value as string, {}); + }, + [handleOnChange, language], + ); + + const toggle = useCallback( + (toggleRef: Ref) => ( + + {language!.language.title} + + ), + [language, isOpen, onToggleClick], + ); + + return ( + languageCatalogMap && + model && + language && ( + + setIsExpanded(!isExpanded)}> + Expression + + + + + + {Object.values(languageCatalogMap).map((language) => { + return ( + + {language.language.title} + + ); + })} + + + handleOnChange(language.language.modelName, model)} + /> + + + + ) + ); +}; diff --git a/packages/ui/src/components/Visualization/Canvas/__snapshots__/ExpressionEditor.test.tsx.snap b/packages/ui/src/components/Visualization/Canvas/__snapshots__/ExpressionEditor.test.tsx.snap new file mode 100644 index 000000000..10bdfd619 --- /dev/null +++ b/packages/ui/src/components/Visualization/Canvas/__snapshots__/ExpressionEditor.test.tsx.snap @@ -0,0 +1,340 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ExpressionEditor should render correctly 1`] = ` +
+
+
+
+ +
+
+
+
+ Expression +
+
+
+
+
+
+ +
+
+
+
+ + +
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+`; diff --git a/packages/ui/src/components/Visualization/Canvas/expression.service.test.ts b/packages/ui/src/components/Visualization/Canvas/expression.service.test.ts new file mode 100644 index 000000000..f2babda3e --- /dev/null +++ b/packages/ui/src/components/Visualization/Canvas/expression.service.test.ts @@ -0,0 +1,130 @@ +import { ExpressionService } from './expression.service'; +import * as yamlDslSchema from '@kaoto-next/camel-catalog/camelYamlDsl.json'; +import * as languageCatalog from '@kaoto-next/camel-catalog/camel-catalog-aggregate-languages.json'; +import { CatalogKind, ICamelLanguageDefinition } from '../../../models'; +import { CamelCatalogService } from '../../../models/visualization/flows'; + +describe('ExpressionService', () => { + beforeAll(() => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + delete (yamlDslSchema as any).default; + delete (languageCatalog as any).default; + CamelCatalogService.setCatalogKey( + CatalogKind.Language, + languageCatalog as unknown as Record, + ); + }); + + describe('getLanguageMap', () => { + it('should return language map', () => { + const languageMap = ExpressionService.getLanguageMap(yamlDslSchema); + expect(languageMap.simple.language.title).toEqual('Simple'); + expect(languageMap.jq.language.name).toEqual('jq'); + expect(languageMap.file).toBeUndefined(); + expect(languageMap.language.language.description).toContain('custom'); + expect(languageMap.language.properties.language.title).toEqual('Language'); + }); + + it('should return language map without custom if schema is empty', () => { + const languageMap = ExpressionService.getLanguageMap({}); + expect(languageMap.simple.language.title).toEqual('Simple'); + expect(languageMap.jq.language.name).toEqual('jq'); + expect(languageMap.language).toBeUndefined(); + }); + }); + + describe('getLanguageSchema', () => { + it('should return language schema', () => { + const languageMap = ExpressionService.getLanguageMap(yamlDslSchema); + const jsonpathSchema = ExpressionService.getLanguageSchema(languageMap.jsonpath); + expect(jsonpathSchema.properties.suppressExceptions.type).toBe('boolean'); + const customSchema = ExpressionService.getLanguageSchema(languageMap.language); + expect(customSchema.properties.language.type).toBe('string'); + }); + }); + + describe('parseExpressionModel', () => { + let languageMap: Record; + beforeAll(() => { + languageMap = ExpressionService.getLanguageMap(yamlDslSchema); + }); + + it('should parse #1', () => { + const { language, model } = ExpressionService.parseExpressionModel(languageMap, { simple: '${body}' }); + expect(language).toEqual(languageMap.simple); + expect(model).toEqual({ expression: '${body}' }); + }); + + it('should parse #2', () => { + const { language, model } = ExpressionService.parseExpressionModel(languageMap, { + simple: { expression: '${body}' }, + }); + expect(language).toEqual(languageMap.simple); + expect(model).toEqual({ expression: '${body}' }); + }); + + it('should parse #3', () => { + const { language, model } = ExpressionService.parseExpressionModel(languageMap, { + expression: { + simple: '${body}', + }, + }); + expect(language).toEqual(languageMap.simple); + expect(model).toEqual({ expression: '${body}' }); + }); + + it('should parse #4', () => { + const { language, model } = ExpressionService.parseExpressionModel(languageMap, { + expression: { + simple: { expression: '${body}' }, + }, + }); + expect(language).toEqual(languageMap.simple); + expect(model).toEqual({ expression: '${body}' }); + }); + + it('should return simple and empty model if model is empty', () => { + const { language, model } = ExpressionService.parseExpressionModel(languageMap, {}); + expect(language).toEqual(languageMap.simple); + expect(model).toEqual({}); + }); + + it('should return simple and empty model if language map and model is empty', () => { + const { language, model } = ExpressionService.parseExpressionModel({}, {}); + expect(language).toBeUndefined(); + expect(model).toEqual({}); + }); + }); + + describe('setExpressionModel', () => { + let languageMap: Record; + beforeAll(() => { + languageMap = ExpressionService.getLanguageMap(yamlDslSchema); + }); + + it('should write expression', () => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + const parentModel: any = {}; + ExpressionService.setExpressionModel(languageMap, parentModel, 'simple', { expression: '${body}' }); + expect(parentModel.expression.simple.expression).toEqual('${body}'); + }); + + it('should write expression and remove existing', () => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + const parentModel: any = { constant: 'foo' }; + ExpressionService.setExpressionModel(languageMap, parentModel, 'simple', { + expression: '${body}', + resultType: 'string', + }); + expect(parentModel.constant).toBeUndefined(); + expect(parentModel.expression.simple.expression).toEqual('${body}'); + expect(parentModel.expression.simple.resultType).toEqual('string'); + }); + + it('should not write if empty', () => { + const parentModel: any = {}; + ExpressionService.setExpressionModel(languageMap, parentModel, '', {}); + expect(parentModel.expression.simple).toBeUndefined(); + }); + }); +}); diff --git a/packages/ui/src/components/Visualization/Canvas/expression.service.ts b/packages/ui/src/components/Visualization/Canvas/expression.service.ts new file mode 100644 index 000000000..baa045de8 --- /dev/null +++ b/packages/ui/src/components/Visualization/Canvas/expression.service.ts @@ -0,0 +1,146 @@ +import { CamelCatalogService } from '../../../models/visualization/flows'; +import { CatalogKind, ICamelLanguageDefinition, ICamelLanguageModel, ICamelLanguageProperty } from '../../../models'; +import { CamelComponentSchemaService } from '../../../models/visualization/flows/support/camel-component-schema.service'; + +export class ExpressionService { + /** + * Get the language catalog map from the Camel catalog. Since "language" language is not in the languages Camel + * catalog, it is extracted from the YAML DSL schema. + * @param yamlDslSchema + */ + static getLanguageMap(yamlDslSchema: Record): Record { + const catalog = { ...CamelCatalogService.getLanguageMap() }; + + // "language" for custom language is not in the Camel catalog. Create from YAML DSL schema and Add here. + const customDefinition = ExpressionService.createCustomLanguageDefinition(yamlDslSchema); + if (customDefinition) { + catalog['language'] = customDefinition; + } + + // "file" language is merged with "simple" language and it doesn't exist in YAML DSL schema. Remove it here. + delete catalog['file']; + + return catalog; + } + + private static createCustomLanguageDefinition(yamlDslSchema: Record) { + const definitions = (yamlDslSchema.items as Record)?.definitions as Record; + const customLanguageSchema = + definitions && (definitions['org.apache.camel.model.language.LanguageExpression'] as Record); + if (!customLanguageSchema) return; + + const properties = Object.entries(customLanguageSchema.properties as Record).reduce( + (acc, [key, value]) => { + acc[key] = { + index: Object.keys(acc).length, + required: (customLanguageSchema.required as string[]).includes(key), + ...(value as Record), + } as ICamelLanguageProperty; + return acc; + }, + {} as Record, + ); + return { + language: { + kind: CatalogKind.Language, + name: 'language', + title: customLanguageSchema.title, + description: customLanguageSchema.description, + deprecated: false, + modelName: 'language', + } as ICamelLanguageModel, + properties: properties, + } as ICamelLanguageDefinition; + } + + /** + * Get the language schema from the language catalog. ATM it delegates to {@link CamelComponentSchemaService.getSchemaFromCamelCommonProperties}. + * @TODO The language `xtokenize` has `namespace` property with the type `array`, which causes an error in uniforms. Fix the schema here, or in {@link CamelComponentSchemaService.getSchemaFromCamelCommonProperties} if it could be common. + * @param languageCatalog + */ + static getLanguageSchema(languageCatalog: ICamelLanguageDefinition) { + return CamelComponentSchemaService.getSchemaFromCamelCommonProperties(languageCatalog.properties); + } + + /** + * Parse the expression model from the parent step model object. Since expression has several dialects, + * this method tries to read all possibility and merge into a single representation. + * @param languageCatalogMap + * @param parentModel + */ + static parseExpressionModel( + languageCatalogMap: Record, + parentModel: Record, + ): { + language: ICamelLanguageDefinition | undefined; + model: Record; + } { + let languageModelName = 'simple'; + let model = undefined; + if (parentModel?.expression && Object.keys(parentModel.expression).length > 0) { + languageModelName = Object.keys(parentModel.expression)[0]; + model = ExpressionService.parseLanguageModel( + parentModel.expression as Record, + languageModelName, + ); + } else { + for (const language of Object.values(languageCatalogMap)) { + if (parentModel[language.language.modelName]) { + languageModelName = language.language.modelName; + model = ExpressionService.parseLanguageModel(parentModel, language.language.modelName); + break; + } + } + if (!model) { + parentModel.expression = {}; + model = {}; + (parentModel.expression as Record)[languageModelName] = model; + } + } + const language = this.getDefinitionFromModelName(languageCatalogMap, languageModelName); + return { language, model }; + } + + private static parseLanguageModel(model: Record, langName: string) { + const lang = model[langName]; + if (typeof lang === 'string') { + return { expression: lang }; + } else { + return lang as Record; + } + } + + private static getDefinitionFromModelName( + languageCatalogMap: Record, + modelName: string, + ): ICamelLanguageDefinition | undefined { + return Object.values(languageCatalogMap).find((model) => model.language.modelName === modelName); + } + + /** + * Set the expression model to the parent step model object. This method uses the most verbose dialect, i.e. + * ```yaml + * - setBody: + * expression: + * simple: + * expression: ${body} + * trim: true + * ``` + * @param languageCatalogMap + * @param parentModel + * @param languageModelName + * @param newExpressionModel + */ + static setExpressionModel( + languageCatalogMap: Record, + parentModel: Record, + languageModelName: string, + newExpressionModel: Record, + ): void { + Object.values(languageCatalogMap).forEach((language) => { + delete parentModel[language.language.modelName]; + }); + parentModel.expression = {}; + (parentModel.expression as Record)[languageModelName] = newExpressionModel; + } +} diff --git a/packages/ui/src/models/camel-catalog-index.ts b/packages/ui/src/models/camel-catalog-index.ts index 994aaeb77..494f7f7d4 100644 --- a/packages/ui/src/models/camel-catalog-index.ts +++ b/packages/ui/src/models/camel-catalog-index.ts @@ -1,5 +1,7 @@ import { ICamelComponentDefinition } from './camel-components-catalog'; import { ICamelProcessorDefinition } from './camel-processors-catalog'; +import { ICamelLanguageDefinition } from './camel-languages-catalog'; +import { ICamelDataformatDefinition } from './camel-dataformats-catalog'; import { CatalogKind } from './catalog-kind'; import { IKameletDefinition } from './kamelets-catalog'; @@ -26,7 +28,12 @@ export interface SchemaEntry extends CatalogEntry { description: string; } -export type ComponentsCatalogTypes = ICamelComponentDefinition | ICamelProcessorDefinition | IKameletDefinition; +export type ComponentsCatalogTypes = + | ICamelComponentDefinition + | ICamelProcessorDefinition + | ICamelLanguageDefinition + | ICamelDataformatDefinition + | IKameletDefinition; export type DefinedComponent = { name: string; type: CatalogKind; @@ -37,5 +44,7 @@ export type DefinedComponent = { export interface ComponentsCatalog { [CatalogKind.Component]?: Record; [CatalogKind.Processor]?: Record; + [CatalogKind.Language]?: Record; + [CatalogKind.Dataformat]?: Record; [CatalogKind.Kamelet]?: Record; } diff --git a/packages/ui/src/models/camel-dataformats-catalog.ts b/packages/ui/src/models/camel-dataformats-catalog.ts new file mode 100644 index 000000000..3ef3d566b --- /dev/null +++ b/packages/ui/src/models/camel-dataformats-catalog.ts @@ -0,0 +1,25 @@ +import { CamelPropertyCommon } from './camel-properties-common'; +import { CatalogKind } from './catalog-kind'; + +export interface ICamelDataformatDefinition { + model: ICamelDataformatModel; + properties: Record; +} + +export interface ICamelDataformatModel { + kind: CatalogKind.Dataformat; + name: string; + title: string; + description?: string; + deprecated: boolean; + label: string; + javaType?: string; + abstract?: boolean; + input?: boolean; + output?: boolean; +} + +export interface ICamelDataformatProperty extends CamelPropertyCommon { + oneOf?: string[]; + type: string; +} diff --git a/packages/ui/src/models/camel-languages-catalog.ts b/packages/ui/src/models/camel-languages-catalog.ts new file mode 100644 index 000000000..aee5a6c00 --- /dev/null +++ b/packages/ui/src/models/camel-languages-catalog.ts @@ -0,0 +1,29 @@ +import { CamelPropertyCommon } from './camel-properties-common'; +import { CatalogKind } from './catalog-kind'; + +export interface ICamelLanguageDefinition { + language: ICamelLanguageModel; + properties: Record; +} + +export interface ICamelLanguageModel { + kind: CatalogKind.Language; + name: string; + title: string; + description?: string; + deprecated: boolean; + firstVersion: string; + label: string; + javaType?: string; + supportLelvel: string; + groupId: string; + artifactId: string; + version: string; + modelName: string; + modelJavaType: string; +} + +export interface ICamelLanguageProperty extends CamelPropertyCommon { + type: string; + title: string; +} diff --git a/packages/ui/src/models/catalog-kind.ts b/packages/ui/src/models/catalog-kind.ts index 798d3fc52..65cba6ac0 100644 --- a/packages/ui/src/models/catalog-kind.ts +++ b/packages/ui/src/models/catalog-kind.ts @@ -1,5 +1,7 @@ export const enum CatalogKind { Component = 'component', Processor = 'model', + Language = 'language', + Dataformat = 'dataformat', Kamelet = 'kamelet', } diff --git a/packages/ui/src/models/index.ts b/packages/ui/src/models/index.ts index b34f02a93..b03fccdd0 100644 --- a/packages/ui/src/models/index.ts +++ b/packages/ui/src/models/index.ts @@ -1,6 +1,8 @@ export * from './camel-catalog-index'; export * from './camel-components-catalog'; export * from './camel-processors-catalog'; +export * from './camel-languages-catalog'; +export * from './camel-dataformats-catalog'; export * from './camel-properties-common'; export * from './catalog-kind'; export * from './kamelets-catalog'; diff --git a/packages/ui/src/models/visualization/flows/camel-catalog.service.ts b/packages/ui/src/models/visualization/flows/camel-catalog.service.ts index 190784745..f42a408e1 100644 --- a/packages/ui/src/models/visualization/flows/camel-catalog.service.ts +++ b/packages/ui/src/models/visualization/flows/camel-catalog.service.ts @@ -3,6 +3,8 @@ import { ICamelComponentDefinition } from '../../camel-components-catalog'; import { ICamelProcessorDefinition } from '../../camel-processors-catalog'; import { CatalogKind } from '../../catalog-kind'; import { IKameletDefinition } from '../../kamelets-catalog'; +import { ICamelDataformatDefinition } from '../../camel-dataformats-catalog'; +import { ICamelLanguageDefinition } from '../../camel-languages-catalog'; export class CamelCatalogService { private static catalogs: ComponentsCatalog = {}; @@ -20,6 +22,11 @@ export class CamelCatalogService { static getComponent(catalogKey: CatalogKind.Component, componentName?: string): ICamelComponentDefinition | undefined; static getComponent(catalogKey: CatalogKind.Processor, componentName?: string): ICamelProcessorDefinition | undefined; + static getComponent(catalogKey: CatalogKind.Language, languageName?: string): ICamelLanguageDefinition | undefined; + static getComponent( + catalogKey: CatalogKind.Dataformat, + dataformatName?: string, + ): ICamelDataformatDefinition | undefined; static getComponent(catalogKey: CatalogKind.Kamelet, componentName?: string): IKameletDefinition | undefined; static getComponent(catalogKey: CatalogKind, componentName?: string): ComponentsCatalogTypes | undefined; static getComponent(catalogKey: CatalogKind, componentName?: string): ComponentsCatalogTypes | undefined { @@ -28,6 +35,10 @@ export class CamelCatalogService { return this.catalogs[catalogKey]?.[componentName]; } + static getLanguageMap(): Record { + return this.catalogs[CatalogKind.Language] || {}; + } + /** * Public only as a convenience method for test * not meant to be used in production code diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts index 5a028974a..df855d80f 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts @@ -7,6 +7,7 @@ import { CatalogKind } from '../../../catalog-kind'; import { VisualComponentSchema } from '../../base-visual-entity'; import { CamelCatalogService } from '../camel-catalog.service'; import { CamelProcessorStepsProperties, ICamelElementLookupResult } from './camel-component-types'; +import { ICamelLanguageProperty } from '../../../camel-languages-catalog'; export class CamelComponentSchemaService { static DISABLED_SIBLING_STEPS = ['from', 'when', 'otherwise', 'doCatch', 'doFinally']; @@ -225,9 +226,10 @@ export class CamelComponentSchemaService { /** * Transform Camel Common properties into a JSON Schema + * @TODO Now this is also used by {@link ExpressionService} and will be used for dataformats for next, possibly move it to a common place */ - private static getSchemaFromCamelCommonProperties( - properties: Record, + static getSchemaFromCamelCommonProperties( + properties: Record, ): JSONSchemaType { const required: string[] = []; const schema = { diff --git a/packages/ui/src/providers/catalog.provider.tsx b/packages/ui/src/providers/catalog.provider.tsx index a64c42708..f92d74997 100644 --- a/packages/ui/src/providers/catalog.provider.tsx +++ b/packages/ui/src/providers/catalog.provider.tsx @@ -23,18 +23,28 @@ export const CatalogLoaderProvider: FunctionComponent = (prop const camelProcessorsFiles = CatalogSchemaLoader.fetchFile( catalogIndex.catalogs.models.file, ); + const camelLanguagesFiles = CatalogSchemaLoader.fetchFile( + catalogIndex.catalogs.languages.file, + ); + const camelDataformatsFiles = CatalogSchemaLoader.fetchFile( + catalogIndex.catalogs.dataformats.file, + ); const kameletsFiles = CatalogSchemaLoader.fetchFile( catalogIndex.catalogs.kamelets.file, ); - const [camelComponents, camelProcessors, kamelets] = await Promise.all([ + const [camelComponents, camelProcessors, camelLanguages, camelDataformats, kamelets] = await Promise.all([ camelComponentsFiles, camelProcessorsFiles, + camelLanguagesFiles, + camelDataformatsFiles, kameletsFiles, ]); CamelCatalogService.setCatalogKey(CatalogKind.Component, camelComponents.body); CamelCatalogService.setCatalogKey(CatalogKind.Processor, camelProcessors.body); + CamelCatalogService.setCatalogKey(CatalogKind.Language, camelLanguages.body); + CamelCatalogService.setCatalogKey(CatalogKind.Dataformat, camelDataformats.body); CamelCatalogService.setCatalogKey(CatalogKind.Kamelet, kamelets.body); }) .then(() => { diff --git a/packages/ui/src/stubs/PipeErrorHandler.ts b/packages/ui/src/stubs/PipeErrorHandler.ts deleted file mode 100644 index 777e4ecaa..000000000 --- a/packages/ui/src/stubs/PipeErrorHandler.ts +++ /dev/null @@ -1,111 +0,0 @@ -export const pipeErrorHandlerJson = { - $schema: 'http://json-schema.org/draft-07/schema#', - type: 'object', - additionalProperties: false, - description: - 'Camel K Pipe ErrorHandler. See https://camel.apache.org/camel-k/latest/pipe-step.html#_error_handler for more details.', - oneOf: [ - { - title: 'No ErrorHandler', - type: 'object', - properties: { - none: { - type: 'object', - }, - }, - required: ['none'], - }, - { - title: 'Log ErrorHandler', - type: 'object', - properties: { - log: { - type: 'object', - additionalProperties: false, - properties: { - parameters: { - type: 'object', - properties: { - maximumRedeliveries: { - type: 'number', - }, - redeliveryDelay: { - type: 'number', - }, - }, - additionalProperties: { - type: 'string', - }, - }, - }, - }, - }, - required: ['log'], - }, - { - title: 'Sink ErrorHandler', - type: 'object', - properties: { - sink: { - type: 'object', - additionalProperties: false, - properties: { - endpoint: { - type: 'object', - additionalProperties: false, - properties: { - ref: { - type: 'object', - additionalProperties: false, - properties: { - kind: { - type: 'string', - }, - apiVersion: { - type: 'string', - }, - name: { - type: 'string', - }, - }, - required: ['kind', 'apiVersion', 'name'], - }, - properties: { - type: 'object', - properties: { - message: { - type: 'string', - }, - additionalProperties: { - type: 'string', - }, - }, - }, - }, - }, - parameters: { - type: 'object', - properties: { - maximumRedeliveries: { - type: 'number', - }, - redeliveryDelay: { - type: 'number', - }, - }, - additionalProperties: { - type: 'string', - }, - }, - }, - }, - }, - required: ['sink'], - }, - ], - properties: { - none: {}, - log: {}, - sink: {}, - }, -};