Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show tooltips for each step on the canvas #629

Merged
merged 12 commits into from
Jan 25, 2024
18 changes: 11 additions & 7 deletions packages/ui/src/components/Visualization/Custom/CustomNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
withSelection,
WithSelectionProps,
} from '@patternfly/react-topology';
import { Tooltip } from '@patternfly/react-core';
import { FunctionComponent, useState } from 'react';
import { AddStepMode } from '../../../models/visualization/base-visual-entity';
import { CanvasDefaults } from '../Canvas/canvas.defaults';
Expand All @@ -23,6 +24,7 @@ interface CustomNodeProps extends WithSelectionProps {
const CustomNode: FunctionComponent<CustomNodeProps> = ({ element, ...rest }) => {
const vizNode = element.getData()?.vizNode;
const label = vizNode?.getNodeLabel();
const tooltipContent = vizNode?.getTooltipContent();
const [statusDecoratorTooltip, setStatusDecoratorTooltip] = useState(vizNode?.getNodeStatusMessage());
vizNode?.addNodeStatusListener((status: NodeStatus, message: string | undefined) => {
element.setNodeStatus(status);
Expand All @@ -46,13 +48,15 @@ const CustomNode: FunctionComponent<CustomNodeProps> = ({ element, ...rest }) =>
width={CanvasDefaults.DEFAULT_NODE_DIAMETER}
height={CanvasDefaults.DEFAULT_NODE_DIAMETER}
>
<div className="custom-node__image">
<img
src={vizNode?.data.icon}
width={CanvasDefaults.DEFAULT_NODE_DIAMETER * 0.7}
height={CanvasDefaults.DEFAULT_NODE_DIAMETER * 0.7}
/>
</div>
<Tooltip content={tooltipContent}>
<div className="custom-node__image">
<img
src={vizNode?.data.icon}
width={CanvasDefaults.DEFAULT_NODE_DIAMETER * 0.7}
height={CanvasDefaults.DEFAULT_NODE_DIAMETER * 0.7}
/>
</div>
</Tooltip>
</foreignObject>
</g>
</DefaultNode>
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/src/models/visualization/base-visual-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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;

Expand Down Expand Up @@ -68,6 +71,9 @@ export interface IVisualizationNode<T extends IVisualizationNodeData = IVisualiz
/** This method returns the label to be used by the canvas nodes */
getNodeLabel(): string;

/** This method returns the tooltip content to be used by the canvas nodes */
getTooltipContent(): string;

addBaseEntityStep(definedComponent: DefinedComponent, mode: AddStepMode, targetProperty?: string): void;

getNodeInteraction(): NodeInteraction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity
return label;
}

getTooltipContent(path?: string): string {
if (!path) return '';
const componentModel = get(this.route, path);

const content = CamelComponentSchemaService.getTooltipContent(
CamelComponentSchemaService.getCamelComponentLookup(path, componentModel),
);

return content;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we please create an issue for adding a test case for this? There are similar test cases that you could get inspiration from.

getComponentSchema(path?: string): VisualComponentSchema | undefined {
if (!path) return undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export class PipeVisualEntity implements BaseVisualCamelEntity {
return KameletSchemaService.getNodeLabel(stepModel, path);
}

getTooltipContent(path?: string): string {
if (!path) return '';

const stepModel = get(this.spec, path) as PipeStep;
return KameletSchemaService.getTooltipContent(stepModel, path);
}

shivamG640 marked this conversation as resolved.
Show resolved Hide resolved
getComponentSchema(path?: string): VisualComponentSchema | undefined {
if (!path) return undefined;
const stepModel = get(this.spec, path) as PipeStep;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,48 @@ describe('CamelComponentSchemaService', () => {
);
});

describe('getTooltipContent', () => {
it('should return the component schema description if provided or return component name', () => {
const camelElementLookup = { processorName: 'from' as keyof ProcessorDefinition, componentName: 'timer' };
const actualContent = CamelComponentSchemaService.getTooltipContent(camelElementLookup);
const componentDefinition = CamelCatalogService.getCatalogLookup(camelElementLookup.componentName)?.definition;

const expectedContent =
(componentDefinition as unknown as ICamelComponentDefinition)?.component.description ??
camelElementLookup.componentName;
shivamG640 marked this conversation as resolved.
Show resolved Hide resolved

expect(actualContent).toEqual(expectedContent);
});

it('should return the kamelet schema description if provided or return kamelet name', () => {
const camelElementLookup = {
processorName: 'to' as keyof ProcessorDefinition,
componentName: 'kamelet:avro-deserialize-action',
};
const actualContent = CamelComponentSchemaService.getTooltipContent(camelElementLookup);
const kameletDefinition = CamelCatalogService.getCatalogLookup(camelElementLookup.componentName)?.definition;

const expectedContent =
(kameletDefinition as unknown as IKameletDefinition)?.spec.definition.description ??
camelElementLookup.componentName;
shivamG640 marked this conversation as resolved.
Show resolved Hide resolved

expect(actualContent).toEqual(expectedContent);
});

it('should return the processor schema description if provided or return processor name', () => {
const path = 'from.steps.0.aggregate';
const definition = { id: 'aggregate-2202' };
const camelElementLookup = CamelComponentSchemaService.getCamelComponentLookup(path, definition);

const actualContent = CamelComponentSchemaService.getTooltipContent(camelElementLookup);
const expectedContent =
CamelComponentSchemaService.getVisualComponentSchema(path, definition)?.schema.description ??
camelElementLookup.processorName;

expect(actualContent).toEqual(expectedContent);
});
});

describe('canHavePreviousStep', () => {
it.each([
['from', false],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -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 === 'component') {
shivamG640 marked this conversation as resolved.
Show resolved Hide resolved
return (
(catalogLookup.definition as unknown as ICamelComponentDefinition)?.component.description ??
camelElementLookup.componentName
);
}

if (catalogLookup.catalogKind === 'kamelet') {
shivamG640 marked this conversation as resolved.
Show resolved Hide resolved
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,27 @@ 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');
const actualResult =
KameletSchemaService.getKameletDefinition(step)?.propertiesSchema?.description ?? step?.ref?.name;

expect(result).toEqual(actualResult);
});

it('should return the Kamelet name as the tooltip content', () => {
const result = KameletSchemaService.getTooltipContent(undefined, 'sink');

expect(result).toEqual('sink: Unknown');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,16 @@ export class KameletSchemaService {
return CamelCatalogService.getComponent(CatalogKind.Kamelet, stepName);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
shivamG640 marked this conversation as resolved.
Show resolved Hide resolved
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 !== undefined && schema.description !== undefined) {
shivamG640 marked this conversation as resolved.
Show resolved Hide resolved
return schema.description;
}

return step?.ref?.name ?? `${path}: Unknown`;
}
}
22 changes: 22 additions & 0 deletions packages/ui/src/models/visualization/visualization-node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions packages/ui/src/models/visualization/visualization-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class VisualizationNode<T extends IVisualizationNodeData = IVisualizationNodeDat
return this.getRootNode().getBaseEntity()?.getNodeLabel(this.data.path) ?? this.id;
}

getTooltipContent(): string {
return this.getRootNode().getBaseEntity()?.getTooltipContent(this.data.path) ?? this.id;
}

addBaseEntityStep(definition: DefinedComponent, mode: AddStepMode): void {
this.getRootNode().getBaseEntity()?.addStep({ definedComponent: definition, mode, data: this.data });
}
Expand Down
Loading