forked from baron/baron-sso
88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
import type { TenantSummary } from "./adminApi";
|
|
|
|
export type TenantNode = TenantSummary & {
|
|
children: TenantNode[];
|
|
recursiveMemberCount: number;
|
|
};
|
|
|
|
/**
|
|
* Builds a hierarchical tree from a flat list of tenants and calculates
|
|
* direct and recursive member counts for each node.
|
|
*/
|
|
export function buildTenantFullTree(
|
|
allTenants: TenantSummary[],
|
|
rootId?: string,
|
|
): { currentBase: TenantNode | null; subTree: TenantNode[] } {
|
|
if (allTenants.length === 0) return { currentBase: null, subTree: [] };
|
|
|
|
const tenantMap = new Map<string, TenantNode>();
|
|
for (const t of allTenants) {
|
|
tenantMap.set(t.id, {
|
|
...t,
|
|
children: [],
|
|
recursiveMemberCount: t.memberCount || 0,
|
|
});
|
|
}
|
|
|
|
const visitedDuringBuild = new Set<string>();
|
|
// Build initial children relations and prevent simple cycles
|
|
for (const t of allTenants) {
|
|
if (t.parentId && t.parentId !== t.id) {
|
|
const parent = tenantMap.get(t.parentId);
|
|
const child = tenantMap.get(t.id);
|
|
if (parent && child) {
|
|
// Simple cycle prevention during build: don't add if it creates an immediate loop
|
|
parent.children.push(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
const visitedForCalc = new Set<string>();
|
|
// Function to calculate recursive counts with cycle protection
|
|
const calculateRecursive = (node: TenantNode): number => {
|
|
if (visitedForCalc.has(node.id)) {
|
|
console.warn(
|
|
`Circular dependency detected in tenant tree for ID: ${node.id}`,
|
|
);
|
|
return 0; // Prevent infinite loop
|
|
}
|
|
visitedForCalc.add(node.id);
|
|
|
|
let total = node.memberCount || 0;
|
|
for (const child of node.children) {
|
|
total += calculateRecursive(child);
|
|
}
|
|
node.recursiveMemberCount = total;
|
|
|
|
// We don't remove from visitedForCalc here because a tree shouldn't have
|
|
// multiple paths to the same node anyway (it's a tree, not a graph).
|
|
// If it were a DAG, we'd need different logic, but for a tree with parentIds,
|
|
// a node should only be visited once.
|
|
return total;
|
|
};
|
|
|
|
// Calculate for all top-level nodes (those without parent)
|
|
for (const node of tenantMap.values()) {
|
|
if (!node.parentId) {
|
|
visitedForCalc.clear();
|
|
calculateRecursive(node);
|
|
}
|
|
}
|
|
|
|
// If a specific rootId is provided, find and return its subtree
|
|
if (rootId) {
|
|
const base = tenantMap.get(rootId);
|
|
if (base) {
|
|
// Re-calculate specifically for our current tenant to be sure if it wasn't a global root
|
|
visitedForCalc.clear();
|
|
calculateRecursive(base);
|
|
return { currentBase: base, subTree: base.children };
|
|
}
|
|
return { currentBase: null, subTree: [] };
|
|
}
|
|
|
|
// If no rootId, return all top-level roots as subTree
|
|
const roots = Array.from(tenantMap.values()).filter((n) => !n.parentId);
|
|
return { currentBase: null, subTree: roots };
|
|
}
|