UNPKG

@keycloakify/keycloak-admin-ui

Version:
170 lines 6.59 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useMemo, useState } from "react"; import { Background, Controls, MiniMap, Position, ReactFlow, useEdgesState, useNodesState, } from "reactflow"; import { useUpdateEffect } from "../../utils/useUpdateEffect"; import { providerConditionFilter } from "../../authentication/FlowDetails"; import { getLayoutedEdges, getLayoutedNodes } from "../../authentication/components/diagram/auto-layout"; import { ButtonEdge } from "../../authentication/components/diagram/ButtonEdge"; import { ConditionalNode } from "../../authentication/components/diagram/ConditionalNode"; import { EndSubFlowNode, StartSubFlowNode } from "../../authentication/components/diagram/SubFlowNode"; import "reactflow/dist/style.css"; import "./flow-diagram.css"; const nodeTypes = { conditional: ConditionalNode, startSubFlow: StartSubFlowNode, endSubFlow: EndSubFlowNode, }; const inOutClasses = new Map([ ["input", "keycloak__authentication__input_node"], ["output", "keycloak__authentication__output_node"], ]); function pairwise(fn, arr) { const result = []; for (let index = 0; index < arr.length - 1; index++) { result.push(fn(arr[index], arr[index + 1])); } return result; } const isBypassable = (execution) => execution.requirement === "ALTERNATIVE" || execution.requirement === "CONDITIONAL"; const createEdge = (fromNode, toNode, label) => ({ id: `edge-${fromNode}-to-${toNode}`, type: "buttonEdge", source: fromNode, target: toNode, label: label, data: { onEdgeClick: (evt, id) => { evt.stopPropagation(); alert(`hello ${id}`); }, }, }); const createNode = (ex, nodeType) => { return { id: ex.id, type: nodeType, sourcePosition: nodeType === "output" ? undefined : Position.Right, targetPosition: nodeType === "input" ? undefined : Position.Left, data: { label: ex.displayName }, position: { x: 0, y: 0 }, className: inOutClasses.get(nodeType || ""), }; }; const consecutiveBypassableFlows = (executionList) => { const result = []; for (let index = 0; index < executionList.length; index++) { const execution = executionList[index]; if (!isBypassable(execution)) { break; } result.push(execution); } return result; }; const borderStep = (node, continuing = true) => ({ startId: node.id, nodes: [node], edges: [], nextLinkFns: continuing ? [(id) => createEdge(node.id, id)] : [], }); const renderSubFlow = (execution) => { if (!execution.executionList) throw new Error("Execution list is required for subflow"); const graph = createGraph(createConcurrentGroupings(execution.executionList)); graph.nextLinkFns.push(...execution.executionList .filter((e) => providerConditionFilter(e)) .map((e) => (id) => createEdge(e.id, id, "false"))); return graph; }; const groupConcurrentSteps = (executionList) => { const executions = consecutiveBypassableFlows(executionList); if (executions.length > 0) { return executions; } return [executionList[0]]; }; const createConcurrentSteps = (executionList) => { if (executionList.length === 0) { return []; } const executions = groupConcurrentSteps(executionList); return executions.map((execution) => { if (execution.executionList) { return renderSubFlow(execution); } const isConditional = providerConditionFilter(execution); const edgeLabel = (() => { if (isConditional) { return "true"; } if (execution.requirement === "ALTERNATIVE") { return "success"; } })(); return { startId: execution.id, nodes: [createNode(execution, isConditional ? "conditional" : undefined)], edges: [], nextLinkFns: [(id) => createEdge(execution.id, id, edgeLabel)], }; }); }; const createConcurrentGroupings = (executionList) => { if (executionList.length === 0) { return []; } const steps = createConcurrentSteps(executionList); return [ steps, ...createConcurrentGroupings(executionList.slice(steps.length)), ]; }; const createGraph = (groupings) => { const nodes = []; const edges = []; let nextLinkFns = []; for (const group of groupings) { nodes.push(...group.flatMap((g) => g.nodes)); edges.push(...group.flatMap((g) => g.edges), ...nextLinkFns.map((fn) => fn(group[0].startId)), ...pairwise((prev, current) => createEdge(prev.startId, current.startId, "attempted"), group)); nextLinkFns = group.flatMap((g) => g.nextLinkFns); } return { startId: groupings[0][0].startId, nodes, edges, nextLinkFns, }; }; const edgeTypes = { buttonEdge: ButtonEdge, }; function renderGraph(executionList) { const executionListNoDisabled = executionList.filter((e) => e.requirement !== "DISABLED"); const groupings = [ [borderStep(createNode({ id: "start", displayName: "Start" }, "input"))], ...createConcurrentGroupings(executionListNoDisabled), [ borderStep(createNode({ id: "end", displayName: "End" }, "output"), false), ], ]; const { nodes, edges } = createGraph(groupings); return [getLayoutedNodes(nodes), getLayoutedEdges(edges)]; } export const FlowDiagram = ({ executionList: { expandableList }, }) => { const [expandDrawer, setExpandDrawer] = useState(false); const [initialNodes, initialEdges] = useMemo(() => renderGraph(expandableList), []); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); useUpdateEffect(() => { const [nodes, edges] = renderGraph(expandableList); setNodes(nodes); setEdges(edges); }, [expandableList]); const onInit = (reactFlowInstance) => reactFlowInstance.fitView({ duration: 100 }); const onNodeClick = () => { setExpandDrawer(!expandDrawer); }; return (_jsxs(ReactFlow, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onInit: onInit, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onNodeClick: onNodeClick, nodesConnectable: false, children: [_jsx(MiniMap, {}), _jsx(Controls, {}), _jsx(Background, {})] })); }; //# sourceMappingURL=FlowDiagram.js.map