DEV Community

Rahul
Rahul

Posted on

DependencyGraph

import dagre from "dagre";
import {
Background,
BackgroundVariant,
Controls,
Handle,
MarkerType,
MiniMap,
Panel,
Position,
ReactFlow,
type Edge,
type Node,
type NodeProps,
} from "@xyflow/react";
import {
Box,
Braces,
Database,
ExternalLink,
FunctionSquare,
} from "lucide-react";
import { useMemo } from "react";
import { field, type ApiItem } from "../lib/api";

type GraphNodeData = {
label: string;
kind: "package" | "subprogram" | "table" | "external";
eyebrow: string;
};

type GraphNode = Node;

function inferKind(label: string, relationship = ""): GraphNodeData["kind"] {
const normalized = ${label} ${relationship}.toUpperCase();
if (
normalized.includes("TABLE") ||
normalized.includes("VIEW") ||
normalized.includes("SELECT") ||
normalized.includes("INSERT") ||
normalized.includes("UPDATE") ||
normalized.includes("DELETE") ||
normalized.includes("MERGE")
) {
return "table";
}
if (
normalized.includes("HTTP") ||
normalized.includes("FILE") ||
normalized.includes("MAIL") ||
normalized.includes("EXTERNAL") ||
normalized.includes("DBLINK")
) {
return "external";
}
if (
normalized.includes("PROCEDURE") ||
normalized.includes("FUNCTION") ||
normalized.includes("CALL")
) {
return "subprogram";
}
return "package";
}

function kindLabel(kind: GraphNodeData["kind"]) {
return {
package: "Package / object",
subprogram: "Procedure / function",
table: "Table / view",
external: "External system",
}[kind];
}

function NodeIcon({ kind }: { kind: GraphNodeData["kind"] }) {
if (kind === "table") return ;
if (kind === "subprogram") return ;
if (kind === "external") return ;
return ;
}

function InsightNode({ data, selected }: NodeProps) {
return (







{data.eyebrow}
{data.label}



);
}

const nodeTypes = { insight: InsightNode };

function layoutGraph(nodes: GraphNode[], edges: Edge[]) {
const graph = new dagre.graphlib.Graph();
graph.setDefaultEdgeLabel(() => ({}));
graph.setGraph({
rankdir: "LR",
nodesep: 46,
ranksep: 112,
marginx: 34,
marginy: 34,
});

nodes.forEach((node) => graph.setNode(node.id, { width: 230, height: 72 }));
edges.forEach((edge) => graph.setEdge(edge.source, edge.target));
dagre.layout(graph);

return nodes.map((node) => {
const point = graph.node(node.id);
return {
...node,
position: { x: point.x - 115, y: point.y - 36 },
};
});
}

export function DependencyGraph({ edges: rawEdges }: { edges: ApiItem[] }) {
const { nodes, edges } = useMemo(() => {
const nodeMap = new Map();
const graphEdges: Edge[] = [];

rawEdges.forEach((item, index) => {
  const source = String(
    field(item, "SOURCE_NODE", field(item, "source", "Source")),
  );
  const target = String(
    field(item, "TARGET_NODE", field(item, "target", "Target")),
  );
  const relationship = String(
    field(
      item,
      "RELATIONSHIP_TYPE",
      field(item, "relationship", "DEPENDS_ON"),
    ),
  );
  const sourceKind = inferKind(source);
  const targetKind = inferKind(target, relationship);
  if (!nodeMap.has(source)) {
    nodeMap.set(source, {
      label: source,
      kind: sourceKind,
      eyebrow: kindLabel(sourceKind),
    });
  }
  if (!nodeMap.has(target)) {
    nodeMap.set(target, {
      label: target,
      kind: targetKind,
      eyebrow: kindLabel(targetKind),
    });
  }
  graphEdges.push({
    id: `edge-${index}-${source}-${target}`,
    source,
    target,
    type: "smoothstep",
    label: relationship.replaceAll("_", " "),
    markerEnd: { type: MarkerType.ArrowClosed, color: "#7796ff" },
    style: { stroke: "#7796ff", strokeWidth: 1.7 },
    labelStyle: {
      fill: "#aebbd0",
      fontSize: 10,
      fontWeight: 700,
    },
    labelBgStyle: {
      fill: "#111a28",
      fillOpacity: 0.94,
    },
    labelBgPadding: [7, 4],
    labelBgBorderRadius: 6,
  });
});

const graphNodes: GraphNode[] = Array.from(nodeMap.entries()).map(
  ([id, data]) => ({
    id,
    type: "insight",
    data,
    position: { x: 0, y: 0 },
  }),
);
return {
  nodes: layoutGraph(graphNodes, graphEdges),
  edges: graphEdges,
};
Enter fullscreen mode Exit fullscreen mode

}, [rawEdges]);

if (!rawEdges.length) {
return (



No dependency edges yet

Analyze an object first. Calls, tables, package containment, and
external systems will appear here.


);
}

return (


nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
fitView
fitViewOptions={{ padding: 0.24 }}
minZoom={0.28}
maxZoom={1.8}
nodesConnectable={false}
deleteKeyCode={null}
proOptions={{ hideAttribution: true }}
>
variant={BackgroundVariant.Dots}
gap={22}
size={1.2}
color="#263248"
/>

position="bottom-right"
pannable
zoomable
nodeStrokeWidth={3}
nodeColor={(node) => {
const kind = (node.data as GraphNodeData).kind;
if (kind === "table") return "#4de2c5";
if (kind === "external") return "#ffbc70";
if (kind === "subprogram") return "#a99eff";
return "#7796ff";
}}
maskColor="rgba(5, 9, 16, .72)"
/>

{(["package", "subprogram", "table", "external"] as const).map(
(kind) => (


{kindLabel(kind)}

),
)}



);
}

Top comments (0)