Update flow diagram to support branching edges for conditionals (#28481)

- If there is a conditional subflow, instead of creating nodes
for the start and end of the subflow, there will now be branching edges
for the condition nodes, representing the false and true cases.
- Adds an optional label attribute to the edges to support having
true and false labels for condition node edges.
- Modifies auto layout to reduce the amount of overlap that
occurs with the branching condition edges and labels.
- Also removed the startSubFlow check in the createNode method
since the start and end subflow nodes are always created by the
renderSubFlowNodes method.

Closes #28453

Signed-off-by: Greg Baroni <greg.baroni@appfolio.com>
This commit is contained in:
gregbaroni 2024-04-09 05:37:10 -07:00 committed by GitHub
parent b2c88e9876
commit 8140c76147
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 87 additions and 19 deletions

View file

@ -31,11 +31,16 @@ type FlowDiagramProps = {
executionList: ExecutionList;
};
const createEdge = (fromNode: string, toNode: string): Edge => ({
const createEdge = (
fromNode: string,
toNode: string,
label?: string,
): Edge => ({
id: `edge-${fromNode}-to-${toNode}`,
type: "buttonEdge",
source: fromNode,
target: toNode,
label: label,
data: {
onEdgeClick: (
evt: ReactMouseEvent<HTMLButtonElement, MouseEvent>,
@ -49,9 +54,6 @@ const createEdge = (fromNode: string, toNode: string): Edge => ({
const createNode = (ex: ExpandableExecution): Node => {
let nodeType: string | undefined = undefined;
if (ex.executionList) {
nodeType = "startSubFlow";
}
if (providerConditionFilter(ex)) {
nodeType = "conditional";
}
@ -73,10 +75,13 @@ const renderParallelEdges = (
start: AuthenticationExecutionInfoRepresentation,
execution: ExpandableExecution,
end: AuthenticationExecutionInfoRepresentation,
): Edge[] => [
): Edge[] => {
const falseConditionLabel = providerConditionFilter(execution) ? "false" : "";
return [
createEdge(start.id!, execution.id!),
createEdge(execution.id!, end.id!),
createEdge(execution.id!, end.id!, falseConditionLabel),
];
};
const renderSequentialNodes = (execution: ExpandableExecution): Node[] => [
createNode(execution),
@ -95,16 +100,46 @@ const renderSequentialEdges = (
if (isFirst) {
edges.push(createEdge(start.id!, execution.id!));
} else {
edges.push(createEdge(prefExecution.id!, execution.id!));
const trueConditionLabel = providerConditionFilter(prefExecution)
? "true"
: "";
edges.push(
createEdge(prefExecution.id!, execution.id!, trueConditionLabel),
);
}
if (isLast) {
edges.push(createEdge(execution.id!, end.id!));
if (isLast || providerConditionFilter(execution)) {
const falseConditionLabel = providerConditionFilter(execution)
? "false"
: "";
edges.push(createEdge(execution.id!, end.id!, falseConditionLabel));
}
return edges;
};
const renderConditionalSubFlowNodes = (
execution: ExpandableExecution,
): Node[] => renderFlowNodes(execution.executionList || []);
const renderConditionalSubFlowEdges = (
execution: ExpandableExecution,
start: AuthenticationExecutionInfoRepresentation,
end: AuthenticationExecutionInfoRepresentation,
prefExecution?: ExpandableExecution,
): Edge[] => {
const conditionalSubFlowStart =
prefExecution && prefExecution.requirement !== "ALTERNATIVE"
? prefExecution
: start!;
return renderFlowEdges(
conditionalSubFlowStart,
execution.executionList || [],
end,
);
};
const renderSubFlowNodes = (execution: ExpandableExecution): Node[] => {
const nodes: Node[] = [];
@ -165,7 +200,11 @@ const renderFlowNodes = (executionList: ExpandableExecution[]): Node[] => {
for (let index = 0; index < executionList.length; index++) {
const execution = executionList[index];
if (execution.executionList) {
if (execution.requirement === "CONDITIONAL") {
elements = elements.concat(renderConditionalSubFlowNodes(execution));
} else {
elements = elements.concat(renderSubFlowNodes(execution));
}
} else {
if (
execution.requirement === "ALTERNATIVE" ||
@ -191,9 +230,20 @@ const renderFlowEdges = (
for (let index = 0; index < executionList.length; index++) {
const execution = executionList[index];
if (execution.executionList) {
if (execution.requirement === "CONDITIONAL") {
elements = elements.concat(
renderConditionalSubFlowEdges(
execution,
start,
end,
executionList[index - 1],
),
);
} else {
elements = elements.concat(
renderSubFlowEdges(execution, start, end, executionList[index - 1]),
);
}
} else {
if (
execution.requirement === "ALTERNATIVE" ||

View file

@ -27,6 +27,7 @@ export const ButtonEdge = ({
targetY,
sourcePosition,
targetPosition,
label,
style = {},
markerType,
markerEndId,
@ -52,6 +53,18 @@ export const ButtonEdge = ({
d={edgePath}
markerEnd={markerEnd}
/>
{!selected && (
<text>
<textPath
href={`#${id}`}
style={{ fontSize: "11px" }}
startOffset="50%"
textAnchor="middle"
>
{label}
</textPath>
</text>
)}
{selected && (
<foreignObject
width={foreignObjectSize}

View file

@ -5,16 +5,21 @@ const dagreGraph = new graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
const nodeWidth = 130;
const nodeHeight = 28;
const nodeHeight = 40;
const nodeAfterConditionalHeight = 130;
export const getLayoutedNodes = (nodes: Node[], direction = "LR"): Node[] => {
const isHorizontal = direction === "LR";
dagreGraph.setGraph({ rankdir: direction });
nodes.forEach((element) => {
nodes.forEach((element, index) => {
const prevNode = index > 0 ? nodes[index - 1] : undefined;
dagreGraph.setNode(element.id, {
width: nodeWidth,
height: nodeHeight,
height:
prevNode?.type === "conditional"
? nodeAfterConditionalHeight
: nodeHeight,
});
});
@ -26,7 +31,7 @@ export const getLayoutedNodes = (nodes: Node[], direction = "LR"): Node[] => {
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
node.position = {
x: nodeWithPosition.x - nodeWidth / 2 + Math.random() / 1000,
x: nodeWithPosition.x - nodeWidth / 2,
y: nodeWithPosition.y - nodeHeight / 2,
};