Class: TrakFlow::Models::Task

Inherits:
Object
  • Object
show all
Defined in:
lib/trak_flow/models/task.rb

Overview

Represents a task in the TrakFlow system

Tasks serve multiple roles determined by flags:

  • Plan (blueprint): task.plan? == true

  • Workflow: task.source_plan_id.present? && !task.plan?

  • Step (conceptual): child Task of a Plan

  • Work item: child Task of a Workflow

Tasks can be bugs, features, tasks, epics, or chores

Constant Summary collapse

VALID_STATUSES =
TrakFlow::STATUSES
VALID_PRIORITIES =
TrakFlow::PRIORITIES
VALID_TYPES =
TrakFlow::TYPES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attrs = {}) ⇒ Task

Returns a new instance of Task.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/trak_flow/models/task.rb', line 23

def initialize(attrs = {})
  @id = attrs[:id]
  @title = attrs[:title]
  @description = attrs[:description] || ""
  @status = attrs[:status] || "open"
  @priority = attrs[:priority] || 2
  @type = attrs[:type] || "task"
  @assignee = attrs[:assignee]
  @parent_id = attrs[:parent_id]
  @created_at = attrs[:created_at] || Time.now.utc
  @updated_at = attrs[:updated_at] || Time.now.utc
  @closed_at = attrs[:closed_at]
  @content_hash = attrs[:content_hash]
  @plan = attrs[:plan] || false
  @source_plan_id = attrs[:source_plan_id]
  @ephemeral = attrs[:ephemeral] || false
  @notes = attrs[:notes] || ""
end

Instance Attribute Details

#assigneeObject

Returns the value of attribute assignee.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def assignee
  @assignee
end

#closed_atObject

Returns the value of attribute closed_at.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def closed_at
  @closed_at
end

#content_hashObject

Returns the value of attribute content_hash.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def content_hash
  @content_hash
end

#created_atObject

Returns the value of attribute created_at.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def created_at
  @created_at
end

#descriptionObject

Returns the value of attribute description.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def description
  @description
end

#ephemeralObject

Returns the value of attribute ephemeral.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def ephemeral
  @ephemeral
end

#idObject

Returns the value of attribute id.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def id
  @id
end

#notesObject

Returns the value of attribute notes.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def notes
  @notes
end

#parent_idObject

Returns the value of attribute parent_id.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def parent_id
  @parent_id
end

#planObject

Returns the value of attribute plan.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def plan
  @plan
end

#priorityObject

Returns the value of attribute priority.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def priority
  @priority
end

#source_plan_idObject

Returns the value of attribute source_plan_id.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def source_plan_id
  @source_plan_id
end

#statusObject

Returns the value of attribute status.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def status
  @status
end

#titleObject

Returns the value of attribute title.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def title
  @title
end

#typeObject

Returns the value of attribute type.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def type
  @type
end

#updated_atObject

Returns the value of attribute updated_at.



15
16
17
# File 'lib/trak_flow/models/task.rb', line 15

def updated_at
  @updated_at
end

Class Method Details

.from_hash(hash) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/trak_flow/models/task.rb', line 160

def from_hash(hash)
  hash = hash.transform_keys(&:to_sym)
  new(
    id: hash[:id],
    title: hash[:title],
    description: hash[:description],
    status: hash[:status],
    priority: hash[:priority]&.to_i,
    type: hash[:type],
    assignee: hash[:assignee],
    parent_id: hash[:parent_id],
    created_at: TimeParser.parse(hash[:created_at]),
    updated_at: TimeParser.parse(hash[:updated_at]),
    closed_at: TimeParser.parse(hash[:closed_at]),
    content_hash: hash[:content_hash],
    plan: hash[:plan],
    source_plan_id: hash[:source_plan_id],
    ephemeral: hash[:ephemeral],
    notes: hash[:notes]
  )
end

.from_json(json_string) ⇒ Object



182
183
184
# File 'lib/trak_flow/models/task.rb', line 182

def from_json(json_string)
  from_hash(Oj.load(json_string, mode: :compat, symbol_keys: true))
end

Instance Method Details

#append_trace(action, message) ⇒ Object



123
124
125
126
127
128
# File 'lib/trak_flow/models/task.rb', line 123

