generated from coulomb/repo-seed
Add graph orientation details
This commit is contained in:
@@ -137,6 +137,10 @@ def graph_explorer_page() -> str:
|
||||
<div id="detail-pills"></div>
|
||||
<ul id="detail-list" class="detail-list"></ul>
|
||||
</section>
|
||||
<section class="section">
|
||||
<p id="orientation-title" class="meta">Select a service, interface, dependency, or registered-only repo.</p>
|
||||
<ul id="orientation-list" class="detail-list"></ul>
|
||||
</section>
|
||||
<section class="section">
|
||||
<div class="button-row">
|
||||
<button type="button" data-override="show">Show</button>
|
||||
@@ -181,6 +185,8 @@ def graph_explorer_page() -> str:
|
||||
const detailSummary = document.getElementById("detail-summary");
|
||||
const detailPills = document.getElementById("detail-pills");
|
||||
const detailList = document.getElementById("detail-list");
|
||||
const orientationTitle = document.getElementById("orientation-title");
|
||||
const orientationList = document.getElementById("orientation-list");
|
||||
const legend = document.getElementById("legend");
|
||||
let cy = null;
|
||||
let selected = null;
|
||||
@@ -268,6 +274,8 @@ def graph_explorer_page() -> str:
|
||||
detailSummary.textContent = "No selection";
|
||||
detailPills.innerHTML = "";
|
||||
detailList.innerHTML = "";
|
||||
orientationTitle.textContent = "Select a service, interface, dependency, or registered-only repo.";
|
||||
orientationList.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
const data = element.data();
|
||||
@@ -291,6 +299,78 @@ def graph_explorer_page() -> str:
|
||||
.filter(([, value]) => value)
|
||||
.map(([key, value]) => `<li><strong>${escapeHtml(key)}</strong> ${escapeHtml(value)}</li>`)
|
||||
.join("");
|
||||
renderOrientation(element);
|
||||
};
|
||||
|
||||
const renderOrientation = (element) => {
|
||||
const data = element.data();
|
||||
const rows = [];
|
||||
if (data.kind === "Repository" && data.lifecycle === "registered-only") {
|
||||
orientationTitle.textContent = "Onboarding gap";
|
||||
rows.push(["repo", data.repo], ["next", "sync Fabric declarations or register a graph snapshot"]);
|
||||
} else if (data.kind === "InterfaceDeclaration") {
|
||||
orientationTitle.textContent = "Interface consumers";
|
||||
cy.edges().filter((edge) =>
|
||||
edge.data("target") === data.id && edge.data("edgeType") === "uses_interface"
|
||||
).forEach((edge) => {
|
||||
const dependency = edge.data("source");
|
||||
const consumerEdge = cy.edges().filter((candidate) =>
|
||||
candidate.data("target") === dependency && candidate.data("edgeType") === "consumes"
|
||||
)[0];
|
||||
const consumerId = consumerEdge ? consumerEdge.data("source") : "";
|
||||
const consumer = consumerId ? cy.getElementById(consumerId).data("name") || consumerId : "unknown";
|
||||
rows.push(["consumer", `${consumer} -> ${dependency}`]);
|
||||
});
|
||||
if (rows.length === 0) rows.push(["consumer", "no accepted consumers in current graph"]);
|
||||
} else if (data.kind === "ServiceDeclaration") {
|
||||
orientationTitle.textContent = "Dependency path";
|
||||
cy.edges().filter((edge) =>
|
||||
edge.data("source") === data.id && edge.data("edgeType") === "consumes"
|
||||
).forEach((edge) => {
|
||||
const dependency = edge.data("target");
|
||||
const providerEdges = cy.edges().filter((candidate) =>
|
||||
candidate.data("source") === dependency && String(candidate.data("edgeType")).startsWith("binds:")
|
||||
);
|
||||
if (providerEdges.length === 0) {
|
||||
rows.push(["requires", `${dependency} -> unresolved`]);
|
||||
return;
|
||||
}
|
||||
providerEdges.forEach((providerEdge) => {
|
||||
const target = providerEdge.data("target");
|
||||
const provider = cy.getElementById(target).data("name") || target;
|
||||
rows.push(["requires", `${dependency} -> ${provider} (${providerEdge.data("edgeType")})`]);
|
||||
});
|
||||
});
|
||||
if (rows.length === 0) rows.push(["requires", "no declared dependencies"]);
|
||||
} else if (data.kind === "DependencyDeclaration") {
|
||||
orientationTitle.textContent = "Dependency binding";
|
||||
cy.edges().filter((edge) =>
|
||||
edge.data("source") === data.id && (
|
||||
String(edge.data("edgeType")).startsWith("binds:") ||
|
||||
edge.data("edgeType") === "uses_interface"
|
||||
)
|
||||
).forEach((edge) => {
|
||||
const target = edge.data("target");
|
||||
const targetName = cy.getElementById(target).data("name") || target;
|
||||
rows.push([edge.data("edgeType"), targetName]);
|
||||
});
|
||||
if (rows.length === 0) rows.push(["binding", "no provider binding in current graph"]);
|
||||
} else if (data.kind === "CapabilityDeclaration") {
|
||||
orientationTitle.textContent = "Provider surface";
|
||||
cy.edges().filter((edge) =>
|
||||
edge.data("target") === data.id && edge.data("edgeType") === "provides"
|
||||
).forEach((edge) => rows.push(["service", cy.getElementById(edge.data("source")).data("name") || edge.data("source")]));
|
||||
cy.edges().filter((edge) =>
|
||||
edge.data("source") === data.id && edge.data("edgeType") === "available_via"
|
||||
).forEach((edge) => rows.push(["interface", cy.getElementById(edge.data("target")).data("name") || edge.data("target")]));
|
||||
if (rows.length === 0) rows.push(["surface", "no provider surface in current graph"]);
|
||||
} else {
|
||||
orientationTitle.textContent = "Graph context";
|
||||
rows.push(["neighbors", `${element.neighborhood().nodes().length} connected nodes`]);
|
||||
}
|
||||
orientationList.innerHTML = rows
|
||||
.map(([key, value]) => `<li><strong>${escapeHtml(key)}</strong> ${escapeHtml(value)}</li>`)
|
||||
.join("");
|
||||
};
|
||||
|
||||
const applyFocus = () => {
|
||||
|
||||
@@ -190,6 +190,9 @@ def test_registry_serves_graph_explorer_exports(tmp_path: Path) -> None:
|
||||
assert 'id="mode-select"' in page
|
||||
assert 'id="layout-select"' in page
|
||||
assert 'id="profile-select"' in page
|
||||
assert 'id="orientation-list"' in page
|
||||
assert "Interface consumers" in page
|
||||
assert "Dependency path" in page
|
||||
assert "cytoscape.min.js" in page
|
||||
assert "/exports/graph-explorer/manifest" in page
|
||||
assert 'data-override="hide"' in page
|
||||
|
||||
@@ -205,7 +205,7 @@ Acceptance notes:
|
||||
|
||||
```task
|
||||
id: RAIL-FAB-WP-0008-T04
|
||||
status: in_progress
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "75c1f234-026c-44ed-9c88-db39653b81e0"
|
||||
```
|
||||
@@ -228,7 +228,7 @@ Acceptance notes:
|
||||
|
||||
```task
|
||||
id: RAIL-FAB-WP-0008-T05
|
||||
status: todo
|
||||
status: in_progress
|
||||
priority: medium
|
||||
state_hub_task_id: "64fe53f1-fbea-4624-8f52-1b5e2a27cf67"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user