diff --git a/packages/ui-tests/.storybook/main.ts b/packages/ui-tests/.storybook/main.ts index 7bd402cfe..b54121c70 100644 --- a/packages/ui-tests/.storybook/main.ts +++ b/packages/ui-tests/.storybook/main.ts @@ -47,6 +47,13 @@ const config: StorybookConfig = { __GIT_DATE: JSON.stringify('1970-01-01T00:00:00Z'), __KAOTO_VERSION: JSON.stringify(packageJson.version), }, + css: { + preprocessorOptions: { + scss: { + api: 'modern-compiler', + }, + }, + }, }; }, }; diff --git a/packages/ui-tests/cypress/e2e/designer/basicNodeActions/stepRemoval.cy.ts b/packages/ui-tests/cypress/e2e/designer/basicNodeActions/stepRemoval.cy.ts index a24e27e52..ca91494d1 100644 --- a/packages/ui-tests/cypress/e2e/designer/basicNodeActions/stepRemoval.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/basicNodeActions/stepRemoval.cy.ts @@ -33,8 +33,8 @@ describe('Tests for Design page', () => { cy.checkCodeSpanLine('json-deserialize-action', 0); cy.checkCodeSpanLine('kafka-source', 0); cy.checkCodeSpanLine('kafka-sink', 0); - cy.checkCodeSpanLine('source: {}', 1); - cy.checkCodeSpanLine('sink: {}', 1); + cy.checkCodeSpanLine('source: {}', 0); + cy.checkCodeSpanLine('sink: {}', 0); }); it('In an integration with at least two steps, user can not delete the from step', () => { diff --git a/packages/ui-tests/stories/canvas/Aggregate.stories.tsx b/packages/ui-tests/stories/canvas/Aggregate.stories.tsx index a630e9af6..9d43e6942 100644 --- a/packages/ui-tests/stories/canvas/Aggregate.stories.tsx +++ b/packages/ui-tests/stories/canvas/Aggregate.stories.tsx @@ -33,7 +33,7 @@ const selectedNode: CanvasNode = { previousNode: undefined, label: 'test', getId: () => 'aggregate-6839', - getTitle: () => 'Aggregate', + getNodeTitle: () => 'Aggregate', getOmitFormFields: () => [], getComponentSchema: () => { return { diff --git a/packages/ui-tests/stories/canvas/CanvasSideBar.stories.tsx b/packages/ui-tests/stories/canvas/CanvasSideBar.stories.tsx index 0df0a1807..0bb6c0484 100644 --- a/packages/ui-tests/stories/canvas/CanvasSideBar.stories.tsx +++ b/packages/ui-tests/stories/canvas/CanvasSideBar.stories.tsx @@ -35,7 +35,7 @@ const selectedNode: CanvasNode = { previousNode: undefined, label: 'test', getId: () => 'log-sink-6839', - getTitle: () => 'My Node', + getNodeTitle: () => 'My Node', getOmitFormFields: () => [], getComponentSchema: () => { return { @@ -77,7 +77,7 @@ const unknownSelectedNode: CanvasNode = { icon: NodeIconResolver.getIcon(''), } as IVisualizationNodeData, getId: () => 'test', - getTitle: () => 'My Node', + getNodeTitle: () => 'My Node', getOmitFormFields: () => [], getComponentSchema: () => { return { diff --git a/packages/ui-tests/stories/metadataEditor/DataformatEditor.stories.tsx b/packages/ui-tests/stories/metadataEditor/DataformatEditor.stories.tsx index 3f042418b..49959a60a 100644 --- a/packages/ui-tests/stories/metadataEditor/DataformatEditor.stories.tsx +++ b/packages/ui-tests/stories/metadataEditor/DataformatEditor.stories.tsx @@ -36,7 +36,7 @@ const mockNode: CanvasNode = { type: 'node', data: { vizNode: { - getTitle: () => 'My Node', + getNodeTitle: () => 'My Node', getComponentSchema: () => visualComponentSchema, updateModel: (_value: unknown) => {}, } as IVisualizationNode, diff --git a/packages/ui-tests/stories/metadataEditor/ExpressionEditor.stories.tsx b/packages/ui-tests/stories/metadataEditor/ExpressionEditor.stories.tsx index d994f81a6..8a57ff2ee 100644 --- a/packages/ui-tests/stories/metadataEditor/ExpressionEditor.stories.tsx +++ b/packages/ui-tests/stories/metadataEditor/ExpressionEditor.stories.tsx @@ -38,7 +38,7 @@ const mockNode: CanvasNode = { type: 'node', data: { vizNode: { - getTitle: () => 'My Node', + getNodeTitle: () => 'My Node', getComponentSchema: () => visualComponentSchema, } as IVisualizationNode, }, diff --git a/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.test.tsx b/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.test.tsx index 81f73fcc2..6fc9a871a 100644 --- a/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.test.tsx +++ b/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.test.tsx @@ -23,7 +23,6 @@ import { import { EntitiesContext, EntitiesProvider } from '../../../../providers/entities.provider'; import { camelRouteJson, kameletJson } from '../../../../stubs'; import { getFirstCatalogMap } from '../../../../stubs/test-load-catalog'; -import { ROOT_PATH } from '../../../../utils'; import { CanvasNode } from '../canvas.models'; import { FlowService } from '../flow.service'; import { CanvasForm } from './CanvasForm'; @@ -77,7 +76,7 @@ describe('CanvasForm', () => { it('should render nothing if no schema is available', () => { const vizNode = createVisualizationNode('route', { - path: ROOT_PATH, + path: CamelRouteVisualEntity.ROOT_PATH, entity: new CamelRouteVisualEntity(camelRouteJson), isGroup: true, processorName: 'route', @@ -113,7 +112,7 @@ describe('CanvasForm', () => { }; const vizNode = createVisualizationNode('route', { - path: ROOT_PATH, + path: CamelRouteVisualEntity.ROOT_PATH, entity: new CamelRouteVisualEntity(camelRouteJson), isGroup: true, processorName: 'route', diff --git a/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.tsx b/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.tsx index 1b2c2cdb8..5f28d6e6a 100644 --- a/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.tsx +++ b/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.tsx @@ -18,7 +18,7 @@ export const CanvasForm: FunctionComponent = ({ selectedNode, o const { visualFlowsApi } = useContext(VisibleFlowsContext)!; const flowIdRef = useRef(undefined); const vizNode = selectedNode.data?.vizNode; - const title = vizNode?.getTitle(); + const title = vizNode?.getNodeTitle(); /** Store the flow's initial Id */ useEffect(() => { diff --git a/packages/ui/src/components/Visualization/Canvas/Form/__snapshots__/CanvasForm.test.tsx.snap b/packages/ui/src/components/Visualization/Canvas/Form/__snapshots__/CanvasForm.test.tsx.snap index 03cfcc1df..d41c19590 100644 --- a/packages/ui/src/components/Visualization/Canvas/Form/__snapshots__/CanvasForm.test.tsx.snap +++ b/packages/ui/src/components/Visualization/Canvas/Form/__snapshots__/CanvasForm.test.tsx.snap @@ -32,7 +32,7 @@ exports[`CanvasForm should render 1`] = ` data-ouia-component-type="PF5/Title" data-ouia-safe="true" > - log + Logger
- route + Route
- route + Route
{ }); expect(mockDeleteModalContext.actionConfirmation).toHaveBeenCalledWith({ - title: "Do you want to delete the 'route-1234' route?", + title: "Do you want to delete the 'route-1234' Route?", text: 'All steps will be lost.', }); }); diff --git a/packages/ui/src/components/Visualization/ContextToolbar/Flows/FlowsList.tsx b/packages/ui/src/components/Visualization/ContextToolbar/Flows/FlowsList.tsx index 77479254c..c497c6d89 100644 --- a/packages/ui/src/components/Visualization/ContextToolbar/Flows/FlowsList.tsx +++ b/packages/ui/src/components/Visualization/ContextToolbar/Flows/FlowsList.tsx @@ -144,7 +144,7 @@ export const FlowsList: FunctionComponent = (props) => { "Do you want to delete the '" + flow.toVizNode().getId() + "' " + - flow.toVizNode().getTitle() + + flow.toVizNode().getNodeTitle() + '?', text: 'All steps will be lost.', }); diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx index 8e2cb2fef..e320e48fe 100644 --- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx @@ -44,7 +44,7 @@ describe('ItemDeleteGroup', () => { fireEvent.click(wrapper.getByText('Delete')); expect(mockDeleteModalContext.actionConfirmation).toHaveBeenCalledWith({ - title: "Do you want to delete the 'undefined' test?", + title: "Do you want to delete the 'undefined' test-1234?", text: 'All steps will be lost.', }); }); diff --git a/packages/ui/src/components/Visualization/Custom/hooks/delete-group.hook.tsx b/packages/ui/src/components/Visualization/Custom/hooks/delete-group.hook.tsx index c86152a2f..806660c56 100644 --- a/packages/ui/src/components/Visualization/Custom/hooks/delete-group.hook.tsx +++ b/packages/ui/src/components/Visualization/Custom/hooks/delete-group.hook.tsx @@ -24,7 +24,7 @@ export const useDeleteGroup = (vizNode: IVisualizationNode) => { const buttonOptions = modalCustoms.length > 0 ? modalCustoms[0].buttonOptions : undefined; /** Open delete confirm modal, get the confirmation */ const modalAnswer = await deleteModalContext?.actionConfirmation({ - title: "Do you want to delete the '" + vizNode.getId() + "' " + vizNode.getTitle() + '?', + title: "Do you want to delete the '" + vizNode.getId() + "' " + vizNode.getNodeTitle() + '?', text: 'All steps will be lost.', additionalModalText, buttonOptions, diff --git a/packages/ui/src/models/camel/kamelet-resource.ts b/packages/ui/src/models/camel/kamelet-resource.ts index 62299d808..93989136e 100644 --- a/packages/ui/src/models/camel/kamelet-resource.ts +++ b/packages/ui/src/models/camel/kamelet-resource.ts @@ -1,5 +1,5 @@ -import { set } from 'lodash'; import { TileFilter } from '../../components/Catalog/Catalog.models'; +import { setValue } from '../../utils'; import { IKameletDefinition } from '../kamelets-catalog'; import { AddStepMode } from '../visualization/base-visual-entity'; import { CamelComponentFilterService } from '../visualization/flows/support/camel-component-filter.service'; @@ -61,9 +61,9 @@ export class KameletResource extends CamelKResource implements RouteTemplateBean * and this way the kamelet definition is updated when the user interacts with * 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.beans', this.beans?.parent.beans); + setValue(this.resource, 'metadata.name', this.flow.getId()); + setValue(this.resource, 'spec.template.from', this.flow.entityDef.template.from); + setValue(this.resource, 'spec.template.beans', this.beans?.parent.beans); return this.resource as IKameletDefinition; } diff --git a/packages/ui/src/models/visualization/base-visual-entity.ts b/packages/ui/src/models/visualization/base-visual-entity.ts index f93758c10..675259904 100644 --- a/packages/ui/src/models/visualization/base-visual-entity.ts +++ b/packages/ui/src/models/visualization/base-visual-entity.ts @@ -24,6 +24,9 @@ export interface BaseVisualCamelEntity extends BaseCamelEntity { /** Given a path, get the component label */ getNodeLabel: (path?: string, labelType?: NodeLabelType) => string; + /** Given a path, get the component title from the catalog */ + getNodeTitle: (path?: string) => string; + /** Given a path, get the component tooltip content */ getTooltipContent: (path?: string) => string; @@ -83,11 +86,8 @@ export interface IVisualizationNode implements Bas return label; } + getNodeTitle(path?: string): string { + if (!path) return ''; + if (path === this.getRootPath()) { + return camelCaseToSpaces(this.getRootPath(), { capitalize: true }); + } + + const componentModel = getValue(this.entityDef, path); + + const title = CamelComponentSchemaService.getNodeTitle( + CamelComponentSchemaService.getCamelComponentLookup(path, componentModel), + ); + + return title; + } + getTooltipContent(path?: string): string { if (!path) return ''; const componentModel = getValue(this.entityDef, path); diff --git a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.test.ts index 4bfc2d2e0..2a81f0365 100644 --- a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.test.ts @@ -174,7 +174,7 @@ describe('CamelErrorHandlerVisualEntity', () => { const entity = new CamelErrorHandlerVisualEntity(errorHandlerDef); const vizNode = entity.toVizNode(); - expect(vizNode.getTitle()).toEqual('Error Handler'); + expect(vizNode.getNodeTitle()).toEqual('Error Handler'); }); it('should serialize the errorHandler definition', () => { diff --git a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts index c476474e5..5272686e7 100644 --- a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts @@ -78,6 +78,10 @@ export class CamelErrorHandlerVisualEntity implements BaseVisualCamelEntity { return errorHandlerId; } + getNodeTitle(): string { + return 'Error Handler'; + } + getTooltipContent(): string { return 'errorHandler'; } @@ -139,7 +143,6 @@ export class CamelErrorHandlerVisualEntity implements BaseVisualCamelEntity { errorHandlerGroupNode.data.entity = this; errorHandlerGroupNode.data.isGroup = true; errorHandlerGroupNode.data.icon = NodeIconResolver.getIcon(this.type, NodeIconType.VisualEntity); - errorHandlerGroupNode.setTitle('Error Handler'); return errorHandlerGroupNode; } diff --git a/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.test.ts index bfe2758a5..e6c01c0d7 100644 --- a/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.test.ts @@ -206,7 +206,7 @@ describe('CamelRestConfigurationVisualEntity', () => { const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef); const vizNode = entity.toVizNode(); - expect(vizNode.getTitle()).toEqual('Rest Configuration'); + expect(vizNode.getNodeTitle()).toEqual('Rest Configuration'); }); }); diff --git a/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts index c484791d8..f70cd2e53 100644 --- a/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts @@ -61,6 +61,10 @@ export class CamelRestConfigurationVisualEntity implements BaseVisualCamelEntity return 'restConfiguration'; } + getNodeTitle(): string { + return 'Rest Configuration'; + } + getTooltipContent(): string { return 'restConfiguration'; } @@ -131,7 +135,6 @@ export class CamelRestConfigurationVisualEntity implements BaseVisualCamelEntity restConfigurationGroupNode.data.entity = this; restConfigurationGroupNode.data.isGroup = true; restConfigurationGroupNode.data.icon = NodeIconResolver.getIcon(this.type, NodeIconType.VisualEntity); - restConfigurationGroupNode.setTitle('Rest Configuration'); return restConfigurationGroupNode; } diff --git a/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.test.ts index 3b52def53..0aa00ec4d 100644 --- a/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.test.ts @@ -215,7 +215,7 @@ describe('CamelRouteConfigurationVisualEntity', () => { const entity = new CamelRouteConfigurationVisualEntity(routeConfigurationDef); const vizNode = entity.toVizNode(); - expect(vizNode.getTitle()).toEqual('Route Configuration'); + expect(vizNode.getNodeTitle()).toEqual('Route Configuration'); }); }); diff --git a/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts index bc5603d12..3eb324780 100644 --- a/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts @@ -150,7 +150,6 @@ export class CamelRouteConfigurationVisualEntity icon: NodeIconResolver.getIcon(this.type, NodeIconType.VisualEntity), processorName: this.getRootPath(), }); - routeConfigurationGroupNode.setTitle('Route Configuration'); CamelComponentSchemaService.getProcessorStepsProperties(this.getRootPath() as keyof ProcessorDefinition).forEach( (stepsProperty) => { diff --git a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.test.ts index e6f9bbb7f..b9a9dd9c6 100644 --- a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.test.ts @@ -2,7 +2,6 @@ import { ProcessorDefinition, RouteDefinition } from '@kaoto/camel-catalog/types import { cloneDeep } from 'lodash'; import { camelFromJson } from '../../../stubs/camel-from'; import { camelRouteJson } from '../../../stubs/camel-route'; -import { ROOT_PATH } from '../../../utils'; import { EntityType } from '../../camel/entities/base-entity'; import { KaotoSchemaDefinition } from '../../kaoto-schema'; import { NodeLabelType } from '../../settings/settings.model'; @@ -197,7 +196,7 @@ describe('Camel Route', () => { }); describe('toVizNode', () => { - it(`should return the group viz node and set the initial path to '${ROOT_PATH}'`, () => { + it(`should return the group viz node and set the initial path to '${CamelRouteVisualEntity.ROOT_PATH}'`, () => { const vizNode = camelEntity.toVizNode(); expect(vizNode).toBeDefined(); diff --git a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.test.ts index aad0444cd..8a2043600 100644 --- a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.test.ts @@ -161,7 +161,7 @@ describe('KameletVisualEntity', () => { const kamelet = new KameletVisualEntity(kameletDef); const vizNode = kamelet.toVizNode(); - expect(vizNode.getTitle()).toEqual('Kamelet'); + expect(vizNode.getNodeTitle()).toEqual('Kamelet'); }); }); }); diff --git a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts index bcec30360..702c091d9 100644 --- a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts @@ -6,11 +6,11 @@ import { EntityType } from '../../camel/entities'; import { CatalogKind } from '../../catalog-kind'; import { IKameletDefinition } from '../../kamelets-catalog'; import { KaotoSchemaDefinition } from '../../kaoto-schema'; -import { AddStepMode, IVisualizationNode, IVisualizationNodeData, VisualComponentSchema } from '../base-visual-entity'; +import { NodeLabelType } from '../../settings'; +import { AddStepMode, IVisualizationNodeData, VisualComponentSchema } from '../base-visual-entity'; import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; import { CamelCatalogService } from './camel-catalog.service'; import { CamelComponentDefaultService } from './support/camel-component-default.service'; -import { NodeLabelType } from '../../settings'; export class KameletVisualEntity extends AbstractCamelVisualEntity<{ id: string; template: { from: FromDefinition } }> { id: string; @@ -53,6 +53,14 @@ export class KameletVisualEntity extends AbstractCamelVisualEntity<{ id: string; return super.getNodeLabel(path, labelType); } + getNodeTitle(path?: string): string { + if (path === this.getRootPath()) { + return 'Kamelet'; + } + + return super.getNodeTitle(path); + } + toJSON(): { from: FromDefinition } { return { from: this.entityDef.template.from }; } @@ -114,13 +122,6 @@ export class KameletVisualEntity extends AbstractCamelVisualEntity<{ id: string; super.removeStep(path); } - toVizNode(): IVisualizationNode { - const vizNode = super.toVizNode(); - vizNode.setTitle('Kamelet'); - - return vizNode; - } - protected getRootUri(): string | undefined { return this.kamelet.spec.template.from?.uri; } diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.ts index 334a75187..eb19525df 100644 --- a/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.ts +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.ts @@ -21,10 +21,7 @@ export class DataMapperNodeMapper extends BaseNodeMapper { isGroup: false, }; - const vizNode = createVisualizationNode(processorName, data); - vizNode.setTitle('Kaoto data mapper'); - - return vizNode; + return createVisualizationNode(processorName, data); } static isDataMapperNode(stepDefinition: Step): boolean { diff --git a/packages/ui/src/models/visualization/flows/pipe-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/pipe-visual-entity.test.ts index 64a4b6d0f..0f0c4fca3 100644 --- a/packages/ui/src/models/visualization/flows/pipe-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/pipe-visual-entity.test.ts @@ -108,14 +108,14 @@ describe('Pipe', () => { pipeVisualEntity.removeStep('source'); expect(pipeVisualEntity.toJSON()).not.toEqual(pipeJson.spec); - expect(pipeVisualEntity.pipe.spec?.source).toEqual({}); + expect(pipeVisualEntity.pipe.spec?.source).toBeUndefined(); }); it('should remove the `sink` step', () => { pipeVisualEntity.removeStep('sink'); expect(pipeVisualEntity.toJSON()).not.toEqual(pipeJson.spec); - expect(pipeVisualEntity.pipe.spec?.sink).toEqual({}); + expect(pipeVisualEntity.pipe.spec?.sink).toBeUndefined(); }); it('should remove the `steps.0` step', () => { @@ -165,7 +165,7 @@ describe('Pipe', () => { const vizNode = pipeVisualEntity.toVizNode(); expect(vizNode).toBeDefined(); - expect(vizNode.data.path).toEqual('#'); + expect(vizNode.data.path).toEqual(PipeVisualEntity.ROOT_PATH); }); it('should use the uri as the node label', () => { @@ -177,19 +177,19 @@ describe('Pipe', () => { it('should set the title to `Pipe`', () => { const vizNode = pipeVisualEntity.toVizNode(); - expect(vizNode.getTitle()).toEqual('Pipe'); + expect(vizNode.getNodeTitle()).toEqual('Pipe'); }); - it('should set the title to children nodes', () => { + it('should get the titles from children nodes', () => { const vizNode = pipeVisualEntity.toVizNode(); const sourceNode = vizNode.getChildren()![0]; const stepNode = vizNode.getChildren()![1]; const sinkNode = vizNode.getChildren()![2]; - expect(sourceNode.getTitle()).toEqual('webhook-source'); - expect(stepNode.getTitle()).toEqual('delay-action'); - expect(sinkNode.getTitle()).toEqual('log-sink'); + expect(sourceNode.getNodeTitle()).toEqual('Webhook Source'); + expect(stepNode.getNodeTitle()).toEqual('Delay Action'); + expect(sinkNode.getNodeTitle()).toEqual('Log Sink'); }); it('should set the node labels when the uri is not available', () => { @@ -205,7 +205,7 @@ describe('Pipe', () => { it('should populate the viz node chain with the steps', () => { const vizNode = pipeVisualEntity.toVizNode(); - expect(vizNode.data.path).toEqual('#'); + expect(vizNode.data.path).toEqual(PipeVisualEntity.ROOT_PATH); expect(vizNode.getNodeLabel()).toEqual('webhook-binding'); expect(vizNode.getPreviousNode()).toBeUndefined(); expect(vizNode.getNextNode()).toBeUndefined(); diff --git a/packages/ui/src/models/visualization/flows/pipe-visual-entity.ts b/packages/ui/src/models/visualization/flows/pipe-visual-entity.ts index 29848aa49..e45a40910 100644 --- a/packages/ui/src/models/visualization/flows/pipe-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/pipe-visual-entity.ts @@ -1,12 +1,10 @@ import { Pipe } from '@kaoto/camel-catalog/types'; -import { get, set } from 'lodash'; import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; import { SchemaService } from '../../../components/Form/schema.service'; import { getArrayProperty, NodeIconResolver, NodeIconType, - ROOT_PATH, getCustomSchemaFromPipe, updatePipeFromCustomSchema, setValue, @@ -33,6 +31,7 @@ import { CatalogKind } from '../../catalog-kind'; export class PipeVisualEntity implements BaseVisualCamelEntity { id: string; readonly type: EntityType = EntityType.Pipe; + static readonly ROOT_PATH = 'pipe'; constructor(public pipe: Pipe) { this.id = (pipe.metadata?.name as string) ?? getCamelRandomId('pipe'); @@ -47,7 +46,7 @@ export class PipeVisualEntity implements BaseVisualCamelEntity { } getRootPath(): string { - return ROOT_PATH; + return PipeVisualEntity.ROOT_PATH; } /** Internal API methods */ @@ -67,14 +66,25 @@ export class PipeVisualEntity implements BaseVisualCamelEntity { return this.id; } - const stepModel = get(this.pipe.spec, path) as PipeStep; + const stepModel: PipeStep = getValue(this.pipe.spec, path); return KameletSchemaService.getNodeLabel(stepModel, path); } + getNodeTitle(path?: string): string { + if (!path) return ''; + + if (path === this.getRootPath()) { + return 'Pipe'; + } + + const stepModel: PipeStep = getValue(this.pipe.spec, path); + return KameletSchemaService.getNodeTitle(stepModel); + } + getTooltipContent(path?: string): string { if (!path) return ''; - const stepModel = get(this.pipe.spec, path) as PipeStep; + const stepModel: PipeStep = getValue(this.pipe.spec, path); return KameletSchemaService.getTooltipContent(stepModel, path); } @@ -87,7 +97,7 @@ export class PipeVisualEntity implements BaseVisualCamelEntity { }; } - const stepModel = get(this.pipe.spec, path) as PipeStep; + const stepModel: PipeStep = getValue(this.pipe.spec, path); return KameletSchemaService.getVisualComponentSchema(stepModel); } @@ -141,12 +151,7 @@ export class PipeVisualEntity implements BaseVisualCamelEntity { /** Replace an existing Kamelet */ if (options.mode === AddStepMode.ReplaceStep) { - if (path === 'source' || path === 'sink') { - set(this.pipe.spec!, path, step); - } else { - set(this.pipe.spec!, path, step); - } - + setValue(this.pipe.spec, path, step); return; } @@ -169,7 +174,7 @@ export class PipeVisualEntity implements BaseVisualCamelEntity { * If the path is `source` or `sink`, we can remove it directly */ if (path === 'source' || path === 'sink') { - set(this.pipe.spec!, path, {}); + setValue(this.pipe.spec, path, {}); return; } const pathArray = path.split('.'); @@ -181,7 +186,7 @@ export class PipeVisualEntity implements BaseVisualCamelEntity { * f.i. from.steps.1.choice.when.0 * last: 0 */ - const array = get(this.pipe.spec, pathArray.slice(0, -1), []); + const array = getArrayProperty(this.pipe.spec!, pathArray.slice(0, -1).join('.')); if (Number.isInteger(Number(last)) && Array.isArray(array)) { array.splice(Number(last), 1); } @@ -221,7 +226,6 @@ export class PipeVisualEntity implements BaseVisualCamelEntity { const stepNodes = this.getVizNodesFromSteps(this.pipe.spec!.steps); const sinkNode = this.getVizNodeFromStep(this.pipe.spec!.sink, 'sink'); - pipeGroupNode.setTitle('Pipe'); pipeGroupNode.addChild(sourceNode); stepNodes.forEach((stepNode) => pipeGroupNode.addChild(stepNode)); pipeGroupNode.addChild(sinkNode); @@ -264,10 +268,7 @@ export class PipeVisualEntity implements BaseVisualCamelEntity { icon, }; - const vizNode = createVisualizationNode(step?.ref?.name ?? path, data); - vizNode.setTitle(kameletDefinition?.metadata.name ?? ''); - - return vizNode; + return createVisualizationNode(step?.ref?.name ?? path, data); } private getVizNodesFromSteps(steps: PipeStep[] = []): IVisualizationNode[] { diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts index 5c146520a..25b9cf0c2 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts @@ -438,6 +438,23 @@ describe('CamelComponentSchemaService', () => { }); }); + describe('getNodeTitle', () => { + const specs: [ICamelElementLookupResult, string][] = [ + [{ processorName: 'route' } as unknown as ICamelElementLookupResult, 'Route'], + [{ processorName: 'from' } as unknown as ICamelElementLookupResult, 'From'], + [{ processorName: 'tokenizer' }, 'Specialized tokenizer for AI applications'], + [{ processorName: 'to', componentName: 'timer' }, 'Timer'], + [{ processorName: 'to', componentName: 'kamelet:chuck-norris-source' }, 'Chuck Norris Source'], + [{ processorName: 'to', componentName: 'kamelet:chuck-norris' }, 'Kamelet'], + ]; + + it.each(specs)(`should return the %s title`, (camelElementLookup, expected) => { + const result = CamelComponentSchemaService.getNodeTitle(camelElementLookup); + + expect(result).toEqual(expected); + }); + }); + describe('getTooltipContent', () => { it('should return the component schema description', () => { const camelElementLookup = { processorName: 'from' as keyof ProcessorDefinition, componentName: 'timer' }; 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 5edebd8ae..38f9fafd4 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 @@ -8,7 +8,6 @@ import { isDataMapperNode, isDefined, } from '../../../../utils'; -import { ICamelComponentDefinition } from '../../../camel-components-catalog'; import { CatalogKind } from '../../../catalog-kind'; import { IKameletDefinition } from '../../../kamelets-catalog'; import { KaotoSchemaDefinition } from '../../../kaoto-schema'; @@ -115,15 +114,32 @@ export class CamelComponentSchemaService { } } - static getTooltipContent(camelElementLookup: ICamelElementLookupResult): string { + static getNodeTitle(camelElementLookup: ICamelElementLookupResult): string { if (camelElementLookup.componentName !== undefined) { const catalogLookup = CamelCatalogService.getCatalogLookup(camelElementLookup.componentName); if (catalogLookup.catalogKind === CatalogKind.Component) { + return catalogLookup.definition?.component.title ?? camelElementLookup.componentName; + } + + if (catalogLookup.catalogKind === CatalogKind.Kamelet) { return ( - (catalogLookup.definition as unknown as ICamelComponentDefinition)?.component.description ?? + (catalogLookup.definition as unknown as IKameletDefinition)?.spec.definition.title ?? camelElementLookup.componentName ); } + } + + const catalogLookup = CamelCatalogService.getComponent(CatalogKind.Processor, camelElementLookup.processorName); + + return catalogLookup?.model.title ?? camelElementLookup.processorName; + } + + static getTooltipContent(camelElementLookup: ICamelElementLookupResult): string { + if (camelElementLookup.componentName !== undefined) { + const catalogLookup = CamelCatalogService.getCatalogLookup(camelElementLookup.componentName); + if (catalogLookup.catalogKind === CatalogKind.Component) { + return catalogLookup.definition?.component.description ?? camelElementLookup.componentName; + } if (catalogLookup.catalogKind === CatalogKind.Kamelet) { return ( diff --git a/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.ts b/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.ts index de29cf2ce..db0c29287 100644 --- a/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.ts +++ b/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.ts @@ -37,6 +37,12 @@ export class KameletSchemaService { return ''; } + static getNodeTitle(step?: PipeStep): string { + const kameletDefinition = this.getKameletDefinition(step); + + return kameletDefinition?.spec.definition.title ?? step?.ref?.name ?? ''; + } + static getTooltipContent(step: PipeStep, path: string): string { const schema = this.getKameletDefinition(step)?.propertiesSchema; if (schema?.description !== undefined) { diff --git a/packages/ui/src/models/visualization/visualization-node.test.ts b/packages/ui/src/models/visualization/visualization-node.test.ts index 8dfe649c2..bdf2e47a7 100644 --- a/packages/ui/src/models/visualization/visualization-node.test.ts +++ b/packages/ui/src/models/visualization/visualization-node.test.ts @@ -21,7 +21,7 @@ describe('VisualizationNode', () => { }); it('should create a node and set the ID as the title', () => { - expect(node.getTitle()).toEqual('test'); + expect(node.getNodeTitle()).toEqual('test-1234'); }); it('should return the base entity ID', () => { diff --git a/packages/ui/src/models/visualization/visualization-node.ts b/packages/ui/src/models/visualization/visualization-node.ts index 09031a174..9855250fe 100644 --- a/packages/ui/src/models/visualization/visualization-node.ts +++ b/packages/ui/src/models/visualization/visualization-node.ts @@ -15,10 +15,7 @@ export const createVisualizationNode = => { - const vizNode = new VisualizationNode(getCamelRandomId(id), data); - vizNode.setTitle(id); - - return vizNode; + return new VisualizationNode(getCamelRandomId(id), data); }; /** @@ -27,7 +24,6 @@ export const createVisualizationNode = implements IVisualizationNode { - private title = ''; private parentNode: IVisualizationNode | undefined = undefined; private previousNode: IVisualizationNode | undefined = undefined; private nextNode: IVisualizationNode | undefined = undefined; @@ -51,12 +47,8 @@ class VisualizationNode { + it('should convert camelCase to space separated string', () => { + expect(camelCaseToSpaces('camelCaseString')).toBe('camel Case String'); + }); + + it('should return an empty string if input is empty', () => { + expect(camelCaseToSpaces('')).toBe(''); + }); + + it('should handle single word strings', () => { + expect(camelCaseToSpaces('word')).toBe('word'); + }); + + it('should capitalize words if the capitalize option is true', () => { + expect(camelCaseToSpaces('camelCaseString', { capitalize: true })).toBe('Camel Case String'); + }); + + it('should not capitalize words if the capitalize option is false', () => { + expect(camelCaseToSpaces('camelCaseString', { capitalize: false })).toBe('camel Case String'); + }); + + it('should ignore strings with multiple uppercase letters', () => { + expect(camelCaseToSpaces('thisIsATestString')).toBe('this Is ATest String'); + }); + + it('should handle strings with no uppercase letters', () => { + expect(camelCaseToSpaces('teststring')).toBe('teststring'); + }); + + it('should handle strings with leading uppercase letters', () => { + expect(camelCaseToSpaces('TestString')).toBe('Test String'); + }); + + it('should handle strings with trailing uppercase letters', () => { + expect(camelCaseToSpaces('testStringA')).toBe('test String A'); + }); +}); diff --git a/packages/ui/src/utils/camel-case-to-space.ts b/packages/ui/src/utils/camel-case-to-space.ts new file mode 100644 index 000000000..aacc846db --- /dev/null +++ b/packages/ui/src/utils/camel-case-to-space.ts @@ -0,0 +1,18 @@ +/** + * Converts a camelCase string to a space separated string. + * @param str The camelCase string to convert. + * @returns The space-separated string. + */ +export function camelCaseToSpaces(str: string, options?: { capitalize: boolean }): string { + if (!str) { + return ''; + } + + let spacedString = str.replace(/([a-z])([A-Z])/g, '$1 $2'); + + if (options?.capitalize) { + spacedString = spacedString.substring(0, 1).toUpperCase() + spacedString.slice(1); + } + + return spacedString; +} diff --git a/packages/ui/src/utils/get-array-property.ts b/packages/ui/src/utils/get-array-property.ts index 10dd4eba5..7249b3e34 100644 --- a/packages/ui/src/utils/get-array-property.ts +++ b/packages/ui/src/utils/get-array-property.ts @@ -1,11 +1,12 @@ -import { get, set } from 'lodash'; +import { getValue } from './get-value'; +import { setValue } from './set-value'; export const getArrayProperty = (model: object, path: string): T[] => { - let stepsArray: T[] | undefined = get(model, path); + let stepsArray: T[] | undefined = getValue(model, path); if (!Array.isArray(stepsArray)) { - set(model, path, []); - stepsArray = get(model, path) as T[]; + setValue(model, path, []); + stepsArray = getValue(model, path) as T[]; } return stepsArray; diff --git a/packages/ui/src/utils/index.ts b/packages/ui/src/utils/index.ts index 7a05172ed..e2178ee3d 100644 --- a/packages/ui/src/utils/index.ts +++ b/packages/ui/src/utils/index.ts @@ -1,3 +1,4 @@ +export * from './camel-case-to-space'; export * from './camel-uri-helper'; export * from './catalog-schema-loader'; export * from './create-camel-properties-sorter'; diff --git a/packages/ui/src/utils/pipe-custom-schema.ts b/packages/ui/src/utils/pipe-custom-schema.ts index 8a6ada495..d9c60e2c1 100644 --- a/packages/ui/src/utils/pipe-custom-schema.ts +++ b/packages/ui/src/utils/pipe-custom-schema.ts @@ -1,7 +1,6 @@ import { Pipe } from '@kaoto/camel-catalog/types'; import { getValue } from './get-value'; import { setValue } from './set-value'; -import { set } from 'lodash'; export const getCustomSchemaFromPipe = (pipe: Pipe) => { const name: string = getValue(pipe, 'metadata.name', ''); @@ -19,11 +18,11 @@ export const getCustomSchemaFromPipe = (pipe: Pipe) => { export const updatePipeFromCustomSchema = (pipe: Pipe, value: Record): void => { // Ensure 'labels' and 'annotations' are defined in 'value' - if (getValue(value, 'labels') === undefined) { - set(value, 'labels', {}); + if (value && getValue(value, 'labels') === undefined) { + value.labels = {}; } - if (getValue(value, 'annotations') === undefined) { - set(value, 'annotations', {}); + if (value && getValue(value, 'annotations') === undefined) { + value.annotations = {}; } const previousName: string = getValue(pipe, 'metadata.name'); const newName: string = getValue(value, 'name');