Skip to content

Commit

Permalink
Show tooltips for each step on the canvas (#629)
Browse files Browse the repository at this point in the history
* fix(538): initial commit

* fix(538): Show tooltips for each step on the canvas

* fix(538): Added tests for camel components

* fix(538): Updating the getTooltipContent() and adding tests

* fix(538): Handled kamlets description and tests

* fix(538): Updated code to fix e2e tests

* fix(538): Updated code to fix e2e tests

* fix(538): Makings tests clear

---------

Co-authored-by: shivamgu <[email protected]>
  • Loading branch information
shivamG640 and Shivam-Gu authored Jan 25, 2024
1 parent 56eb442 commit 2c3c995
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 8 deletions.
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 } from 'react';
import { AddStepMode } from '../../../models/visualization/base-visual-entity';
import { CanvasDefaults } from '../Canvas/canvas.defaults';
Expand All @@ -24,6 +25,7 @@ const noopFn = () => {};
const CustomNode: FunctionComponent<CustomNodeProps> = ({ 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;

Expand All @@ -45,13 +47,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 @@ -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;

Expand Down Expand Up @@ -71,6 +74,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;
}

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);
}

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,60 @@ describe('CamelComponentSchemaService', () => {
);
});

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],
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 === 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
});
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
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`;
}
}
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 @@ -49,6 +49,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

0 comments on commit 2c3c995

Please sign in to comment.