feat(FN-3509): document node mapping availability UI in dashboard guide

Documents the node mapping availability UI in the dashboard guide.

Fusion-Task-Id: FN-3509
This commit is contained in:
Fusion
2026-05-08 18:40:00 -07:00
committed by gsxdsm
parent 6ab07c27d0
commit eed16abacb
13 changed files with 464 additions and 730 deletions

View File

@@ -1,75 +1,55 @@
/**
* Node-Project Assignment Utilities
*
* Provides canonical counting logic for projects routed to a node.
*
* **Runtime Behavior:**
* - Projects with `nodeId` pointing to a remote node → run on that remote node
* - Projects with `nodeId` pointing to a local node → run on that local node
* - Projects without `nodeId` (unassigned) → run on local in-process runtime
*
* **Counting Rules:**
* - Local nodes: include both explicitly-assigned projects AND unassigned projects
* - Remote nodes: include only explicitly-assigned projects
*/
import type { NodeInfo, ProjectInfoWithSource, ProjectNodeAvailability } from "../api";
import type { NodeInfo, ProjectInfo } from "../api";
/**
* Check if a project is routed to a specific node based on runtime rules.
*
* @param project - The project to check
* @param node - The node to check against
* @returns true if the project runs on this node
*/
export function isProjectRoutedToNode(project: ProjectInfo, node: NodeInfo): boolean {
if (node.type === "remote") {
// Remote nodes: only explicit assignment counts
return project.nodeId === node.id;
}
// Local nodes: explicit assignment OR unassigned (null/undefined)
if (project.nodeId === node.id) {
return true;
}
// Unassigned projects run on local in-process runtime
if (project.nodeId === undefined || project.nodeId === null) {
return true;
}
return false;
function isNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.trim().length > 0;
}
/**
* Get all projects that are routed to a specific node.
*
* @param projects - All projects
* @param node - The node to filter by
* @returns Projects routed to this node
*/
export function getProjectsForNode(projects: ProjectInfo[], node: NodeInfo): ProjectInfo[] {
return projects.filter((project) => isProjectRoutedToNode(project, node));
export function getNodeMappingsForProject(project: ProjectInfoWithSource): ProjectNodeAvailability[] {
const mappings = project.nodeMappings ?? [];
return mappings
.filter((mapping) => isNonEmptyString(mapping.nodeId) && isNonEmptyString(mapping.path))
.map((mapping) => ({
nodeId: mapping.nodeId,
nodeName: mapping.nodeName,
path: mapping.path,
available: mapping.available !== false,
}));
}
/**
* Get the count of projects routed to a specific node.
*
* @param projects - All projects
* @param node - The node to count projects for
* @returns Number of projects on this node
*/
export function getProjectCountForNode(projects: ProjectInfo[], node: NodeInfo): number {
export function getAvailableNodeMappingsForNode(
project: ProjectInfoWithSource,
node: NodeInfo,
): ProjectNodeAvailability[] {
return getNodeMappingsForProject(project).filter(
(mapping) => mapping.nodeId === node.id && mapping.available,
);
}
export function isProjectAvailableOnNode(project: ProjectInfoWithSource, node: NodeInfo): boolean {
return getAvailableNodeMappingsForNode(project, node).length > 0;
}
export function getProjectsForNode(projects: ProjectInfoWithSource[], node: NodeInfo): ProjectInfoWithSource[] {
return projects.filter((project) => isProjectAvailableOnNode(project, node));
}
export function getProjectCountForNode(projects: ProjectInfoWithSource[], node: NodeInfo): number {
return getProjectsForNode(projects, node).length;
}
/**
* Get the count of unassigned projects (projects without nodeId).
* These projects run on the local in-process runtime.
*
* @param projects - All projects
* @returns Number of unassigned projects
*/
export function getUnassignedProjectCount(projects: ProjectInfo[]): number {
return projects.filter((project) => project.nodeId === undefined || project.nodeId === null).length;
export function resolveNodeDisplayName(
nodeId: string,
mapping: ProjectNodeAvailability | undefined,
nodes: NodeInfo[],
project: ProjectInfoWithSource,
): string {
const nodeMatch = nodes.find((node) => node.id === nodeId);
if (nodeMatch?.name) return nodeMatch.name;
if (mapping?.nodeName) return mapping.nodeName;
if (project._sourceNodeName) return project._sourceNodeName;
return nodeId;
}
export function getUnassignedProjectCount(projects: ProjectInfoWithSource[]): number {
return projects.filter((project) => getNodeMappingsForProject(project).length === 0).length;
}