feat(dashboard): aggregateContainerEdges (directional, types-set)

This commit is contained in:
Lum1104
2026-05-03 15:57:28 +08:00
Unverified
parent 74342da070
commit 46dc0485b9
2 changed files with 132 additions and 1 deletions
@@ -0,0 +1,61 @@
import { describe, it, expect } from "vitest";
import { aggregateContainerEdges } from "../edgeAggregation";
import type { GraphEdge } from "@understand-anything/core/types";
const ce = (source: string, target: string, type: string = "calls"): GraphEdge =>
({ source, target, type }) as GraphEdge;
describe("aggregateContainerEdges", () => {
it("returns empty arrays for empty input", () => {
const r = aggregateContainerEdges([], new Map());
expect(r.intraContainer).toEqual([]);
expect(r.interContainerAggregated).toEqual([]);
});
it("preserves intra-container edges as-is", () => {
const m = new Map([
["a", "auth"],
["b", "auth"],
]);
const r = aggregateContainerEdges([ce("a", "b")], m);
expect(r.intraContainer).toHaveLength(1);
expect(r.interContainerAggregated).toEqual([]);
});
it("merges multiple same-direction inter edges into one", () => {
const m = new Map([
["a", "auth"],
["b", "auth"],
["c", "cart"],
["d", "cart"],
]);
const edges = [ce("a", "c"), ce("a", "d"), ce("b", "c", "imports")];
const r = aggregateContainerEdges(edges, m);
expect(r.interContainerAggregated).toHaveLength(1);
const agg = r.interContainerAggregated[0];
expect(agg.sourceContainerId).toBe("auth");
expect(agg.targetContainerId).toBe("cart");
expect(agg.count).toBe(3);
expect(agg.types.sort()).toEqual(["calls", "imports"]);
});
it("treats opposite directions as separate aggregated edges", () => {
const m = new Map([
["a", "auth"],
["c", "cart"],
]);
const r = aggregateContainerEdges([ce("a", "c"), ce("c", "a")], m);
expect(r.interContainerAggregated).toHaveLength(2);
const dirs = r.interContainerAggregated.map(
(e) => `${e.sourceContainerId}${e.targetContainerId}`,
);
expect(dirs.sort()).toEqual(["auth→cart", "cart→auth"]);
});
it("ignores edges whose endpoints have no container mapping", () => {
const m = new Map([["a", "auth"]]);
const r = aggregateContainerEdges([ce("a", "z")], m);
expect(r.intraContainer).toEqual([]);
expect(r.interContainerAggregated).toEqual([]);
});
});
@@ -1,4 +1,4 @@
import type { KnowledgeGraph } from "@understand-anything/core/types";
import type { GraphEdge, KnowledgeGraph } from "@understand-anything/core/types";
export interface LayerEdgeAggregation {
sourceLayerId: string;
@@ -133,3 +133,73 @@ export function findCrossLayerFileNodes(
}
return result;
}
export interface AggregatedContainerEdge {
sourceContainerId: string;
targetContainerId: string;
count: number;
types: string[];
}
export interface ContainerEdgeBuckets {
intraContainer: GraphEdge[];
interContainerAggregated: AggregatedContainerEdge[];
}
/**
* Bucket edges into intra-container (preserved) and inter-container
* (aggregated by directed (source,target) container pair).
*
* Direction is significant: A→B and B→A produce two independent
* aggregated edges. Edges whose endpoints have no container mapping
* are dropped (treat them as pre-filtered).
*/
export function aggregateContainerEdges(
edges: GraphEdge[],
nodeToContainer: Map<string, string>,
): ContainerEdgeBuckets {
const intra: GraphEdge[] = [];
const interMap = new Map<
string,
{
sourceContainerId: string;
targetContainerId: string;
count: number;
types: Set<string>;
}
>();
for (const e of edges) {
const sc = nodeToContainer.get(e.source);
const tc = nodeToContainer.get(e.target);
if (!sc || !tc) continue;
if (sc === tc) {
intra.push(e);
continue;
}
const key = `${sc} ${tc}`;
const existing = interMap.get(key);
if (existing) {
existing.count++;
existing.types.add(e.type);
} else {
interMap.set(key, {
sourceContainerId: sc,
targetContainerId: tc,
count: 1,
types: new Set([e.type]),
});
}
}
const interContainerAggregated: AggregatedContainerEdge[] = [...interMap.values()].map(
(v) => ({
sourceContainerId: v.sourceContainerId,
targetContainerId: v.targetContainerId,
count: v.count,
types: [...v.types],
}),
);
return { intraContainer: intra, interContainerAggregated };
}