def append_trace(action, message)
  timestamp = Time.now.utc.iso8601
  entry = "[#{timestamp}] [#{action}] #{message}"
  self.notes = "#{notes}\n#{entry}".strip
  touch!
end

#blocked?Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/trak_flow/models/task.rb', line 74

def blocked?
  status == "blocked"
end

#close!(reason: nil) ⇒ Object



104
105
106
107
108
109
# File 'lib/trak_flow/models/task.rb', line 104

def close!(reason: nil)
  self.status = "closed"
  self.closed_at = Time.now.utc
  self.notes = "#{notes}\n[Closed] #{reason}".strip if reason
  touch!
end

#closed?Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/trak_flow/models/task.rb', line 66

def closed?
  status == "closed" || status == "tombstone"
end

#discardable?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/trak_flow/models/task.rb', line 100

def discardable?
  ephemeral?
end

#ephemeral?Boolean

Returns:

  • (Boolean)


92
93
94
# File 'lib/trak_flow/models/task.rb', line 92

def ephemeral?
  !!ephemeral
end

#epic?Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/trak_flow/models/task.rb', line 78

def epic?
  type == "epic"
end

#errorsObject



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/trak_flow/models/task.rb', line 46

def errors
  errs = []
  errs << "Title is required" if title.nil? || title.strip.empty?
  errs << "Invalid status: #{status}" unless VALID_STATUSES.include?(status)
  errs << "Invalid priority: #{priority}" unless VALID_PRIORITIES.include?(priority)
  errs << "Invalid type: #{type}" unless VALID_TYPES.include?(type)
  errs << "Plans cannot be ephemeral" if plan && ephemeral
  errs << "Plans cannot change status" if plan && status != "open"
  errs << "Plans cannot be derived from other Plans" if plan && source_plan_id
  errs
end

#executable?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/trak_flow/models/task.rb', line 96

def executable?
  !plan?
end

#in_progress?Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/trak_flow/models/task.rb', line 70

def in_progress?
  status == "in_progress"
end

#open?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/trak_flow/models/task.rb', line 62

def open?
  status == "open"
end

#plan?Boolean

Plan/Workflow role predicates

Returns:

  • (Boolean)


84
85
86
# File 'lib/trak_flow/models/task.rb', line 84

def plan?
  !!plan
end

#reopen!(reason: nil) ⇒ Object



111
112
113
114
115
116
# File 'lib/trak_flow/models/task.rb', line 111

def reopen!(reason: nil)
  self.status = "open"
  self.closed_at = nil
  self.notes = "#{notes}\n[Reopened] #{reason}".strip if reason
  touch!
end

#to_hObject



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/trak_flow/models/task.rb', line 134

def to_h
  {
    id: id,
    title: title,
    description: description,
    status: status,
    priority: priority,
    type: type,
    assignee: assignee,
    parent_id: parent_id,
    created_at: created_at&.iso8601,
    updated_at: updated_at&.iso8601,
    closed_at: closed_at&.iso8601,
    content_hash: content_hash,
    plan: plan,
    source_plan_id: source_plan_id,
    ephemeral: ephemeral,
    notes: notes
  }.compact
end

#to_json(*args) ⇒ Object



155
156
157
# File 'lib/trak_flow/models/task.rb', line 155

def to_json(*args)
  Oj.dump(to_h, mode: :compat)
end

#touch!Object



118
119
120
121
# File 'lib/trak_flow/models/task.rb', line 118

def touch!
  self.updated_at = Time.now.utc
  update_content_hash!
end

#update_content_hash!Object



130
131
132
# File 'lib/trak_flow/models/task.rb', line 130

def update_content_hash!
  self.content_hash = IdGenerator.content_hash(to_h.except(:content_hash, :updated_at))
end

#valid?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'lib/trak_flow/models/task.rb', line 42

def valid?
  errors.empty?
end

#validate!Object

Raises:



58
59
60
# File 'lib/trak_flow/models/task.rb', line 58

def validate!
  raise ValidationError, errors.join(", ") unless valid?
end

#workflow?Boolean

Returns:

  • (Boolean)


88
89
90
# File 'lib/trak_flow/models/task.rb', line 88

def workflow?
  source_plan_id && !source_plan_id.to_s.empty? && !plan?
end