Class: TrakFlow::Graph::DependencyGraph
- Inherits:
-
Object
- Object
- TrakFlow::Graph::DependencyGraph
- Defined in:
- lib/trak_flow/graph/dependency_graph.rb
Overview
Dependency graph operations for visualizing and analyzing task relationships
Constant Summary collapse
- COLORS =
Graph visualization colors (dark theme compatible)
{ status: { closed: "#4a5568", tombstone: "#4a5568", in_progress: "#3182ce", blocked: "#e53e3e", deferred: "#d69e2e", pinned: "#805ad5" }, priority: { critical: "#e53e3e", high: "#ed8936", medium: "#48bb78", low: "#4299e1", backlog: "#a0aec0" }, edge: { blocks: "#e53e3e", parent_child: "#3182ce", related: "#a0aec0", discovered_from: "#805ad5" } }.freeze
Instance Method Summary collapse
-
#all_blocked(task_id) ⇒ Object
Find all tasks blocked by the given task (directly or transitively).
-
#all_blockers(task_id) ⇒ Object
Find all tasks that block the given task (directly or transitively).
-
#analyze ⇒ Object
Analyze the graph for potential problems.
-
#critical_path(root_task_id) ⇒ Object
Find the critical path - longest chain of blocking dependencies.
-
#dependency_tree(task_id, direction: :blocking, max_depth: 10) ⇒ Object
Build a tree representation of dependencies for a task.
-
#initialize(db) ⇒ DependencyGraph
constructor
A new instance of DependencyGraph.
-
#leaf_tasks ⇒ Object
Get all leaf tasks (tasks with no children/blocked tasks).
-
#root_tasks ⇒ Object
Get all root tasks (tasks with no parents/blockers).
-
#to_dot(task_ids: nil, include_closed: false) ⇒ Object
Generate a DOT representation for Graphviz.
-
#to_svg(task_ids: nil, include_closed: false) ⇒ Object
Generate SVG using Graphviz (if available).
Constructor Details
#initialize(db) ⇒ DependencyGraph
Returns a new instance of DependencyGraph.
32 33 34 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 32 def initialize(db) @db = db end |
Instance Method Details
#all_blocked(task_id) ⇒ Object
Find all tasks blocked by the given task (directly or transitively)
48 49 50 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 48 def all_blocked(task_id) (task_id, :outgoing, Models::Dependency::BLOCKING_TYPES) end |
#all_blockers(task_id) ⇒ Object
Find all tasks that block the given task (directly or transitively)
43 44 45 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 43 def all_blockers(task_id) (task_id, :incoming, Models::Dependency::BLOCKING_TYPES) end |
#analyze ⇒ Object
Analyze the graph for potential problems
114 115 116 117 118 119 120 121 122 123 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 114 def analyze { total_tasks: @db.all_task_ids.size, open_tasks: @db.list_tasks(status: "open").size, ready_tasks: @db.ready_tasks.size, blocked_tasks: @db.blocked_tasks.size, orphan_tasks: find_orphans.size, potential_cycles: find_potential_bottlenecks } end |
#critical_path(root_task_id) ⇒ Object
Find the critical path - longest chain of blocking dependencies
53 54 55 56 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 53 def critical_path(root_task_id) visited = {} find_longest_path(root_task_id, visited) end |
#dependency_tree(task_id, direction: :blocking, max_depth: 10) ⇒ Object
Build a tree representation of dependencies for a task
37 38 39 40 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 37 def dependency_tree(task_id, direction: :blocking, max_depth: 10) task = @db.find_task!(task_id) build_tree_node(task, direction, max_depth, Set.new) end |
#leaf_tasks ⇒ Object
Get all leaf tasks (tasks with no children/blocked tasks)
59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 59 def leaf_tasks all_targets = Set.new @db.all_task_ids.each do |id| @db.find_dependencies(id, direction: :outgoing).each do |dep| all_targets << dep.target_id if dep.blocking? end end @db.list_tasks.reject { |task| all_targets.include?(task.id) } end |
#root_tasks ⇒ Object
Get all root tasks (tasks with no parents/blockers)
72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 72 def root_tasks all_sources = Set.new @db.all_task_ids.each do |id| @db.find_dependencies(id, direction: :incoming).each do |dep| all_sources << dep.source_id if dep.blocking? end end @db.list_tasks.reject { |task| all_sources.include?(task.id) } end |
#to_dot(task_ids: nil, include_closed: false) ⇒ Object
Generate a DOT representation for Graphviz
85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 85 def to_dot(task_ids: nil, include_closed: false) task_ids ||= @db.all_task_ids tasks = task_ids.map { |id| @db.find_task(id) }.compact tasks = tasks.reject(&:closed?) unless include_closed lines = ["digraph dependencies {", " rankdir=TB;", " node [shape=box, style=filled];", ""] lines.concat(dot_node_lines(tasks)) lines << "" lines.concat(dot_edge_lines(tasks)) lines << "}" lines.join("\n") end |
#to_svg(task_ids: nil, include_closed: false) ⇒ Object
Generate SVG using Graphviz (if available)
99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/trak_flow/graph/dependency_graph.rb', line 99 def to_svg(task_ids: nil, include_closed: false) dot = to_dot(task_ids: task_ids, include_closed: include_closed) require "open3" stdout, stderr, status = Open3.capture3("dot", "-Tsvg", stdin_data: dot) unless status.success? raise Error, "Graphviz error: #{stderr}" end # Make background transparent for dark theme compatibility stdout.gsub('fill="white"', 'fill="none"') end |