diff --git a/packages/ui/src/components/Visualization/Custom/CustomNode.tsx b/packages/ui/src/components/Visualization/Custom/CustomNode.tsx index 6e68f2498..fa69f7bac 100644 --- a/packages/ui/src/components/Visualization/Custom/CustomNode.tsx +++ b/packages/ui/src/components/Visualization/Custom/CustomNode.tsx @@ -6,6 +6,7 @@ import { withSelection, WithSelectionProps, } from '@patternfly/react-topology'; +import { Tooltip } from '@patternfly/react-core'; import { FunctionComponent } from 'react'; import { AddStepMode } from '../../../models/visualization/base-visual-entity'; import { CanvasDefaults } from '../Canvas/canvas.defaults'; @@ -24,6 +25,7 @@ const noopFn = () => {}; const CustomNode: FunctionComponent = ({ element, ...rest }) => { const vizNode = element.getData()?.vizNode; const label = vizNode?.getNodeLabel(); + const tooltipContent = vizNode?.getTooltipContent(); const statusDecoratorTooltip = vizNode?.getNodeValidationText(); const nodeStatus = !statusDecoratorTooltip ? NodeStatus.default : NodeStatus.warning; @@ -45,13 +47,15 @@ const CustomNode: FunctionComponent = ({ element, ...rest }) => width={CanvasDefaults.DEFAULT_NODE_DIAMETER} height={CanvasDefaults.DEFAULT_NODE_DIAMETER} > -
- -
+ +
+ +
+
diff --git a/packages/ui/src/models/visualization/base-visual-entity.ts b/packages/ui/src/models/visualization/base-visual-entity.ts index 1825aed55..4cf2bc0d6 100644 --- a/packages/ui/src/models/visualization/base-visual-entity.ts +++ b/packages/ui/src/models/visualization/base-visual-entity.ts @@ -20,6 +20,9 @@ export interface BaseVisualCamelEntity extends BaseCamelEntity { /** Given a path, get the component label */ getNodeLabel: (path?: string) => string; + /** Given a path, get the component tooltip content */ + getTooltipContent: (path?: string) => string; + /** Given a path, get the component type and definition */ getComponentSchema: (path?: string) => VisualComponentSchema | undefined; @@ -71,6 +74,9 @@ export interface IVisualizationNode { ); }); + describe('getTooltipContent', () => { + it('should return the component schema description', () => { + const camelElementLookup = { processorName: 'from' as keyof ProcessorDefinition, componentName: 'timer' }; + const actualContent = CamelComponentSchemaService.getTooltipContent(camelElementLookup); + + expect(actualContent).toEqual('Generate messages in specified intervals using java.util.Timer.'); + }); + + it('should return the component name', () => { + const camelElementLookup = { processorName: 'from' as keyof ProcessorDefinition, componentName: 'xyz' }; + const actualContent = CamelComponentSchemaService.getTooltipContent(camelElementLookup); + + expect(actualContent).toEqual('xyz'); + }); + + it('should return the kamelet schema description', () => { + const camelElementLookup = { + processorName: 'to' as keyof ProcessorDefinition, + componentName: 'kamelet:avro-deserialize-action', + }; + const actualContent = CamelComponentSchemaService.getTooltipContent(camelElementLookup); + + expect(actualContent).toEqual('Deserialize payload to Avro'); + }); + + it('should return the kamelet name', () => { + const camelElementLookup = { + processorName: 'to' as keyof ProcessorDefinition, + componentName: 'kamelet:xyz', + }; + const actualContent = CamelComponentSchemaService.getTooltipContent(camelElementLookup); + + expect(actualContent).toEqual('kamelet:xyz'); + }); + + it('should return the processor schema description', () => { + const path = 'from.steps.0.aggregate'; + const definition = { id: 'aggregate-2202' }; + const camelElementLookup = CamelComponentSchemaService.getCamelComponentLookup(path, definition); + const actualContent = CamelComponentSchemaService.getTooltipContent(camelElementLookup); + + expect(actualContent).toEqual('Aggregates many messages into a single message'); + }); + + it('should return the processor name', () => { + const path = 'from.steps.0.xyz'; + const definition = { id: 'xyz-2202' }; + const camelElementLookup = CamelComponentSchemaService.getCamelComponentLookup(path, definition); + const actualContent = CamelComponentSchemaService.getTooltipContent(camelElementLookup); + + expect(actualContent).toEqual('xyz'); + }); + }); + describe('canHavePreviousStep', () => { it.each([ ['from', false], 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 da1a78f79..48b4d9f67 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 @@ -6,6 +6,8 @@ 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 { IKameletDefinition } from '../../../kamelets-catalog'; +import { ICamelComponentDefinition } from '../../../camel-components-catalog'; export class CamelComponentSchemaService { static DISABLED_SIBLING_STEPS = ['from', 'when', 'otherwise', 'doCatch', 'doFinally']; @@ -70,6 +72,32 @@ export class CamelComponentSchemaService { } } + static getTooltipContent(camelElementLookup: ICamelElementLookupResult): string { + if (camelElementLookup.componentName !== undefined) { + const catalogLookup = CamelCatalogService.getCatalogLookup(camelElementLookup.componentName); + if (catalogLookup.catalogKind === CatalogKind.Component) { + return ( + (catalogLookup.definition as unknown as ICamelComponentDefinition)?.component.description ?? + camelElementLookup.componentName + ); + } + + if (catalogLookup.catalogKind === CatalogKind.Kamelet) { + return ( + (catalogLookup.definition as unknown as IKameletDefinition)?.spec.definition.description ?? + camelElementLookup.componentName + ); + } + } + + const schema = this.getSchema(camelElementLookup); + if (schema.description !== undefined) { + return schema.description; + } + + return camelElementLookup.processorName; + } + static canHavePreviousStep(processorName: keyof ProcessorDefinition): boolean { return !this.DISABLED_SIBLING_STEPS.includes(processorName); } diff --git a/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.test.ts b/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.test.ts index bd761edcf..d03d006e8 100644 --- a/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.test.ts +++ b/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.test.ts @@ -110,4 +110,38 @@ describe('KameletSchemaService', () => { expect(result).toEqual('sink: Unknown'); }); }); + + describe('getTooltipContent', () => { + it('should return the Kamelet description as the tooltip content', () => { + const step = { + ref: { + kind: 'Kamelet', + apiVersion: 'camel.apache.org/v1', + name: 'beer-source', + }, + }; + const result = KameletSchemaService.getTooltipContent(step, 'source'); + + expect(result).toEqual('Produces periodic events about beers!'); + }); + + it('should return the Kamelet name as the tooltip content', () => { + const step = { + ref: { + kind: 'Kamelet', + apiVersion: 'camel.apache.org/v1', + name: 'xyz-source', + }, + }; + const result = KameletSchemaService.getTooltipContent(step, 'source'); + + expect(result).toEqual('xyz-source'); + }); + + it('should return the Kamelet path as the tooltip content', () => { + const result = KameletSchemaService.getTooltipContent(undefined, 'sink'); + + expect(result).toEqual('sink: Unknown'); + }); + }); }); 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 56893f47d..60e5f2f79 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 @@ -26,8 +26,16 @@ export class KameletSchemaService { return CamelCatalogService.getComponent(CatalogKind.Kamelet, stepName); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any static getNodeLabel(step: PipeStep, path: string): string { return step?.ref?.name ?? `${path}: Unknown`; } + + static getTooltipContent(step: PipeStep, path: string): string { + const schema = this.getKameletDefinition(step)?.propertiesSchema; + if (schema?.description !== undefined) { + return schema.description; + } + + return step?.ref?.name ?? `${path}: Unknown`; + } } diff --git a/packages/ui/src/models/visualization/visualization-node.test.ts b/packages/ui/src/models/visualization/visualization-node.test.ts index a6eb4cff2..aa9e65a4a 100644 --- a/packages/ui/src/models/visualization/visualization-node.test.ts +++ b/packages/ui/src/models/visualization/visualization-node.test.ts @@ -55,6 +55,28 @@ describe('VisualizationNode', () => { }); }); + describe('getTooltipContent', () => { + it('should return the tootltip content from the underlying BaseVisualCamelEntity', () => { + const getTooltipContentSpy = jest.fn().mockReturnValue('test-description'); + const visualEntity = { + getTooltipContent: getTooltipContentSpy, + } as unknown as BaseVisualCamelEntity; + + node = createVisualizationNode('test', { path: 'test-path', entity: visualEntity }); + const content = node.getTooltipContent(); + + expect(getTooltipContentSpy).toHaveBeenCalledWith(node.data.path); + expect(content).toEqual('test-description'); + }); + + it('should return the id when the underlying BaseVisualCamelEntity is not defined', () => { + node = createVisualizationNode('test', {}); + const content = node.getTooltipContent(); + + expect(content).toEqual(node.id); + }); + }); + it('should return the component schema from the root node', () => { /** Arrange */ const getComponentSchemaSpy = jest.fn(); diff --git a/packages/ui/src/models/visualization/visualization-node.ts b/packages/ui/src/models/visualization/visualization-node.ts index 13f8cc442..0e475fa7c 100644 --- a/packages/ui/src/models/visualization/visualization-node.ts +++ b/packages/ui/src/models/visualization/visualization-node.ts @@ -49,6 +49,10 @@ class VisualizationNode