From c412db0e0ce2b21fbcca6d2b44c0299ac07e1cbc Mon Sep 17 00:00:00 2001 From: mmelko Date: Thu, 28 Nov 2024 14:44:47 +0100 Subject: [PATCH] refactor(entities,resources): Decouple parsing from entities hook into separate serializer class * create CamelResourceSerializer interface and YamlCamelResourceSerializer implementantion * create CamelResourceFactory and CamelKResourceFactory to creating new resources * move parsing out of the entities hook --- packages/ui/src/hooks/entities.ts | 34 +++---------- .../models/camel/camel-k-resource-factory.ts | 33 +++++++++++++ .../ui/src/models/camel/camel-k-resource.ts | 25 +++++++--- .../models/camel/camel-resource-factory.ts | 27 ++++++++++ .../src/models/camel/camel-resource.test.ts | 28 +++++------ .../ui/src/models/camel/camel-resource.ts | 49 ++----------------- .../models/camel/camel-route-resource.test.ts | 34 +++++-------- .../src/models/camel/camel-route-resource.ts | 34 ++++++++----- .../src/models/camel/kamelet-resource.test.ts | 8 --- .../ui/src/models/camel/kamelet-resource.ts | 2 +- .../ui/src/models/camel/pipe-resource.test.ts | 8 --- .../serializers/camel-resource-serializer.ts | 6 +++ packages/ui/src/serializers/index.ts | 3 ++ .../xml-camel-resource-serializer.ts | 18 +++++++ .../yaml-camel-resource-serializer.ts | 45 +++++++++++++++++ 15 files changed, 210 insertions(+), 144 deletions(-) create mode 100644 packages/ui/src/models/camel/camel-k-resource-factory.ts create mode 100644 packages/ui/src/models/camel/camel-resource-factory.ts create mode 100644 packages/ui/src/serializers/camel-resource-serializer.ts create mode 100644 packages/ui/src/serializers/index.ts create mode 100644 packages/ui/src/serializers/xml-camel-resource-serializer.ts create mode 100644 packages/ui/src/serializers/yaml-camel-resource-serializer.ts diff --git a/packages/ui/src/hooks/entities.ts b/packages/ui/src/hooks/entities.ts index f376a404d..edc7a86b9 100644 --- a/packages/ui/src/hooks/entities.ts +++ b/packages/ui/src/hooks/entities.ts @@ -1,9 +1,10 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; -import { parse, stringify } from 'yaml'; -import { CamelResource, SourceSchemaType, createCamelResource } from '../models/camel'; + +import { CamelResource, SourceSchemaType } from '../models/camel'; import { BaseCamelEntity } from '../models/camel/entities'; import { BaseVisualCamelEntity } from '../models/visualization/base-visual-entity'; import { EventNotifier } from '../utils'; +import { CamelResourceFactory } from '../models/camel/camel-resource-factory'; /** * Regular expression to match commented lines, regardless of indentation @@ -15,7 +16,6 @@ import { EventNotifier } from '../utils'; * ``` * The regular expression should match the first three lines */ -const COMMENTED_LINES_REGEXP = /^\s*#.*$/; export interface EntitiesContextResult { entities: BaseCamelEntity[]; @@ -49,7 +49,7 @@ export interface EntitiesContextResult { export const useEntities = (): EntitiesContextResult => { const eventNotifier = EventNotifier.getInstance(); - const [camelResource, setCamelResource] = useState(createCamelResource()); + const [camelResource, setCamelResource] = useState(CamelResourceFactory.createCamelResource()); const [entities, setEntities] = useState([]); const [visualEntities, setVisualEntities] = useState([]); @@ -58,22 +58,10 @@ export const useEntities = (): EntitiesContextResult => { */ useLayoutEffect(() => { return eventNotifier.subscribe('code:updated', (code) => { - /** Extract comments from the source code */ - const lines = code.split('\n'); - const comments: string[] = []; - for (const line of lines) { - if (line.trim() === '' || COMMENTED_LINES_REGEXP.test(line)) { - comments.push(line); - } else { - break; - } - } - - const rawEntities = parse(code); - const camelResource = createCamelResource(rawEntities); - camelResource.setComments(comments); + const camelResource = CamelResourceFactory.createCamelResource(code); const entities = camelResource.getEntities(); const visualEntities = camelResource.getVisualEntities(); + setCamelResource(camelResource); setEntities(entities); setVisualEntities(visualEntities); @@ -81,13 +69,7 @@ export const useEntities = (): EntitiesContextResult => { }, [eventNotifier]); const updateSourceCodeFromEntities = useCallback(() => { - let code = stringify(camelResource, { sortMapEntries: camelResource.sortFn, schema: 'yaml-1.1' }) || ''; - - if (camelResource.getComments().length > 0) { - const comments = camelResource.getComments().join('\n'); - code = comments + '\n' + code; - } - + const code = camelResource.toString(); eventNotifier.next('entities:updated', code); }, [camelResource, eventNotifier]); @@ -105,7 +87,7 @@ export const useEntities = (): EntitiesContextResult => { const setCurrentSchemaType = useCallback( (type: SourceSchemaType) => { - setCamelResource(createCamelResource(type)); + setCamelResource(CamelResourceFactory.createCamelResource(type)); updateEntitiesFromCamelResource(); }, [updateEntitiesFromCamelResource], diff --git a/packages/ui/src/models/camel/camel-k-resource-factory.ts b/packages/ui/src/models/camel/camel-k-resource-factory.ts new file mode 100644 index 000000000..817292fb7 --- /dev/null +++ b/packages/ui/src/models/camel/camel-k-resource-factory.ts @@ -0,0 +1,33 @@ +import { SourceSchemaType } from './source-schema-type'; +import { CamelResource } from './camel-resource'; +import { IntegrationResource } from './integration-resource'; +import { KameletResource } from './kamelet-resource'; +import { KameletBindingResource } from './kamelet-binding-resource'; +import { PipeResource } from './pipe-resource'; +import { IKameletDefinition } from '../kamelets-catalog'; +import { + Integration as IntegrationType, + KameletBinding as KameletBindingType, + Pipe as PipeType, +} from '@kaoto/camel-catalog/types'; + +export class CamelKResourceFactory { + static getCamelKResource(json?: unknown, type?: SourceSchemaType): CamelResource | undefined { + const jsonRecord = json as Record; + if ((jsonRecord && typeof json === 'object' && 'kind' in jsonRecord) || type) { + switch (jsonRecord['kind'] || type) { + case SourceSchemaType.Integration: + return new IntegrationResource(json as IntegrationType); + case SourceSchemaType.Kamelet: + return new KameletResource(json as IKameletDefinition); + case SourceSchemaType.KameletBinding: + return new KameletBindingResource(json as KameletBindingType); + case SourceSchemaType.Pipe: + return new PipeResource(json as PipeType); + default: + return undefined; + } + } + return undefined; + } +} diff --git a/packages/ui/src/models/camel/camel-k-resource.ts b/packages/ui/src/models/camel/camel-k-resource.ts index c13bc9362..0a0c3225c 100644 --- a/packages/ui/src/models/camel/camel-k-resource.ts +++ b/packages/ui/src/models/camel/camel-k-resource.ts @@ -11,6 +11,7 @@ import { MetadataEntity } from '../visualization/metadata'; import { BaseVisualCamelEntityDefinition, CamelResource } from './camel-resource'; import { BaseCamelEntity } from './entities'; import { SourceSchemaType } from './source-schema-type'; +import { CamelResourceSerializer, YamlCamelResourceSerializer } from '../../serializers'; export type CamelKType = IntegrationType | IKameletDefinition | KameletBindingType | PipeType; @@ -25,14 +26,19 @@ export const CAMEL_K_K8S_API_VERSION_V1 = 'camel.apache.org/v1'; export abstract class CamelKResource implements CamelResource { static readonly PARAMETERS_ORDER = ['apiVersion', 'kind', 'metadata', 'spec', 'source', 'steps', 'sink']; + // static serializer = new YamlResourceSerializer(); readonly sortFn = createCamelPropertiesSorter(CamelKResource.PARAMETERS_ORDER) as (a: unknown, b: unknown) => number; protected resource: CamelKType; private metadata?: MetadataEntity; - private comments: string[] = []; - constructor(resource?: CamelKType) { - if (resource) { - this.resource = resource; + constructor( + parsedResource: unknown, + private readonly serializer: CamelResourceSerializer = new YamlCamelResourceSerializer(), + ) { + this.serializer = serializer; + + if (parsedResource) { + this.resource = parsedResource as CamelKType; } else { this.resource = { apiVersion: CAMEL_K_K8S_API_VERSION_V1, @@ -94,12 +100,15 @@ export abstract class CamelKResource implements CamelResource { getCompatibleComponents(_mode: AddStepMode, _visualEntityData: IVisualizationNodeData): TileFilter | undefined { return undefined; } + getSerializer() { + return this.serializer; + } - setComments(comments: string[]) { - this.comments = comments; + setSerializer(_serializer: CamelResourceSerializer): void { + /** Not supported by default */ } - getComments(): string[] { - return this.comments; + toString(): string { + return this.serializer.serialize(this); } } diff --git a/packages/ui/src/models/camel/camel-resource-factory.ts b/packages/ui/src/models/camel/camel-resource-factory.ts new file mode 100644 index 000000000..c39d8d79c --- /dev/null +++ b/packages/ui/src/models/camel/camel-resource-factory.ts @@ -0,0 +1,27 @@ +import { SourceSchemaType } from './source-schema-type'; +import { CamelResource } from './camel-resource'; +import { XmlCamelResourceSerializer, YamlCamelResourceSerializer } from '../../serializers'; +import { CamelRouteResource } from './camel-route-resource'; +import { CamelKResourceFactory } from './camel-k-resource-factory'; + +export class CamelResourceFactory { + /** + * Creates a CamelResource based on the given {@link type} and {@link source}. If + * both are not specified, a default empty {@link CamelRouteResource} is created. + * If only {@link type} is specified, an empty {@link CamelResource} of the given + * {@link type} is created. + * @param type + * @param source + */ + static createCamelResource(source?: string, type?: SourceSchemaType): CamelResource { + if (XmlCamelResourceSerializer.isApplicable(source)) { + return new CamelRouteResource(source, new XmlCamelResourceSerializer()); + } + + const serializer = new YamlCamelResourceSerializer(); + const resource = CamelKResourceFactory.getCamelKResource(serializer.parse(source ?? ''), type); + + if (resource) return resource; + return new CamelRouteResource(source, serializer); + } +} diff --git a/packages/ui/src/models/camel/camel-resource.test.ts b/packages/ui/src/models/camel/camel-resource.test.ts index 99af20671..f5d83a61f 100644 --- a/packages/ui/src/models/camel/camel-resource.test.ts +++ b/packages/ui/src/models/camel/camel-resource.test.ts @@ -1,50 +1,50 @@ -import { camelRouteJson } from '../../stubs/camel-route'; +import { camelRouteYaml } from '../../stubs/camel-route'; import { integrationJson } from '../../stubs/integration'; import { kameletBindingJson } from '../../stubs/kamelet-binding-route'; import { kameletJson } from '../../stubs/kamelet-route'; import { pipeJson } from '../../stubs/pipe'; import { CamelRouteVisualEntity, PipeVisualEntity } from '../visualization/flows'; -import { createCamelResource } from './camel-resource'; import { SourceSchemaType } from './source-schema-type'; +import { CamelResourceFactory } from './camel-resource-factory'; -describe('createCamelResource', () => { +describe('CamelResourceFactory.createCamelResource', () => { it('should create an empty CamelRouteResource if no args is specified', () => { - const resource = createCamelResource(); + const resource = CamelResourceFactory.createCamelResource(); expect(resource.getType()).toEqual(SourceSchemaType.Route); expect(resource.getEntities()).toEqual([]); expect(resource.getVisualEntities()).toEqual([]); }); it('should create an empty CamelRouteResource if no args is specified', () => { - const resource = createCamelResource(undefined, SourceSchemaType.Route); + const resource = CamelResourceFactory.createCamelResource(undefined, SourceSchemaType.Route); expect(resource.getType()).toEqual(SourceSchemaType.Route); expect(resource.getEntities()).toEqual([]); expect(resource.getVisualEntities()).toEqual([]); }); it('should create an empty IntegrationResource if no args is specified', () => { - const resource = createCamelResource(undefined, SourceSchemaType.Integration); + const resource = CamelResourceFactory.createCamelResource(undefined, SourceSchemaType.Integration); expect(resource.getType()).toEqual(SourceSchemaType.Integration); expect(resource.getEntities()).toEqual([]); expect(resource.getVisualEntities()).toEqual([]); }); it('should create an empty KameletResource if no args is specified', () => { - const resource = createCamelResource(undefined, SourceSchemaType.Kamelet); + const resource = CamelResourceFactory.createCamelResource(undefined, SourceSchemaType.Kamelet); expect(resource.getType()).toEqual(SourceSchemaType.Kamelet); expect(resource.getEntities()).toEqual([]); expect(resource.getVisualEntities()).toMatchSnapshot(); }); it('should create an empty CameletBindingResource if no args is specified', () => { - const resource = createCamelResource(undefined, SourceSchemaType.KameletBinding); + const resource = CamelResourceFactory.createCamelResource(undefined, SourceSchemaType.KameletBinding); expect(resource.getType()).toEqual(SourceSchemaType.KameletBinding); expect(resource.getEntities()).toEqual([]); expect(resource.getVisualEntities().length).toEqual(1); }); it('should create an empty PipeResource if no args is specified', () => { - const resource = createCamelResource(undefined, SourceSchemaType.Pipe); + const resource = CamelResourceFactory.createCamelResource(undefined, SourceSchemaType.Pipe); expect(resource.getType()).toEqual(SourceSchemaType.Pipe); expect(resource.getEntities()).toEqual([]); expect(resource.getVisualEntities().length).toEqual(1); @@ -55,7 +55,7 @@ describe('createCamelResource', () => { }); it('should create a camel route', () => { - const resource = createCamelResource(camelRouteJson); + const resource = CamelResourceFactory.createCamelResource(camelRouteYaml); expect(resource.getType()).toEqual(SourceSchemaType.Route); expect(resource.getVisualEntities().length).toEqual(1); const vis = resource.getVisualEntities()[0] as CamelRouteVisualEntity; @@ -64,19 +64,19 @@ describe('createCamelResource', () => { // TODO it.skip('should create an Integration', () => { - const resource = createCamelResource(integrationJson); + const resource = CamelResourceFactory.createCamelResource(JSON.stringify(integrationJson)); expect(resource.getType()).toEqual(SourceSchemaType.Integration); expect(resource.getVisualEntities().length).toEqual(2); }); it('should create a Kamelet', () => { - const resource = createCamelResource(kameletJson); + const resource = CamelResourceFactory.createCamelResource(kameletJson); expect(resource.getType()).toEqual(SourceSchemaType.Kamelet); expect(resource.getVisualEntities().length).toEqual(1); }); it('should create a KameletBindingPipe', () => { - const resource = createCamelResource(kameletBindingJson); + const resource = CamelResourceFactory.createCamelResource(JSON.stringify(kameletBindingJson)); expect(resource.getType()).toEqual(SourceSchemaType.KameletBinding); expect(resource.getVisualEntities().length).toEqual(1); const vis = resource.getVisualEntities()[0] as PipeVisualEntity; @@ -84,7 +84,7 @@ describe('createCamelResource', () => { }); it('should create a Pipe', () => { - const resource = createCamelResource(pipeJson); + const resource = CamelResourceFactory.createCamelResource(JSON.stringify(pipeJson)); expect(resource.getType()).toEqual(SourceSchemaType.Pipe); expect(resource.getVisualEntities().length).toEqual(1); const vis = resource.getVisualEntities()[0] as PipeVisualEntity; diff --git a/packages/ui/src/models/camel/camel-resource.ts b/packages/ui/src/models/camel/camel-resource.ts index 965cd0fc6..79c0d073d 100644 --- a/packages/ui/src/models/camel/camel-resource.ts +++ b/packages/ui/src/models/camel/camel-resource.ts @@ -1,20 +1,10 @@ -import { - Integration as IntegrationType, - KameletBinding as KameletBindingType, - Pipe as PipeType, -} from '@kaoto/camel-catalog/types'; import { TileFilter } from '../../components/Catalog'; -import { IKameletDefinition } from '../kamelets-catalog'; import { AddStepMode, BaseVisualCamelEntity, IVisualizationNodeData } from '../visualization/base-visual-entity'; import { BeansEntity } from '../visualization/metadata'; import { RouteTemplateBeansEntity } from '../visualization/metadata/routeTemplateBeansEntity'; -import { CamelRouteResource } from './camel-route-resource'; import { BaseCamelEntity, EntityType } from './entities'; -import { IntegrationResource } from './integration-resource'; -import { KameletBindingResource } from './kamelet-binding-resource'; -import { KameletResource } from './kamelet-resource'; -import { PipeResource } from './pipe-resource'; import { SourceSchemaType } from './source-schema-type'; +import { CamelResourceSerializer } from '../../serializers'; export interface CamelResource { getVisualEntities(): BaseVisualCamelEntity[]; @@ -23,8 +13,11 @@ export interface CamelResource { removeEntity(id?: string): void; supportsMultipleVisualEntities(): boolean; toJSON(): unknown; + toString(): string; getType(): SourceSchemaType; getCanvasEntityList(): BaseVisualCamelEntityDefinition; + getSerializer(): CamelResourceSerializer; + setSerializer(serializer: CamelResourceSerializer): void; /** Components Catalog related methods */ getCompatibleComponents( @@ -35,8 +28,6 @@ export interface CamelResource { ): TileFilter | undefined; sortFn?: (a: unknown, b: unknown) => number; - setComments(comments: string[]): void; - getComments(): string[]; } export interface BaseVisualCamelEntityDefinition { @@ -60,35 +51,3 @@ export interface RouteTemplateBeansAwareResource { getRouteTemplateBeansEntity(): RouteTemplateBeansEntity | undefined; deleteRouteTemplateBeansEntity(): void; } - -/** - * Creates a CamelResource based on the given {@link type} and {@link json}. If - * both are not specified, a default empty {@link CamelRouteResource} is created. - * If only {@link type} is specified, an empty {@link CamelResource} of the given - * {@link type} is created. - * @param type - * @param json - */ -export function createCamelResource(json?: unknown, type?: SourceSchemaType): CamelResource { - const jsonRecord = json as Record; - if (json && typeof json === 'object' && 'kind' in jsonRecord) { - return doCreateCamelResource(json, jsonRecord['kind'] as SourceSchemaType); - } else { - return doCreateCamelResource(json, type || SourceSchemaType.Route); - } -} - -function doCreateCamelResource(json?: unknown, type?: SourceSchemaType): CamelResource { - switch (type) { - case SourceSchemaType.Integration: - return new IntegrationResource(json as IntegrationType); - case SourceSchemaType.Kamelet: - return new KameletResource(json as IKameletDefinition); - case SourceSchemaType.KameletBinding: - return new KameletBindingResource(json as KameletBindingType); - case SourceSchemaType.Pipe: - return new PipeResource(json as PipeType); - default: - return new CamelRouteResource(json); - } -} diff --git a/packages/ui/src/models/camel/camel-route-resource.test.ts b/packages/ui/src/models/camel/camel-route-resource.test.ts index 2a013d0f5..ebb35d1e5 100644 --- a/packages/ui/src/models/camel/camel-route-resource.test.ts +++ b/packages/ui/src/models/camel/camel-route-resource.test.ts @@ -1,19 +1,19 @@ import { beansJson } from '../../stubs/beans'; import { camelFromJson } from '../../stubs/camel-from'; -import { camelRouteJson } from '../../stubs/camel-route'; +import { camelRouteJson, camelRouteYaml } from '../../stubs/camel-route'; import { AddStepMode } from '../visualization/base-visual-entity'; import { CamelRouteVisualEntity } from '../visualization/flows/camel-route-visual-entity'; import { NonVisualEntity } from '../visualization/flows/non-visual-entity'; import { CamelComponentFilterService } from '../visualization/flows/support/camel-component-filter.service'; import { BeansEntity } from '../visualization/metadata/beansEntity'; -import { createCamelResource } from './camel-resource'; import { CamelRouteResource } from './camel-route-resource'; import { EntityType } from './entities'; import { SourceSchemaType } from './source-schema-type'; +import { CamelResourceFactory } from './camel-resource-factory'; describe('CamelRouteResource', () => { it('should create CamelRouteResource', () => { - const resource = new CamelRouteResource(camelRouteJson); + const resource = new CamelRouteResource(camelRouteYaml); expect(resource.getType()).toEqual(SourceSchemaType.Route); expect(resource.getVisualEntities().length).toEqual(1); expect(resource.getEntities().length).toEqual(0); @@ -39,7 +39,7 @@ describe('CamelRouteResource', () => { [null, undefined], [[], undefined], ])('should return the appropriate entity for: %s', (json, expected) => { - const resource = new CamelRouteResource(json); + const resource = new CamelRouteResource(JSON.stringify(json)); const firstEntity = resource.getVisualEntities()[0] ?? resource.getEntities()[0]; if (typeof expected === 'function') { @@ -98,14 +98,14 @@ describe('CamelRouteResource', () => { }); it('should return visual entities', () => { - const resource = new CamelRouteResource(camelRouteJson); + const resource = new CamelRouteResource(camelRouteYaml); expect(resource.getVisualEntities()).toHaveLength(1); expect(resource.getVisualEntities()[0]).toBeInstanceOf(CamelRouteVisualEntity); expect(resource.getEntities()).toHaveLength(0); }); it('should return entities', () => { - const resource = new CamelRouteResource(beansJson); + const resource = new CamelRouteResource(JSON.stringify(beansJson)); expect(resource.getEntities()).toHaveLength(1); expect(resource.getEntities()[0]).toBeInstanceOf(BeansEntity); expect(resource.getVisualEntities()).toHaveLength(0); @@ -113,7 +113,7 @@ describe('CamelRouteResource', () => { describe('toJSON', () => { it('should return JSON', () => { - const resource = new CamelRouteResource(camelRouteJson); + const resource = new CamelRouteResource(camelRouteYaml); expect(resource.toJSON()).toMatchSnapshot(); }); @@ -141,7 +141,7 @@ describe('CamelRouteResource', () => { describe('removeEntity', () => { it('should not do anything if the ID is not provided', () => { - const resource = new CamelRouteResource(camelRouteJson); + const resource = new CamelRouteResource(camelRouteYaml); resource.removeEntity(); @@ -149,7 +149,7 @@ describe('CamelRouteResource', () => { }); it('should not do anything when providing a non existing ID', () => { - const resource = new CamelRouteResource(camelRouteJson); + const resource = new CamelRouteResource(camelRouteYaml); resource.removeEntity('non-existing-id'); @@ -157,7 +157,7 @@ describe('CamelRouteResource', () => { }); it('should allow to remove an entity', () => { - const resource = new CamelRouteResource([camelRouteJson, camelFromJson]); + const resource = new CamelRouteResource(JSON.stringify([camelRouteJson, camelFromJson])); const camelRouteEntity = resource.getVisualEntities()[0]; resource.removeEntity(camelRouteEntity.id); @@ -166,7 +166,7 @@ describe('CamelRouteResource', () => { }); it('should NOT create a new entity after deleting them all', () => { - const resource = new CamelRouteResource(camelRouteJson); + const resource = new CamelRouteResource(camelRouteYaml); const camelRouteEntity = resource.getVisualEntities()[0]; resource.removeEntity(camelRouteEntity.id); @@ -179,7 +179,7 @@ describe('CamelRouteResource', () => { it('should delegate to the CamelComponentFilterService', () => { const filterSpy = jest.spyOn(CamelComponentFilterService, 'getCamelCompatibleComponents'); - const resource = createCamelResource(camelRouteJson); + const resource = CamelResourceFactory.createCamelResource(camelRouteYaml); resource.getCompatibleComponents(AddStepMode.ReplaceStep, { path: 'from', label: 'timer' }); expect(filterSpy).toHaveBeenCalledWith(AddStepMode.ReplaceStep, { path: 'from', label: 'timer' }, undefined); @@ -209,17 +209,9 @@ describe('CamelRouteResource', () => { [{ anotherUnknownContent: {} }], [{}], ])('should not throw error when calling: %s', (json) => { - const resource = new CamelRouteResource(json); + const resource = new CamelRouteResource(JSON.stringify(json)); const firstEntity = resource.getVisualEntities()[0] ?? resource.getEntities()[0]; expect(firstEntity.toJSON()).not.toBeUndefined(); }); }); - - describe('comments', () => { - it('should set and get comments', () => { - const resource = new CamelRouteResource(); - resource.setComments(['a', 'b']); - expect(resource.getComments()).toEqual(['a', 'b']); - }); - }); }); diff --git a/packages/ui/src/models/camel/camel-route-resource.ts b/packages/ui/src/models/camel/camel-route-resource.ts index 13a1a7a2c..d3b28add2 100644 --- a/packages/ui/src/models/camel/camel-route-resource.ts +++ b/packages/ui/src/models/camel/camel-route-resource.ts @@ -20,6 +20,8 @@ import { BeansEntity, isBeans } from '../visualization/metadata'; import { BaseVisualCamelEntityDefinition, BeansAwareResource, CamelResource } from './camel-resource'; import { BaseCamelEntity, EntityType } from './entities'; import { SourceSchemaType } from './source-schema-type'; +import { CamelResourceSerializer } from '../../serializers/camel-resource-serializer'; +import { YamlCamelResourceSerializer } from '../../serializers'; export class CamelRouteResource implements CamelResource, BeansAwareResource { static readonly SUPPORTED_ENTITIES: { type: EntityType; group: string; Entity: BaseVisualCamelEntityConstructor }[] = @@ -46,12 +48,15 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource { ) => number; private entities: BaseCamelEntity[] = []; private resolvedEntities: BaseVisualCamelEntityDefinition | undefined; - private comments: string[] = []; + private serializer: CamelResourceSerializer; - constructor(json?: unknown) { - if (!json) return; - const rawEntities = Array.isArray(json) ? json : [json]; - this.entities = rawEntities.reduce((acc, rawItem) => { + constructor(code?: string, serializer?: CamelResourceSerializer) { + this.serializer = serializer ?? new YamlCamelResourceSerializer(); + if (!code) return; + + const parsedCode = this.serializer.parse(code); + const entities = Array.isArray(parsedCode) ? parsedCode : [parsedCode]; + this.entities = entities.reduce((acc, rawItem) => { const entity = this.getEntity(rawItem); if (isDefined(entity) && typeof entity === 'object') { acc.push(entity); @@ -89,6 +94,13 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource { return this.resolvedEntities; } + getSerializer(): CamelResourceSerializer { + return this.serializer; + } + setSerializer(serializer: CamelResourceSerializer): void { + this.serializer = serializer; + } + addNewEntity(entityType?: EntityType): string { if (entityType && entityType !== EntityType.Route) { const supportedEntity = CamelRouteResource.SUPPORTED_ENTITIES.find(({ type }) => type === entityType); @@ -137,6 +149,10 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource { return this.entities.map((entity) => entity.toJSON()); } + toString() { + return this.serializer.serialize(this); + } + createBeansEntity(): BeansEntity { const newBeans = { beans: [] }; const beansEntity = new BeansEntity(newBeans); @@ -170,14 +186,6 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource { return CamelComponentFilterService.getCamelCompatibleComponents(mode, visualEntityData, definition); } - setComments(comments: string[]): void { - this.comments = comments; - } - - getComments(): string[] { - return this.comments; - } - private getEntity(rawItem: unknown): BaseCamelEntity | undefined { if (!isDefined(rawItem) || Array.isArray(rawItem)) { return undefined; diff --git a/packages/ui/src/models/camel/kamelet-resource.test.ts b/packages/ui/src/models/camel/kamelet-resource.test.ts index 3afde53f5..cbdfd4102 100644 --- a/packages/ui/src/models/camel/kamelet-resource.test.ts +++ b/packages/ui/src/models/camel/kamelet-resource.test.ts @@ -98,12 +98,4 @@ describe('KameletResource', () => { expect(model.spec.template.beans).toBeUndefined(); expect(kameletResource.getRouteTemplateBeansEntity()).toBeUndefined(); }); - - describe('comments', () => { - it('should set and get comments', () => { - const resource = new KameletResource(); - resource.setComments(['a', 'b']); - expect(resource.getComments()).toEqual(['a', 'b']); - }); - }); }); diff --git a/packages/ui/src/models/camel/kamelet-resource.ts b/packages/ui/src/models/camel/kamelet-resource.ts index 62299d808..19f4f8563 100644 --- a/packages/ui/src/models/camel/kamelet-resource.ts +++ b/packages/ui/src/models/camel/kamelet-resource.ts @@ -62,7 +62,7 @@ export class KameletResource extends CamelKResource implements RouteTemplateBean * the CamelRouteVisualEntity. */ set(this.resource, 'metadata.name', this.flow.getId()); - set(this.resource, 'spec.template.from', this.flow.entityDef.template.from); + set(this.resource, 'spec.template.from', this.flow.route.from); set(this.resource, 'spec.template.beans', this.beans?.parent.beans); return this.resource as IKameletDefinition; } diff --git a/packages/ui/src/models/camel/pipe-resource.test.ts b/packages/ui/src/models/camel/pipe-resource.test.ts index c1607c008..101ba07ba 100644 --- a/packages/ui/src/models/camel/pipe-resource.test.ts +++ b/packages/ui/src/models/camel/pipe-resource.test.ts @@ -53,12 +53,4 @@ describe('PipeResource', () => { expect(resource.getEntities().length).toEqual(0); expect(resource.toJSON().metadata).toBeUndefined(); }); - - describe('comments', () => { - it('should set and get comments', () => { - const resource = new PipeResource(); - resource.setComments(['a', 'b']); - expect(resource.getComments()).toEqual(['a', 'b']); - }); - }); }); diff --git a/packages/ui/src/serializers/camel-resource-serializer.ts b/packages/ui/src/serializers/camel-resource-serializer.ts new file mode 100644 index 000000000..2853bdc5d --- /dev/null +++ b/packages/ui/src/serializers/camel-resource-serializer.ts @@ -0,0 +1,6 @@ +import { CamelResource } from '../models/camel'; + +export interface CamelResourceSerializer { + parse: (code: string) => unknown; + serialize: (resource: CamelResource) => string; +} diff --git a/packages/ui/src/serializers/index.ts b/packages/ui/src/serializers/index.ts new file mode 100644 index 000000000..1ad677bad --- /dev/null +++ b/packages/ui/src/serializers/index.ts @@ -0,0 +1,3 @@ +export * from './camel-resource-serializer'; +export * from './xml-camel-resource-serializer'; +export * from './yaml-camel-resource-serializer'; diff --git a/packages/ui/src/serializers/xml-camel-resource-serializer.ts b/packages/ui/src/serializers/xml-camel-resource-serializer.ts new file mode 100644 index 000000000..70e5eae13 --- /dev/null +++ b/packages/ui/src/serializers/xml-camel-resource-serializer.ts @@ -0,0 +1,18 @@ +import { CamelResource } from '../models/camel'; +import { CamelResourceSerializer } from './camel-resource-serializer'; + +export class XmlCamelResourceSerializer implements CamelResourceSerializer { + static isApplicable(_code: unknown): boolean { + return false; + } + + parse(_code: unknown): unknown { + //TODO implement + return {}; + } + + serialize(_resource: CamelResource): string { + //TODO implement + return ''; + } +} diff --git a/packages/ui/src/serializers/yaml-camel-resource-serializer.ts b/packages/ui/src/serializers/yaml-camel-resource-serializer.ts new file mode 100644 index 000000000..a19092d2a --- /dev/null +++ b/packages/ui/src/serializers/yaml-camel-resource-serializer.ts @@ -0,0 +1,45 @@ +import { CamelResource } from '../models/camel'; +import { parse, stringify } from 'yaml'; +import { CamelResourceSerializer } from './camel-resource-serializer'; + +export class YamlCamelResourceSerializer implements CamelResourceSerializer { + COMMENTED_LINES_REGEXP = /^\s*#.*$/; + comments: string[] = []; + + static isApplicable(code: unknown): boolean { + //TODO + // return !isXML(code); + + return true; + } + + parse(code: string): unknown { + if (!code || typeof code !== 'string') return {}; + + this.comments = this.parseComments(code); + const json = parse(code); + return json; + } + + serialize(resource: CamelResource): string { + let code = stringify(resource, { sortMapEntries: resource.sortFn, schema: 'yaml-1.1' }) || ''; + if (this.comments.length > 0) { + const comments = this.comments.join('\n'); + code = comments + '\n' + code; + } + return code; + } + + private parseComments(code: string): string[] { + const lines = code.split('\n'); + const comments: string[] = []; + for (const line of lines) { + if (line.trim() === '' || this.COMMENTED_LINES_REGEXP.test(line)) { + comments.push(line); + } else { + break; + } + } + return comments; + } +}