Show missing dependencies in dependency graph instead of failing
When dependencies are not cached on the server (common since we removed proactive caching), the dependency graph now: - Continues resolving what it can find - Shows missing dependencies in a separate section with amber styling - Displays the constraint and which package required them - Updates the header stats to show "X cached • Y not cached" This provides a better user experience than showing an error when some dependencies haven't been downloaded yet.
This commit is contained in:
@@ -42,6 +42,7 @@ from .schemas import (
|
|||||||
ResolvedArtifact,
|
ResolvedArtifact,
|
||||||
DependencyResolutionResponse,
|
DependencyResolutionResponse,
|
||||||
DependencyConflict,
|
DependencyConflict,
|
||||||
|
MissingDependency,
|
||||||
PaginationMeta,
|
PaginationMeta,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -690,6 +691,8 @@ def resolve_dependencies(
|
|||||||
|
|
||||||
# Track resolved artifacts and their versions
|
# Track resolved artifacts and their versions
|
||||||
resolved_artifacts: Dict[str, ResolvedArtifact] = {}
|
resolved_artifacts: Dict[str, ResolvedArtifact] = {}
|
||||||
|
# Track missing dependencies (not cached on server)
|
||||||
|
missing_dependencies: List[MissingDependency] = []
|
||||||
# Track version requirements for conflict detection
|
# Track version requirements for conflict detection
|
||||||
version_requirements: Dict[str, List[Dict[str, Any]]] = {} # pkg_key -> [(version, required_by)]
|
version_requirements: Dict[str, List[Dict[str, Any]]] = {} # pkg_key -> [(version, required_by)]
|
||||||
# Track visiting/visited for cycle detection
|
# Track visiting/visited for cycle detection
|
||||||
@@ -773,12 +776,15 @@ def resolve_dependencies(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not resolved_dep:
|
if not resolved_dep:
|
||||||
|
# Dependency not cached on server - track as missing but continue
|
||||||
constraint = dep.version_constraint or dep.tag_constraint
|
constraint = dep.version_constraint or dep.tag_constraint
|
||||||
raise DependencyNotFoundError(
|
missing_dependencies.append(MissingDependency(
|
||||||
dep.dependency_project,
|
project=dep.dependency_project,
|
||||||
dep.dependency_package,
|
package=dep.dependency_package,
|
||||||
constraint,
|
constraint=constraint,
|
||||||
)
|
required_by=pkg_key,
|
||||||
|
))
|
||||||
|
continue
|
||||||
|
|
||||||
dep_artifact_id, dep_version, dep_size = resolved_dep
|
dep_artifact_id, dep_version, dep_size = resolved_dep
|
||||||
_resolve_recursive(
|
_resolve_recursive(
|
||||||
@@ -828,6 +834,7 @@ def resolve_dependencies(
|
|||||||
"ref": ref,
|
"ref": ref,
|
||||||
},
|
},
|
||||||
resolved=resolved_list,
|
resolved=resolved_list,
|
||||||
|
missing=missing_dependencies,
|
||||||
total_size=total_size,
|
total_size=total_size,
|
||||||
artifact_count=len(resolved_list),
|
artifact_count=len(resolved_list),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1033,10 +1033,19 @@ class ResolvedArtifact(BaseModel):
|
|||||||
download_url: str
|
download_url: str
|
||||||
|
|
||||||
|
|
||||||
|
class MissingDependency(BaseModel):
|
||||||
|
"""A dependency that could not be resolved (not cached on server)"""
|
||||||
|
project: str
|
||||||
|
package: str
|
||||||
|
constraint: Optional[str] = None
|
||||||
|
required_by: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class DependencyResolutionResponse(BaseModel):
|
class DependencyResolutionResponse(BaseModel):
|
||||||
"""Response from dependency resolution endpoint"""
|
"""Response from dependency resolution endpoint"""
|
||||||
requested: Dict[str, str] # project, package, ref
|
requested: Dict[str, str] # project, package, ref
|
||||||
resolved: List[ResolvedArtifact]
|
resolved: List[ResolvedArtifact]
|
||||||
|
missing: List[MissingDependency] = []
|
||||||
total_size: int
|
total_size: int
|
||||||
artifact_count: int
|
artifact_count: int
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,10 @@
|
|||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.missing-count {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -314,6 +318,63 @@
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Missing Dependencies */
|
||||||
|
.missing-dependencies {
|
||||||
|
border-top: 1px solid var(--border-primary);
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: rgba(245, 158, 11, 0.05);
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.missing-dependencies h3 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.missing-hint {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.missing-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.missing-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.missing-name {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.missing-constraint {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.missing-required-by {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.dependency-graph-modal {
|
.dependency-graph-modal {
|
||||||
|
|||||||
@@ -244,7 +244,11 @@ function DependencyGraph({ projectName, packageName, tagName, onClose }: Depende
|
|||||||
<span>{projectName}/{packageName} @ {tagName}</span>
|
<span>{projectName}/{packageName} @ {tagName}</span>
|
||||||
{resolution && (
|
{resolution && (
|
||||||
<span className="graph-stats">
|
<span className="graph-stats">
|
||||||
{resolution.artifact_count} packages • {formatBytes(resolution.total_size)} total
|
{resolution.artifact_count} cached
|
||||||
|
{resolution.missing && resolution.missing.length > 0 && (
|
||||||
|
<span className="missing-count"> • {resolution.missing.length} not cached</span>
|
||||||
|
)}
|
||||||
|
• {formatBytes(resolution.total_size)} total
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -307,6 +311,22 @@ function DependencyGraph({ projectName, packageName, tagName, onClose }: Depende
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{resolution && resolution.missing && resolution.missing.length > 0 && (
|
||||||
|
<div className="missing-dependencies">
|
||||||
|
<h3>Not Cached ({resolution.missing.length})</h3>
|
||||||
|
<p className="missing-hint">These dependencies are referenced but not yet cached on the server.</p>
|
||||||
|
<ul className="missing-list">
|
||||||
|
{resolution.missing.map((dep, i) => (
|
||||||
|
<li key={i} className="missing-item">
|
||||||
|
<span className="missing-name">{dep.project}/{dep.package}</span>
|
||||||
|
{dep.constraint && <span className="missing-constraint">@{dep.constraint}</span>}
|
||||||
|
{dep.required_by && <span className="missing-required-by">← {dep.required_by}</span>}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{hoveredNode && (
|
{hoveredNode && (
|
||||||
<div className="graph-tooltip">
|
<div className="graph-tooltip">
|
||||||
<strong>{hoveredNode.project}/{hoveredNode.package}</strong>
|
<strong>{hoveredNode.project}/{hoveredNode.package}</strong>
|
||||||
|
|||||||
@@ -433,6 +433,13 @@ export interface ResolvedArtifact {
|
|||||||
download_url: string;
|
download_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MissingDependency {
|
||||||
|
project: string;
|
||||||
|
package: string;
|
||||||
|
constraint: string | null;
|
||||||
|
required_by: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DependencyResolutionResponse {
|
export interface DependencyResolutionResponse {
|
||||||
requested: {
|
requested: {
|
||||||
project: string;
|
project: string;
|
||||||
@@ -440,6 +447,7 @@ export interface DependencyResolutionResponse {
|
|||||||
ref: string;
|
ref: string;
|
||||||
};
|
};
|
||||||
resolved: ResolvedArtifact[];
|
resolved: ResolvedArtifact[];
|
||||||
|
missing: MissingDependency[];
|
||||||
total_size: number;
|
total_size: number;
|
||||||
artifact_count: number;
|
artifact_count: number;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user