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

feat(Canvas): Add quick icon edge end #1571

Merged
merged 2 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ describe('Test for Multi route actions from the code editor', () => {
cy.editorDeleteLine(7, 4);
cy.openDesignPage();
cy.showAllRoutes();
cy.get('[data-id^="log"]').should('have.length', 1);
/** We check how many nodes are remaining */
cy.get('[data-id^="log"][data-kind="node"]').should('have.length', 1);
cy.get('[data-testid="flows-list-route-count"]').should('have.text', '2/2');
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ describe('Test for Multi route actions from the canvas', () => {
cy.addNewRoute();

cy.showAllRoutes();
cy.get('[data-id^="log"]').should('have.length', 3);
/** We check how many nodes are remaining */
cy.get('[data-id^="log"][data-kind="node"]').should('have.length', 3);
cy.get('[data-testid="flows-list-route-count"]').should('have.text', '3/3');
});
});

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
Visualization,
withPanZoom,
} from '@patternfly/react-topology';
import { CustomGroupWithSelection, CustomNodeWithSelection, NoBendpointsEdge } from '../Custom';
import { CustomGroupWithSelection, CustomNodeWithSelection, NoBendpointsEdge, EdgeEndWithButton } from '../Custom';
import { LayoutType } from './canvas.models';

export class ControllerService {
Expand Down Expand Up @@ -109,6 +109,8 @@ export class ControllerService {
switch (type) {
case 'group':
return CustomGroupWithSelection;
case 'edge-end':
return EdgeEndWithButton;
default:
switch (kind) {
case ModelKind.graph:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createVisualizationNode } from '../../../models/visualization';
import { BaseVisualCamelEntity } from '../../../models/visualization/base-visual-entity';
import { CamelRouteVisualEntity, createVisualizationNode } from '../../../models/visualization';
import { FlowService } from './flow.service';

describe('FlowService', () => {
Expand Down Expand Up @@ -86,18 +85,7 @@ describe('FlowService', () => {
});

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 routeNode = new CamelRouteVisualEntity({ from: { uri: 'timer:clock', steps: [] } }).toVizNode();

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

Expand Down
13 changes: 13 additions & 0 deletions packages/ui/src/components/Visualization/Canvas/flow.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,12 @@ export class FlowService {

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

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

return edges;
Expand Down Expand Up @@ -111,4 +114,14 @@ export class FlowService {
edgeStyle: EdgeStyle.solid,
};
}

private static getEdgeEnd(source: string): CanvasEdge {
return {
id: `${source}-end`,
type: 'edge-end',
source,
target: source,
edgeStyle: EdgeStyle.dashed,
};
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,49 @@
import { ContextMenuItem } from '@patternfly/react-topology';
import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react';
import { IDataTestID } from '../../../../models';
import { AddStepMode, IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
import {
AddStepMode,
IVisualizationNode,
IVisualizationNodeData,
} from '../../../../models/visualization/base-visual-entity';
import { CatalogModalContext } from '../../../../providers/catalog-modal.provider';
import { EntitiesContext } from '../../../../providers/entities.provider';
import { EntitiesContextResult } from '../../../../hooks';

interface ItemAddStepProps extends PropsWithChildren<IDataTestID> {
mode: AddStepMode.PrependStep | AddStepMode.AppendStep;
vizNode: IVisualizationNode;
}

export const ItemAddStep: FunctionComponent<ItemAddStepProps> = (props) => {
const entitiesContext = useContext(EntitiesContext);
const catalogModalContext = useContext(CatalogModalContext);
export const addNode = async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
catalogModalContext: any,
entitiesContext: EntitiesContextResult | null,
vizNode: IVisualizationNode<IVisualizationNodeData>,
mode: AddStepMode = AddStepMode.AppendStep,
) => {
if (!vizNode || !entitiesContext) return;

const onAddNode = useCallback(async () => {
if (!props.vizNode || !entitiesContext) return;
/** Get compatible nodes and the location where can be introduced */
const compatibleNodes = entitiesContext.camelResource.getCompatibleComponents(mode, vizNode.data);

Check warning on line 28 in packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx#L28

Added line #L28 was not covered by tests

/** Open Catalog modal, filtering the compatible nodes */
const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes);

Check warning on line 31 in packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx#L31

Added line #L31 was not covered by tests
if (!definedComponent) return;

/** Get compatible nodes and the location where can be introduced */
const compatibleNodes = entitiesContext.camelResource.getCompatibleComponents(props.mode, props.vizNode.data);
/** Add new node to the entities */
vizNode.addBaseEntityStep(definedComponent, mode);

Check warning on line 35 in packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx#L35

Added line #L35 was not covered by tests

/** Open Catalog modal, filtering the compatible nodes */
const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes);
if (!definedComponent) return;
/** Update entity */
entitiesContext.updateEntitiesFromCamelResource();

Check warning on line 38 in packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx#L38

Added line #L38 was not covered by tests
};

/** Add new node to the entities */
props.vizNode.addBaseEntityStep(definedComponent, props.mode);
export const ItemAddStep: FunctionComponent<ItemAddStepProps> = (props) => {
const entitiesContext = useContext(EntitiesContext);
const catalogModalContext = useContext(CatalogModalContext);

/** Update entity */
entitiesContext.updateEntitiesFromCamelResource();
const onAddNode = useCallback(async () => {
addNode(catalogModalContext, entitiesContext, props.vizNode, props.mode);

Check warning on line 46 in packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx#L46

Added line #L46 was not covered by tests
}, [catalogModalContext, entitiesContext, props.mode, props.vizNode]);

return (
Expand Down
57 changes: 57 additions & 0 deletions packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { PlusIcon } from '@patternfly/react-icons';
import {
Decorator,
DefaultEdge,
EdgeModel,
EdgeTerminalType,
GraphElement,
isEdge,
observer,
} from '@patternfly/react-topology';
import { FunctionComponent, useCallback, useContext } from 'react';
import { IVisualizationNode } from '../../../../models';
import { CatalogModalContext, EntitiesContext } from '../../../../providers';
import { addNode } from '../ContextMenu/ItemAddStep';
import { LayoutType } from '../../Canvas';

type DefaultEdgeProps = Parameters<typeof DefaultEdge>[0];
interface EdgeEndProps extends DefaultEdgeProps {
/** We're not providing Data to edges */
element: GraphElement<EdgeModel, unknown>;
}

export const EdgeEndWithButton: FunctionComponent<EdgeEndProps> = observer(({ element, ...rest }) => {
if (!isEdge(element)) {
throw new Error('EdgeEndWithButton must be used only on Edge elements');

Check warning on line 25 in packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx#L25

Added line #L25 was not covered by tests
}
const entitiesContext = useContext(EntitiesContext);
const catalogModalContext = useContext(CatalogModalContext);
const vizNode: IVisualizationNode = element.getSource().getData()?.vizNode;
const isHorizontal = element.getGraph().getLayout() === LayoutType.DagreHorizontal;
const endPoint = element.getEndPoint();

const onAdd = useCallback(() => {
addNode(catalogModalContext, entitiesContext, vizNode);

Check warning on line 34 in packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx#L34

Added line #L34 was not covered by tests
}, [catalogModalContext, entitiesContext, vizNode]);

let x = endPoint.x;
let y = endPoint.y;
if (isHorizontal) {
x += 14;

Check warning on line 40 in packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx#L40

Added line #L40 was not covered by tests
} else {
y += 4;
}

return (
<DefaultEdge
element={element}
startTerminalType={EdgeTerminalType.none}
endTerminalType={EdgeTerminalType.none}
{...rest}
>
<g data-testid={`custom-edge__${element?.getId()}`}>
<Decorator showBackground radius={14} x={x} y={y} icon={<PlusIcon />} onClick={onAdd} />
</g>
</DefaultEdge>
);
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,68 @@
import { BaseEdge, Point } from '@patternfly/react-topology';
import { BaseEdge, getTopCollapsedParent, Point } from '@patternfly/react-topology';
import { LayoutType } from '../Canvas';

export class NoBendpointsEdge extends BaseEdge {
getBendpoints(): Point[] {
return [];
}

getStartPoint(): Point {
if (this.getTarget() === this.getSource()) {
const parent = getTopCollapsedParent(this.getSource());
const isHorizontal = this.getGraph().getLayout() === LayoutType.DagreHorizontal;
const parentPos = parent.getPosition();
const parentSize = parent.getDimensions();
let x, y;
if (isHorizontal) {
if (parent.getType() === 'group') {
x = parentPos.x + parentSize.width / 2.0;
y = parentPos.y;
} else {
x = parentPos.x + parentSize.width;
y = parentPos.y + parentSize.height / 2.0;

Check warning on line 22 in packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx#L18-L22

Added lines #L18 - L22 were not covered by tests
}
} else {
if (parent.getType() === 'group') {
x = parentPos.x;
y = parentPos.y + parentSize.height / 2.0;

Check warning on line 27 in packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx#L26-L27

Added lines #L26 - L27 were not covered by tests
} else {
x = parentPos.x + parentSize.width / 2.0;
y = parentPos.y + parentSize.height;
}
}
return new Point(x, y);
}

return super.getStartPoint();
}

getEndPoint(): Point {
if (this.getTarget() === this.getSource()) {
const parent = getTopCollapsedParent(this.getSource());
const isHorizontal = this.getGraph().getLayout() === LayoutType.DagreHorizontal;
const parentPos = parent.getPosition();
const parentSize = parent.getDimensions();
let x, y;
if (isHorizontal) {
if (parent.getType() === 'group') {
x = parentPos.x + parentSize.width / 2.0 + 15;
y = parentPos.y;
} else {
x = parentPos.x + parentSize.width / 2.0 + 55;
y = parentPos.y + parentSize.height / 2.0;

Check warning on line 52 in packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx#L48-L52

Added lines #L48 - L52 were not covered by tests
}
} else {
if (parent.getType() === 'group') {
x = parentPos.x;
y = parentPos.y + parentSize.height / 2.0 + 15;

Check warning on line 57 in packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx

View check run for this annotation

Codecov / codecov/patch

packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx#L56-L57

Added lines #L56 - L57 were not covered by tests
} else {
x = parentPos.x + parentSize.width / 2.0;
y = parentPos.y + parentSize.height / 2.0 + 85;
}
}
return new Point(x, y);
}

return super.getEndPoint();
}
}
1 change: 1 addition & 0 deletions packages/ui/src/components/Visualization/Custom/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './Group/CustomGroup';
export * from './NoBendingEdge';
export * from './Node/CustomNode';
export * from './Edge/EdgeEnd';
21 changes: 21 additions & 0 deletions packages/ui/src/tests/__snapshots__/nodes-edges.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4922,12 +4922,33 @@ exports[`Nodes and Edges should generate edges for steps with branches 2`] = `
"target": "choice-1234",
"type": "edge",
},
{
"edgeStyle": "dashed",
"id": "setHeader-1234-end",
"source": "setHeader-1234",
"target": "setHeader-1234",
"type": "edge-end",
},
{
"edgeStyle": "dashed",
"id": "log-1234-end",
"source": "log-1234",
"target": "log-1234",
"type": "edge-end",
},
{
"edgeStyle": "solid",
"id": "choice-1234-to-sql-1234",
"source": "choice-1234",
"target": "sql-1234",
"type": "edge",
},
{
"edgeStyle": "dashed",
"id": "sql-1234-end",
"source": "sql-1234",
"target": "sql-1234",
"type": "edge-end",
},
]
`;
Loading