generated from coulomb/repo-seed
Dependency graph vizualization
This commit is contained in:
@@ -1090,6 +1090,115 @@ class RegistryService:
|
||||
graph=graph,
|
||||
)
|
||||
|
||||
def dependency_graph_elements(
|
||||
self,
|
||||
repository_id: int,
|
||||
*,
|
||||
base_analysis_run_id: int | None = None,
|
||||
target_analysis_run_id: int | None = None,
|
||||
) -> dict[str, object]:
|
||||
impact = None
|
||||
if base_analysis_run_id is not None or target_analysis_run_id is not None:
|
||||
if base_analysis_run_id is None or target_analysis_run_id is None:
|
||||
raise ValueError(
|
||||
"base_analysis_run_id and target_analysis_run_id must be provided together"
|
||||
)
|
||||
impact = self.analyze_dependency_impact(
|
||||
repository_id,
|
||||
base_analysis_run_id,
|
||||
target_analysis_run_id,
|
||||
)
|
||||
graph = impact.graph
|
||||
else:
|
||||
graph = self.build_dependency_graph(repository_id)
|
||||
|
||||
impact_by_key = (
|
||||
{item.item_key: item for item in impact.impacts} if impact is not None else {}
|
||||
)
|
||||
changed_fact_keys = set(impact.changed_fact_keys) if impact is not None else set()
|
||||
nodes: dict[str, dict[str, object]] = {}
|
||||
|
||||
def ensure_node(kind: str, key: str, item_id: int | None) -> None:
|
||||
if key in nodes:
|
||||
return
|
||||
impact_item = impact_by_key.get(key)
|
||||
is_changed_fact = key in changed_fact_keys
|
||||
nodes[key] = {
|
||||
"data": {
|
||||
"id": key,
|
||||
"kind": kind,
|
||||
"label": self._dependency_node_label(repository_id, kind, key, item_id),
|
||||
"ownership": self._ownership_for_kind(kind),
|
||||
"freshnessState": (
|
||||
impact_item.freshness_state
|
||||
if impact_item is not None
|
||||
else "changed"
|
||||
if is_changed_fact
|
||||
else "current"
|
||||
),
|
||||
"recommendedAction": (
|
||||
impact_item.recommended_action if impact_item is not None else ""
|
||||
),
|
||||
"impactDepth": (
|
||||
impact_item.impact_depth if impact_item is not None else None
|
||||
),
|
||||
"reasons": impact_item.reasons if impact_item is not None else [],
|
||||
},
|
||||
"classes": " ".join(
|
||||
class_name
|
||||
for class_name in (
|
||||
kind,
|
||||
"stale" if impact_item is not None else "current",
|
||||
"changed" if is_changed_fact else "",
|
||||
)
|
||||
if class_name
|
||||
),
|
||||
}
|
||||
|
||||
for edge in graph.edges:
|
||||
ensure_node(edge.source_kind, edge.source_key, edge.source_id)
|
||||
ensure_node(edge.target_kind, edge.target_key, edge.target_id)
|
||||
|
||||
edges = [
|
||||
{
|
||||
"data": {
|
||||
"id": f"{edge.source_key}->{edge.target_key}:{index}",
|
||||
"source": edge.source_key,
|
||||
"target": edge.target_key,
|
||||
"dependencyType": edge.dependency_type,
|
||||
"strength": edge.strength,
|
||||
"edgeSource": edge.source,
|
||||
"sameLayer": edge.same_layer,
|
||||
"label": edge.dependency_type,
|
||||
},
|
||||
"classes": " ".join(
|
||||
class_name
|
||||
for class_name in (
|
||||
edge.dependency_type,
|
||||
edge.strength,
|
||||
"same-layer" if edge.same_layer else "",
|
||||
)
|
||||
if class_name
|
||||
),
|
||||
}
|
||||
for index, edge in enumerate(graph.edges)
|
||||
]
|
||||
return {
|
||||
"repository": asdict(graph.repository),
|
||||
"scope": asdict(graph.scope),
|
||||
"mode": "impact" if impact is not None else "full",
|
||||
"metrics": {
|
||||
"node_count": len(nodes),
|
||||
"edge_count": len(edges),
|
||||
"propagation_breadth": impact.propagation_breadth if impact else 0,
|
||||
"max_depth": impact.max_depth if impact else 0,
|
||||
"scope_impacted": impact.scope_impacted if impact else False,
|
||||
},
|
||||
"changed_fact_keys": impact.changed_fact_keys if impact else [],
|
||||
"elements": [*nodes.values(), *edges],
|
||||
"impacts": [asdict(item) for item in impact.impacts] if impact else [],
|
||||
}
|
||||
|
||||
def approve_analysis_run_changes(
|
||||
self,
|
||||
repository_id: int,
|
||||
@@ -2300,6 +2409,24 @@ class RegistryService:
|
||||
return evidence.reference
|
||||
return f"{kind}:{item_id}"
|
||||
|
||||
def _dependency_node_label(
|
||||
self,
|
||||
repository_id: int,
|
||||
kind: str,
|
||||
key: str,
|
||||
item_id: int | None,
|
||||
) -> str:
|
||||
if item_id is not None and kind != "fact":
|
||||
return self._dependency_display_name(repository_id, kind, item_id)
|
||||
if kind == "fact":
|
||||
parts = key.split(":", 3)
|
||||
if len(parts) == 4:
|
||||
_, fact_kind, path, name = parts
|
||||
return f"{name} ({fact_kind}, {path})"
|
||||
if item_id is not None:
|
||||
return f"{kind}:{item_id}"
|
||||
return key
|
||||
|
||||
def _chunk_index(
|
||||
self,
|
||||
chunks: Sequence[ContentChunk],
|
||||
|
||||
Reference in New Issue
Block a user