Skip to content

Commit

Permalink
feat(viz): Add new nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
lordrip committed Oct 26, 2023
1 parent c9b3255 commit adebc2d
Show file tree
Hide file tree
Showing 19 changed files with 356 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('Canvas', () => {

it('should render the Catalog button if `CatalogModalContext` is provided', async () => {
const result = render(
<CatalogModalContext.Provider value={{ setIsModalOpen: jest.fn() }}>
<CatalogModalContext.Provider value={{ getNewComponent: jest.fn(), setIsModalOpen: jest.fn() }}>
<VisibleFlowsContext.Provider
value={{ visibleFlows: { ['route-8888']: true } } as unknown as VisibleFLowsContextResult}
>
Expand Down
53 changes: 51 additions & 2 deletions packages/ui/src/components/Visualization/Custom/CustomNode.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MinusIcon } from '@patternfly/react-icons';
import { AngleDownIcon, AngleUpIcon, MinusIcon, PlusIcon } from '@patternfly/react-icons';
import {
ContextMenuItem,
DefaultNode,
Expand All @@ -10,7 +10,8 @@ import {
withContextMenu,
withSelection,
} from '@patternfly/react-topology';
import { FunctionComponent, useCallback, useContext } from 'react';
import { FunctionComponent, PropsWithChildren, useCallback, useContext, useMemo } from 'react';
import { CatalogModalContext } from '../../../providers/catalog-modal.provider';
import { EntitiesContext } from '../../../providers/entities.provider';
import { CanvasNode } from '../Canvas/canvas.models';
import { CanvasService } from '../Canvas/canvas.service';
Expand Down Expand Up @@ -46,6 +47,46 @@ const CustomNode: FunctionComponent<CustomNodeProps> = ({ element, ...rest }) =>
);
};

const AddNode: FunctionComponent<PropsWithChildren<{ mode: 'prepend' | 'append' }>> = (props) => {
const entitiesContext = useContext(EntitiesContext);
const catalogModalContext = useContext(CatalogModalContext);
const element: GraphElement<ElementModel, CanvasNode['data']> = useContext(ElementContext);
const vizNode = element.getData()?.vizNode;
const shouldRender = useMemo(() => {
const isRootNode = vizNode?.getParentNode() === undefined && vizNode?.getPreviousNode() === undefined;

/** If this is the root node and this component is in `prepend` mode, we should skip rendering */
return !(isRootNode && props.mode === 'prepend');
}, [props.mode, vizNode]);

const onAddNode = useCallback(async () => {
/** Get compatible nodes and the location where can be introduced */

/** Prepare Catalog filter to only show the compatible nodes */

/** Open Catalog modal */
const newComponent = await catalogModalContext?.getNewComponent();
if (!newComponent || !vizNode || !entitiesContext) {
return;
}

/** Add new node to the entities */
const defaultDefinition = entitiesContext.camelResource.getDefaultNodeDefition(newComponent);

/** Add new node to the canvas */
if (props.mode === 'prepend') {
vizNode.prependBaseEntityChild(defaultDefinition);
} else {
vizNode.appendBaseEntityChild(defaultDefinition);
}

/** Update entity */
entitiesContext.updateCodeFromEntities();
}, [catalogModalContext, entitiesContext, props.mode, vizNode]);

return shouldRender ? <ContextMenuItem onClick={onAddNode}>{props.children}</ContextMenuItem> : null;
};

const RemoveNode: FunctionComponent = () => {
const entitiesContext = useContext(EntitiesContext);
const element: GraphElement<ElementModel, CanvasNode['data']> = useContext(ElementContext);
Expand All @@ -64,5 +105,13 @@ const RemoveNode: FunctionComponent = () => {
};

export const CustomNodeWithSelection: typeof DefaultNode = withContextMenu(() => [
<AddNode key="context-menu-item-prepend" mode="prepend">
<AngleUpIcon />
<PlusIcon /> Prepend node
</AddNode>,
<AddNode key="context-menu-item-append" mode="append">
<AngleDownIcon />
<PlusIcon /> Append node
</AddNode>,
<RemoveNode key="context-menu-item-remove" />,
])(withSelection()(CustomNode) as typeof DefaultNode) as typeof DefaultNode;
2 changes: 1 addition & 1 deletion packages/ui/src/models/camel-catalog-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface SchemaEntry extends CatalogEntry {
description: string;
}

export type CatalogTypes = Record<string, ICamelComponentDefinition | ICamelProcessorDefinition | IKameletDefinition>;
export type ComponentsCatalogTypes = ICamelComponentDefinition | ICamelProcessorDefinition | IKameletDefinition;

export interface ComponentsCatalog {
[CatalogKind.Component]?: Record<string, ICamelComponentDefinition>;
Expand Down
4 changes: 4 additions & 0 deletions packages/ui/src/models/camel-component-lookup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface ICamelElementLookupResult {
processorName: string;
componentName?: string;
}
11 changes: 8 additions & 3 deletions packages/ui/src/models/camel/camel-k-resource.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { MetadataEntity } from '../visualization/metadata';
import {
Integration as IntegrationType,
Kamelet as KameletType,
KameletBinding as KameletBindingType,
Kamelet as KameletType,
Pipe as PipeType,
} from '@kaoto-next/camel-catalog/types';
import { ComponentsCatalogTypes } from '..';
import { BaseVisualCamelEntity } from '../visualization/base-visual-entity';
import { MetadataEntity } from '../visualization/metadata';
import { CamelResource } from './camel-resource';
import { BaseCamelEntity } from './entities';
import { SourceSchemaType } from './source-schema-type';
import { BaseVisualCamelEntity } from '../visualization/base-visual-entity';

export type CamelKType = IntegrationType | KameletType | KameletBindingType | PipeType;

Expand Down Expand Up @@ -61,6 +62,10 @@ export abstract class CamelKResource implements CamelResource {
return answer;
}

getDefaultNodeDefition(_newComponent: ComponentsCatalogTypes | undefined): object {
throw new Error('Method not implemented.');
}

abstract getType(): SourceSchemaType;

abstract getVisualEntities(): BaseVisualCamelEntity[];
Expand Down
22 changes: 13 additions & 9 deletions packages/ui/src/models/camel/camel-resource.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import {
Integration as IntegrationType,
KameletBinding as KameletBindingType,
Kamelet as KameletType,
Pipe as PipeType,
} from '@kaoto-next/camel-catalog/types';
import { ComponentsCatalogTypes } from '../camel-catalog-index';
import { BaseVisualCamelEntity } from '../visualization/base-visual-entity';
import { BaseCamelEntity } from './entities';
import { BeansEntity } from '../visualization/metadata';
import { CamelRouteResource } from './camel-route-resource';
import { BaseCamelEntity } from './entities';
import { IntegrationResource } from './integration-resource';
import { KameletResource } from './kamelet-resource';
import { KameletBindingResource } from './kamelet-binding-resource';
import { KameletResource } from './kamelet-resource';
import { PipeResource } from './pipe-resource';
import { SourceSchemaType } from './source-schema-type';
import { BeansEntity } from '../visualization/metadata';
import {
Integration as IntegrationType,
Kamelet as KameletType,
KameletBinding as KameletBindingType,
Pipe as PipeType,
} from '@kaoto-next/camel-catalog/types';

export interface CamelResource {
getVisualEntities(): BaseVisualCamelEntity[];
Expand All @@ -22,6 +23,9 @@ export interface CamelResource {
supportsMultipleVisualEntities(): boolean;
toJSON(): unknown;
getType(): SourceSchemaType;

/** Components Catalog related methods */
getDefaultNodeDefition(newComponent: ComponentsCatalogTypes | undefined): object;
}

export interface BeansAwareResource {
Expand Down
17 changes: 12 additions & 5 deletions packages/ui/src/models/camel/camel-route-resource.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { BeansAwareResource, CamelResource } from './camel-resource';
import { BaseCamelEntity } from './entities';
import { RouteDefinition } from '@kaoto-next/camel-catalog/types';
import { isDefined } from '../../utils';
import { ComponentsCatalogTypes } from '../camel-catalog-index';
import { CamelRouteVisualEntity, isCamelRoute } from '../visualization/flows';
import { flowTemplateService } from '../visualization/flows/flow-templates-service';
import { CamelComponentDefaultService } from '../visualization/flows/support/camel-component-default.service';
import { BeansEntity, isBeans } from '../visualization/metadata';
import { BeansAwareResource, CamelResource } from './camel-resource';
import { BaseCamelEntity } from './entities';
import { SourceSchemaType } from './source-schema-type';
import { isDefined } from '../../utils';
import { flowTemplateService } from '../visualization/flows/flow-templates-service';
import { RouteDefinition } from '@kaoto-next/camel-catalog/types';

export class CamelRouteResource implements CamelResource, BeansAwareResource {
private entities: BaseCamelEntity[] = [];
Expand Down Expand Up @@ -88,4 +90,9 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource {
this.addNewEntity();
}
}

/** Components Catalog related methods */
getDefaultNodeDefition(newComponent: ComponentsCatalogTypes): object {
return CamelComponentDefaultService.getDefaultValue(newComponent);
}
}
8 changes: 8 additions & 0 deletions packages/ui/src/models/visualization/base-visual-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export interface BaseVisualCamelEntity extends BaseCamelEntity {
/** Retrieve the steps from the underlying Camel entity */
getSteps: () => unknown[];

/** Add a step to the underlying Camel entity */
addStep: (options: { definition: object; mode: 'prepend' | 'append'; data: IVisualizationNodeData }) => void;

/** Remove the step at a given path from the underlying Camel entity */
removeStep: (path?: string) => void;

Expand All @@ -45,6 +48,10 @@ export interface IVisualizationNode<T extends IVisualizationNodeData = IVisualiz
/** This property is only set on the root node */
getBaseEntity(): BaseVisualCamelEntity | undefined;

prependBaseEntityChild(definition: object): void;

appendBaseEntityChild(definition: object): void;

getComponentSchema(): VisualComponentSchema | undefined;

updateModel(value: unknown): void;
Expand Down Expand Up @@ -83,6 +90,7 @@ export interface IVisualizationNodeData {
icon?: string;
path?: string;
entity?: BaseVisualCamelEntity;
[key: string]: unknown;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentsCatalog } from '../../camel-catalog-index';
import { ComponentsCatalog, ComponentsCatalogTypes } from '../../camel-catalog-index';
import { ICamelComponentDefinition } from '../../camel-components-catalog';
import { ICamelProcessorDefinition } from '../../camel-processors-catalog';
import { CatalogKind } from '../../catalog-kind';
Expand All @@ -21,10 +21,8 @@ export class CamelCatalogService {
static getComponent(catalogKey: CatalogKind.Component, componentName?: string): ICamelComponentDefinition | undefined;
static getComponent(catalogKey: CatalogKind.Processor, componentName?: string): ICamelProcessorDefinition | undefined;
static getComponent(catalogKey: CatalogKind.Kamelet, componentName?: string): IKameletDefinition | undefined;
static getComponent(
catalogKey: CatalogKind,
componentName?: string,
): ICamelComponentDefinition | ICamelProcessorDefinition | IKameletDefinition | undefined {
static getComponent(catalogKey: CatalogKind, componentName?: string): ComponentsCatalogTypes | undefined;
static getComponent(catalogKey: CatalogKind, componentName?: string): ComponentsCatalogTypes | undefined {
if (componentName === undefined) return undefined;

return this.catalogs[catalogKey]?.[componentName];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { JSONSchemaType } from 'ajv';
import cloneDeep from 'lodash.clonedeep';
import { camelRouteJson } from '../../../stubs/camel-route';
import { EntityType } from '../../camel/entities/base-entity';
import { CamelComponentSchemaService } from './camel-component-schema.service';
import { CamelComponentSchemaService } from './support/camel-component-schema.service';
import { CamelRouteVisualEntity, isCamelRoute } from './camel-route-visual-entity';

describe('Camel Route', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import set from 'lodash.set';
import { getCamelRandomId } from '../../../camel-utils/camel-random-id';
import { isDefined } from '../../../utils';
import { NodeIconResolver } from '../../../utils/node-icon-resolver';
import { ICamelElementLookupResult } from '../../camel-component-lookup';
import { EntityType } from '../../camel/entities';
import {
BaseVisualCamelEntity,
Expand All @@ -13,7 +14,7 @@ import {
VisualComponentSchema,
} from '../base-visual-entity';
import { createVisualizationNode } from '../visualization-node';
import { CamelComponentSchemaService, ICamelElementLookupResult } from './camel-component-schema.service';
import { CamelComponentSchemaService } from './support/camel-component-schema.service';

type CamelRouteVisualEntityData = IVisualizationNodeData & ICamelElementLookupResult;

Expand Down Expand Up @@ -70,6 +71,73 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity {
return this.route.from?.steps ?? [];
}

/**
* Add a step to the route
*
* path examples:
* from
* from.steps.0.setHeader
* from.steps.1.choice.otherwise
* from.steps.1.choice.otherwise.steps.0.setHeader
* from.steps.1.choice.when.0
* from.steps.1.choice.when.0.steps.0.setHeader
*/
addStep(options: {
definition: ProcessorDefinition;
mode: 'prepend' | 'append';
data: IVisualizationNodeData;
}): void {
if (options.data.path === undefined) return;

console.log(options);

const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties(
(options.data as CamelRouteVisualEntityData).processorName,
);
if (stepsProperties.length === 1 && stepsProperties[0].type === 'list') {
const arrayPath = `${options.data.path}.${stepsProperties[0].name}`;
let stepsArray: ProcessorDefinition[] | undefined = get(this.route, arrayPath);
if (!Array.isArray(stepsArray)) {
set(this.route, arrayPath, []);
stepsArray = get(this.route, arrayPath) as ProcessorDefinition[];
}

stepsArray.unshift(options.definition);

return;
}

/**
* If there's only one path segment, it means the target is the `from` property of the route
* therefore we insert the step as the first element in the array
*/
if (options.data.path === 'from') {
this.route.from?.steps.unshift(options.definition);
return;
}

const pathArray = options.data.path.split('.');
const last = pathArray[pathArray.length - 1];
const penultimate = pathArray[pathArray.length - 2];

/**
* If the last segment is a string and the penultimate is a number, it means the target is member of an array
* therefore we need to look for the array and insert the element at the given index + 1
*
* f.i. from.steps.0.setHeader
* penultimate: 0
* last: setHeader
*/
if (!Number.isInteger(Number(last)) && Number.isInteger(Number(penultimate))) {
const desiredIndex = options.mode === 'prepend' ? Number(penultimate) : Number(penultimate) + 1;

const stepsArray: ProcessorDefinition[] = get(this.route, pathArray.slice(0, -2), []);
stepsArray.splice(desiredIndex, 0, options.definition);

return;
}
}

removeStep(path?: string): void {
if (!path) return;
/**
Expand Down Expand Up @@ -128,7 +196,7 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity {
}
}

toVizNode(): IVisualizationNode<CamelRouteVisualEntityData> {
toVizNode(): IVisualizationNode {
const rootNode = this.getVizNodeFromProcessor('from', { processorName: 'from' });
rootNode.data.entity = this;

Expand All @@ -145,10 +213,7 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity {
return rootNode;
}

private getVizNodeFromProcessor(
path: string,
componentLookup: ICamelElementLookupResult,
): IVisualizationNode<CamelRouteVisualEntityData> {
private getVizNodeFromProcessor(path: string, componentLookup: ICamelElementLookupResult): IVisualizationNode {
const data: CamelRouteVisualEntityData = {
label: CamelComponentSchemaService.getLabel(componentLookup, get(this.route, path)),
path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export class KameletVisualEntity implements BaseVisualCamelEntity {
return []; // TODO
}

addStep(): void {
return; // TODO
}

removeStep(): void {
return; // TODO
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { JSONSchemaType } from 'ajv';
import cloneDeep from 'lodash/cloneDeep';
import { pipeJson } from '../../../stubs/pipe';
import { EntityType } from '../../camel/entities';
import { KameletSchemaService } from './kamelet-schema.service';
import { PipeVisualEntity } from './pipe-visual-entity';
import { KameletSchemaService } from './support/kamelet-schema.service';

describe('Pipe', () => {
let pipeCR: Pipe;
Expand Down
Loading

0 comments on commit adebc2d

Please sign in to comment.