Class: TrakFlow::Graph::DependencyGraph

Inherits:
Object
  • Object
show all
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

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)
  collect_related_tasks(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)
  collect_related_tasks(task_id, :incoming, Models::Dependency::BLOCKING_TYPES)
end

#analyzeObject

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_tasksObject

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_tasksObject

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