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:
parent
b2c88e9876
commit
8140c76147
3 changed files with 87 additions and 19 deletions
|
@ -31,11 +31,16 @@ type FlowDiagramProps = {
|
||||||
executionList: ExecutionList;
|
executionList: ExecutionList;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createEdge = (fromNode: string, toNode: string): Edge => ({
|
const createEdge = (
|
||||||
|
fromNode: string,
|
||||||
|
toNode: string,
|
||||||
|
label?: string,
|
||||||
|
): Edge => ({
|
||||||
id: `edge-${fromNode}-to-${toNode}`,
|
id: `edge-${fromNode}-to-${toNode}`,
|
||||||
type: "buttonEdge",
|
type: "buttonEdge",
|
||||||
source: fromNode,
|
source: fromNode,
|
||||||
target: toNode,
|
target: toNode,
|
||||||
|
label: label,
|
||||||
data: {
|
data: {
|
||||||
onEdgeClick: (
|
onEdgeClick: (
|
||||||
evt: ReactMouseEvent<HTMLButtonElement, MouseEvent>,
|
evt: ReactMouseEvent<HTMLButtonElement, MouseEvent>,
|
||||||
|
@ -49,9 +54,6 @@ const createEdge = (fromNode: string, toNode: string): Edge => ({
|
||||||
|
|
||||||
const createNode = (ex: ExpandableExecution): Node => {
|
const createNode = (ex: ExpandableExecution): Node => {
|
||||||
let nodeType: string | undefined = undefined;
|
let nodeType: string | undefined = undefined;
|
||||||
if (ex.executionList) {
|
|
||||||
nodeType = "startSubFlow";
|
|
||||||
}
|
|
||||||
if (providerConditionFilter(ex)) {
|
if (providerConditionFilter(ex)) {
|
||||||
nodeType = "conditional";
|
nodeType = "conditional";
|
||||||
}
|
}
|
||||||
|
@ -73,10 +75,13 @@ const renderParallelEdges = (
|
||||||
start: AuthenticationExecutionInfoRepresentation,
|
start: AuthenticationExecutionInfoRepresentation,
|
||||||
execution: ExpandableExecution,
|
execution: ExpandableExecution,
|
||||||
end: AuthenticationExecutionInfoRepresentation,
|
end: AuthenticationExecutionInfoRepresentation,
|
||||||
): Edge[] => [
|
): Edge[] => {
|
||||||
|
const falseConditionLabel = providerConditionFilter(execution) ? "false" : "";
|
||||||
|
return [
|
||||||
createEdge(start.id!, execution.id!),
|
createEdge(start.id!, execution.id!),
|
||||||
createEdge(execution.id!, end.id!),
|
createEdge(execution.id!, end.id!, falseConditionLabel),
|
||||||
];
|
];
|
||||||
|
};
|
||||||
|
|
||||||
const renderSequentialNodes = (execution: ExpandableExecution): Node[] => [
|
const renderSequentialNodes = (execution: ExpandableExecution): Node[] => [
|
||||||
createNode(execution),
|
createNode(execution),
|
||||||
|
@ -95,16 +100,46 @@ const renderSequentialEdges = (
|
||||||
if (isFirst) {
|
if (isFirst) {
|
||||||
edges.push(createEdge(start.id!, execution.id!));
|
edges.push(createEdge(start.id!, execution.id!));
|
||||||
} else {
|
} else {
|
||||||
edges.push(createEdge(prefExecution.id!, execution.id!));
|
const trueConditionLabel = providerConditionFilter(prefExecution)
|
||||||
|
? "true"
|
||||||
|
: "";
|
||||||
|
edges.push(
|
||||||
|
createEdge(prefExecution.id!, execution.id!, trueConditionLabel),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLast) {
|
if (isLast || providerConditionFilter(execution)) {
|
||||||
edges.push(createEdge(execution.id!, end.id!));
|
const falseConditionLabel = providerConditionFilter(execution)
|
||||||
|
? "false"
|
||||||
|
: "";
|
||||||
|
edges.push(createEdge(execution.id!, end.id!, falseConditionLabel));
|
||||||
}
|
}
|
||||||
|
|
||||||
return edges;
|
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 renderSubFlowNodes = (execution: ExpandableExecution): Node[] => {
|
||||||
const nodes: Node[] = [];
|
const nodes: Node[] = [];
|
||||||
|
|
||||||
|
@ -165,7 +200,11 @@ const renderFlowNodes = (executionList: ExpandableExecution[]): Node[] => {
|
||||||
for (let index = 0; index < executionList.length; index++) {
|
for (let index = 0; index < executionList.length; index++) {
|
||||||
const execution = executionList[index];
|
const execution = executionList[index];
|
||||||
if (execution.executionList) {
|
if (execution.executionList) {
|
||||||
|
if (execution.requirement === "CONDITIONAL") {
|
||||||
|
elements = elements.concat(renderConditionalSubFlowNodes(execution));
|
||||||
|
} else {
|
||||||
elements = elements.concat(renderSubFlowNodes(execution));
|
elements = elements.concat(renderSubFlowNodes(execution));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
execution.requirement === "ALTERNATIVE" ||
|
execution.requirement === "ALTERNATIVE" ||
|
||||||
|
@ -191,9 +230,20 @@ const renderFlowEdges = (
|
||||||
for (let index = 0; index < executionList.length; index++) {
|
for (let index = 0; index < executionList.length; index++) {
|
||||||
const execution = executionList[index];
|
const execution = executionList[index];
|
||||||
if (execution.executionList) {
|
if (execution.executionList) {
|
||||||
|
if (execution.requirement === "CONDITIONAL") {
|
||||||
|
elements = elements.concat(
|
||||||
|
renderConditionalSubFlowEdges(
|
||||||
|
execution,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
executionList[index - 1],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
elements = elements.concat(
|
elements = elements.concat(
|
||||||
renderSubFlowEdges(execution, start, end, executionList[index - 1]),
|
renderSubFlowEdges(execution, start, end, executionList[index - 1]),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
execution.requirement === "ALTERNATIVE" ||
|
execution.requirement === "ALTERNATIVE" ||
|
||||||
|
|
|
@ -27,6 +27,7 @@ export const ButtonEdge = ({
|
||||||
targetY,
|
targetY,
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
|
label,
|
||||||
style = {},
|
style = {},
|
||||||
markerType,
|
markerType,
|
||||||
markerEndId,
|
markerEndId,
|
||||||
|
@ -52,6 +53,18 @@ export const ButtonEdge = ({
|
||||||
d={edgePath}
|
d={edgePath}
|
||||||
markerEnd={markerEnd}
|
markerEnd={markerEnd}
|
||||||
/>
|
/>
|
||||||
|
{!selected && (
|
||||||
|
<text>
|
||||||
|
<textPath
|
||||||
|
href={`#${id}`}
|
||||||
|
style={{ fontSize: "11px" }}
|
||||||
|
startOffset="50%"
|
||||||
|
textAnchor="middle"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</textPath>
|
||||||
|
</text>
|
||||||
|
)}
|
||||||
{selected && (
|
{selected && (
|
||||||
<foreignObject
|
<foreignObject
|
||||||
width={foreignObjectSize}
|
width={foreignObjectSize}
|
||||||
|
|
|
@ -5,16 +5,21 @@ const dagreGraph = new graphlib.Graph();
|
||||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||||
|
|
||||||
const nodeWidth = 130;
|
const nodeWidth = 130;
|
||||||
const nodeHeight = 28;
|
const nodeHeight = 40;
|
||||||
|
const nodeAfterConditionalHeight = 130;
|
||||||
|
|
||||||
export const getLayoutedNodes = (nodes: Node[], direction = "LR"): Node[] => {
|
export const getLayoutedNodes = (nodes: Node[], direction = "LR"): Node[] => {
|
||||||
const isHorizontal = direction === "LR";
|
const isHorizontal = direction === "LR";
|
||||||
dagreGraph.setGraph({ rankdir: direction });
|
dagreGraph.setGraph({ rankdir: direction });
|
||||||
|
|
||||||
nodes.forEach((element) => {
|
nodes.forEach((element, index) => {
|
||||||
|
const prevNode = index > 0 ? nodes[index - 1] : undefined;
|
||||||
dagreGraph.setNode(element.id, {
|
dagreGraph.setNode(element.id, {
|
||||||
width: nodeWidth,
|
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.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
|
||||||
|
|
||||||
node.position = {
|
node.position = {
|
||||||
x: nodeWithPosition.x - nodeWidth / 2 + Math.random() / 1000,
|
x: nodeWithPosition.x - nodeWidth / 2,
|
||||||
y: nodeWithPosition.y - nodeHeight / 2,
|
y: nodeWithPosition.y - nodeHeight / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue