fix(graph): traverse all dependency types, not just parent-child
The graph command now shows issues connected via any dependency type (blocks, parent-child, etc.), not just parent-child relationships. This makes bd graph useful for epics where children are connected via blocks dependencies rather than hierarchical parent-child. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -108,9 +108,73 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loadGraphSubgraph loads an issue and its subgraph for visualization
|
// loadGraphSubgraph loads an issue and its subgraph for visualization
|
||||||
// Reuses template subgraph loading logic
|
// Unlike template loading, this includes ALL dependency types (not just parent-child)
|
||||||
func loadGraphSubgraph(ctx context.Context, s storage.Storage, issueID string) (*TemplateSubgraph, error) {
|
func loadGraphSubgraph(ctx context.Context, s storage.Storage, issueID string) (*TemplateSubgraph, error) {
|
||||||
return loadTemplateSubgraph(ctx, s, issueID)
|
if s == nil {
|
||||||
|
return nil, fmt.Errorf("no database connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the root issue
|
||||||
|
root, err := s.GetIssue(ctx, issueID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get issue: %w", err)
|
||||||
|
}
|
||||||
|
if root == nil {
|
||||||
|
return nil, fmt.Errorf("issue %s not found", issueID)
|
||||||
|
}
|
||||||
|
|
||||||
|
subgraph := &TemplateSubgraph{
|
||||||
|
Root: root,
|
||||||
|
Issues: []*types.Issue{root},
|
||||||
|
IssueMap: map[string]*types.Issue{root.ID: root},
|
||||||
|
}
|
||||||
|
|
||||||
|
// BFS to find all connected issues (via any dependency type)
|
||||||
|
// We traverse both directions: dependents and dependencies
|
||||||
|
queue := []string{root.ID}
|
||||||
|
visited := map[string]bool{root.ID: true}
|
||||||
|
|
||||||
|
for len(queue) > 0 {
|
||||||
|
currentID := queue[0]
|
||||||
|
queue = queue[1:]
|
||||||
|
|
||||||
|
// Get issues that depend on this one (dependents)
|
||||||
|
dependents, err := s.GetDependents(ctx, currentID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, dep := range dependents {
|
||||||
|
if !visited[dep.ID] {
|
||||||
|
visited[dep.ID] = true
|
||||||
|
subgraph.Issues = append(subgraph.Issues, dep)
|
||||||
|
subgraph.IssueMap[dep.ID] = dep
|
||||||
|
queue = append(queue, dep.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get issues this one depends on (dependencies) - but only for non-root
|
||||||
|
// to avoid pulling in unrelated upstream issues
|
||||||
|
if currentID == root.ID {
|
||||||
|
// For root, we might want to include direct dependencies too
|
||||||
|
// Skip for now to keep graph focused on "what this issue encompasses"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all dependencies within the subgraph
|
||||||
|
for _, issue := range subgraph.Issues {
|
||||||
|
deps, err := s.GetDependencyRecords(ctx, issue.ID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, dep := range deps {
|
||||||
|
// Only include dependencies where both ends are in the subgraph
|
||||||
|
if _, ok := subgraph.IssueMap[dep.DependsOnID]; ok {
|
||||||
|
subgraph.Dependencies = append(subgraph.Dependencies, dep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subgraph, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// computeLayout assigns layers to nodes using topological sort
|
// computeLayout assigns layers to nodes using topological sort
|
||||||
|
|||||||
Reference in New Issue
Block a user