Skip to content

Commit

Permalink
chore(Canvas): Split CanvasService in ControllerService and `Flow…
Browse files Browse the repository at this point in the history
…Service`

Split `CanvasService` for better separation of concerns.

fix: #1329
  • Loading branch information
lordrip committed Aug 19, 2024
1 parent a72d5f2 commit fb0da39
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/ui/src/components/Visualization/Canvas/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { CanvasSideBar } from './CanvasSideBar';
import { CanvasDefaults } from './canvas.defaults';
import { CanvasEdge, CanvasNode, LayoutType } from './canvas.models';
import { CanvasService } from './canvas.service';
import { FlowService } from './flow.service';

interface CanvasProps {
contextToolbar?: ReactNode;
Expand Down Expand Up @@ -162,7 +163,7 @@ export const Canvas: FunctionComponent<PropsWithChildren<CanvasProps>> = ({ enti

entities.forEach((entity) => {
if (visibleFlows[entity.id]) {
const { nodes: childNodes, edges: childEdges } = CanvasService.getFlowDiagram(entity.toVizNode());
const { nodes: childNodes, edges: childEdges } = FlowService.getFlowDiagram(entity.toVizNode());
nodes.push(...childNodes);
edges.push(...childEdges);
}
Expand Down
112 changes: 112 additions & 0 deletions packages/ui/src/components/Visualization/Canvas/flow.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { createVisualizationNode } from '../../../models/visualization';
import { BaseVisualCamelEntity } from '../../../models/visualization/base-visual-entity';
import { FlowService } from './flow.service';

describe('FlowService', () => {
beforeEach(() => {
FlowService.nodes = [];
FlowService.edges = [];
});

it('should start with an empty nodes array', () => {
expect(FlowService.nodes).toEqual([]);
});

it('should start with an empty edges array', () => {
expect(FlowService.edges).toEqual([]);
});

describe('getFlowDiagram', () => {
it('should return nodes and edges for a simple VisualizationNode', () => {
const vizNode = createVisualizationNode('node', {});

const { nodes, edges } = FlowService.getFlowDiagram(vizNode);

expect(nodes).toMatchSnapshot();
expect(edges).toMatchSnapshot();
});

it('should return nodes and edges for a group with children', () => {
const groupVizNode = createVisualizationNode('group', { isGroup: true });
const child1VizNode = createVisualizationNode('child1', {});
const child2VizNode = createVisualizationNode('child2', {});
groupVizNode.addChild(child1VizNode);
groupVizNode.addChild(child2VizNode);

const { nodes, edges } = FlowService.getFlowDiagram(groupVizNode);

expect(nodes).toMatchSnapshot();
expect(edges).toMatchSnapshot();
});

it('should return nodes and edges for a two-nodes VisualizationNode', () => {
const vizNode = createVisualizationNode('node', {});
const childNode = createVisualizationNode('child', {});
vizNode.addChild(childNode);

const { nodes, edges } = FlowService.getFlowDiagram(vizNode);

expect(nodes).toMatchSnapshot();
expect(edges).toMatchSnapshot();
});

it('should return nodes and edges for a multiple nodes VisualizationNode', () => {
const vizNode = createVisualizationNode('node', {});

const setHeaderNode = createVisualizationNode('set-header', {});
vizNode.setNextNode(setHeaderNode);
setHeaderNode.setPreviousNode(vizNode);

const choiceNode = createVisualizationNode('choice', {});
setHeaderNode.setNextNode(choiceNode);
choiceNode.setPreviousNode(setHeaderNode);

const directNode = createVisualizationNode('direct', {});
choiceNode.setNextNode(directNode);
directNode.setPreviousNode(choiceNode);

const whenNode = createVisualizationNode('when', {});
choiceNode.addChild(whenNode);

const otherwiseNode = createVisualizationNode('otherwise', {});
choiceNode.addChild(otherwiseNode);

const whenLeafNode = createVisualizationNode('when-leaf', {});
whenNode.addChild(whenLeafNode);

const processNode = createVisualizationNode('process', {});
otherwiseNode.addChild(processNode);
const logNode = createVisualizationNode('log', {});
processNode.addChild(logNode);

const { nodes, edges } = FlowService.getFlowDiagram(vizNode);

expect(nodes).toMatchSnapshot();
expect(edges).toMatchSnapshot();
});

it('should return a group node for a multiple nodes VisualizationNode with a group', () => {
const routeNode = createVisualizationNode('route', {
entity: { getId: () => 'myId' } as BaseVisualCamelEntity,
isGroup: true,
});

const fromNode = createVisualizationNode('timer', {
path: 'from',
icon: undefined,
processorName: 'from',
componentName: 'timer',
});
routeNode.addChild(fromNode);

const { nodes, edges } = FlowService.getFlowDiagram(routeNode);

expect(nodes).toHaveLength(2);
expect(edges).toHaveLength(0);

const group = nodes[nodes.length - 1];
expect(group.children).toEqual(['timer-1234']);
expect(group.group).toBeTruthy();
});
});
});
114 changes: 114 additions & 0 deletions packages/ui/src/components/Visualization/Canvas/flow.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { EdgeStyle } from '@patternfly/react-topology';
import { IVisualizationNode } from '../../../models/visualization/base-visual-entity';
import { CanvasDefaults } from './canvas.defaults';
import { CanvasEdge, CanvasNode, CanvasNodesAndEdges } from './canvas.models';

export class FlowService {
static nodes: CanvasNode[] = [];
static edges: CanvasEdge[] = [];
private static visitedNodes: string[] = [];

static getFlowDiagram(vizNode: IVisualizationNode): CanvasNodesAndEdges {
this.nodes = [];
this.edges = [];
this.visitedNodes = [];

this.appendNodesAndEdges(vizNode);

return { nodes: this.nodes, edges: this.edges };
}

/** Method for iterating over all the IVisualizationNode and its children using a depth-first algorithm */
private static appendNodesAndEdges(vizNodeParam: IVisualizationNode): void {
if (this.visitedNodes.includes(vizNodeParam.id)) {
return;
}

let node: CanvasNode;

const children = vizNodeParam.getChildren();
if (vizNodeParam.data.isGroup && children) {
children.forEach((child) => {
this.appendNodesAndEdges(child);
});

const containerId = vizNodeParam.id;
node = this.getContainer(containerId, {
label: containerId,
children: children.map((child) => child.id),
parentNode: vizNodeParam.getParentNode()?.id,
data: { vizNode: vizNodeParam },
});
} else {
node = this.getCanvasNode(vizNodeParam);
}

/** Add node */
this.nodes.push(node);
this.visitedNodes.push(node.id);

/** Add edges */
this.edges.push(...this.getEdgesFromVizNode(vizNodeParam));
}

private static getCanvasNode(vizNodeParam: IVisualizationNode): CanvasNode {
/** Join the parent if exist to form a group */
const parentNode =
vizNodeParam.getParentNode()?.getChildren() !== undefined ? vizNodeParam.getParentNode()?.id : undefined;

return this.getNode(vizNodeParam.id, {
parentNode,
data: { vizNode: vizNodeParam },
});
}

private static getEdgesFromVizNode(vizNodeParam: IVisualizationNode): CanvasEdge[] {
const edges: CanvasEdge[] = [];

if (vizNodeParam.getNextNode() !== undefined) {
edges.push(this.getEdge(vizNodeParam.id, vizNodeParam.getNextNode()!.id));
}

return edges;
}

private static getContainer(
id: string,
options: { label?: string; children?: string[]; parentNode?: string; data?: CanvasNode['data'] } = {},
): CanvasNode {
return {
id,
type: 'group',
group: true,
label: options.label ?? id,
children: options.children ?? [],
parentNode: options.parentNode,
data: options.data,
style: {
padding: CanvasDefaults.DEFAULT_NODE_DIAMETER * 0.8,
},
};
}

private static getNode(id: string, options: { parentNode?: string; data?: CanvasNode['data'] } = {}): CanvasNode {
return {
id,
type: 'node',
parentNode: options.parentNode,
data: options.data,
width: CanvasDefaults.DEFAULT_NODE_DIAMETER,
height: CanvasDefaults.DEFAULT_NODE_DIAMETER,
shape: CanvasDefaults.DEFAULT_NODE_SHAPE,
};
}

private static getEdge(source: string, target: string): CanvasEdge {
return {
id: `${source}-to-${target}`,
type: 'edge',
source,
target,
edgeStyle: EdgeStyle.solid,
};
}
}

0 comments on commit fb0da39

Please sign in to comment.