Class: Tasker::Task
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Tasker::Task
- Defined in:
- app/models/tasker/task.rb
Overview
Task represents a workflow process that contains multiple workflow steps. Each Task is identified by a name and has a context which defines the parameters for the task. Tasks track their status via state machine transitions, initiator, source system, and other metadata.
Constant Summary collapse
- ALPHANUM_PLUS_HYPHEN_DASH =
Regular expression to sanitize strings for database operations
/[^0-9a-z\-_]/i
Class Method Summary collapse
-
.active ⇒ ActiveRecord::Relation
Scopes tasks that are currently active (not in terminal states).
-
.by_annotation ⇒ ActiveRecord::Relation
Scopes a query to find tasks with a specific annotation value.
-
.by_current_state ⇒ ActiveRecord::Relation
Scopes a query to find tasks by their current state using state machine transitions.
-
.completed_since ⇒ ActiveRecord::Relation
Scopes tasks completed within a specific time period.
-
.create_with_defaults!(task_request) ⇒ Tasker::Task
Creates a task with default values from a task request and saves it to the database.
-
.created_since ⇒ ActiveRecord::Relation
Scopes tasks created within a specific time period.
-
.failed_since ⇒ ActiveRecord::Relation
Scopes tasks that have failed within a specific time period This scope identifies tasks that are actually in a failed state (task status = 'error'), not just tasks that have some failed steps but may still be progressing.
-
.from_task_request(task_request) ⇒ Tasker::Task
Creates a new unsaved task instance from a task request.
-
.get_default_task_request_options(named_task) ⇒ Hash
Provides default options for a task.
-
.get_request_options(task_request) ⇒ Hash
Extracts and compacts options from a task request.
-
.in_namespace ⇒ ActiveRecord::Relation
Scopes tasks by namespace name through the named_task association.
-
.unique_task_types_count ⇒ Integer
Class method for counting unique task types.
-
.with_task_name ⇒ ActiveRecord::Relation
Scopes tasks by task name through the named_task association.
-
.with_version ⇒ ActiveRecord::Relation
Scopes tasks by version through the named_task association.
Instance Method Summary collapse
-
#all_steps_complete? ⇒ Boolean
Checks if all steps in the task are complete.
-
#dependency_graph ⇒ Hash
Provides runtime dependency graph analysis Delegates to RuntimeGraphAnalyzer for graph-based analysis.
-
#get_step_by_name(name) ⇒ Tasker::WorkflowStep?
Finds a workflow step by its name.
- #reload ⇒ Object
- #runtime_analyzer ⇒ Object
-
#state_machine ⇒ Object
State machine integration.
-
#status ⇒ Object
Status is now entirely managed by the state machine.
- #task_execution_context ⇒ Object
-
#with_all_associated ⇒ ActiveRecord::Relation
Includes all associated models for efficient querying.
Methods inherited from ApplicationRecord
configure_database_connections, database_configuration_exists?
Class Method Details
.active ⇒ ActiveRecord::Relation
Scopes tasks that are currently active (not in terminal states)
182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'app/models/tasker/task.rb', line 182 scope :active, lambda { # Active tasks are those with at least one workflow step whose most recent transition # is NOT in a terminal state. Using EXISTS subquery for clarity and performance. where(<<-SQL.squish) EXISTS ( SELECT 1 FROM tasker_workflow_steps ws INNER JOIN tasker_workflow_step_transitions wst ON wst.workflow_step_id = ws.workflow_step_id WHERE ws.task_id = tasker_tasks.task_id AND wst.most_recent = true AND wst.to_state NOT IN ('complete', 'error', 'skipped', 'resolved_manually') ) SQL } |
.by_annotation ⇒ ActiveRecord::Relation
Scopes a query to find tasks with a specific annotation value
96 97 98 99 100 101 102 |
# File 'app/models/tasker/task.rb', line 96 scope :by_annotation, lambda { |name, key, value| clean_key = key.to_s.gsub(ALPHANUM_PLUS_HYPHEN_DASH, '') joins(:task_annotations, :annotation_types) .where({ annotation_types: { name: name.to_s.strip } }) .where("tasker_task_annotations.annotation->>'#{clean_key}' = :value", value: value) } |
.by_current_state ⇒ ActiveRecord::Relation
Scopes a query to find tasks by their current state using state machine transitions
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'app/models/tasker/task.rb', line 109 scope :by_current_state, lambda { |state = nil| relation = joins(<<-SQL.squish) INNER JOIN ( SELECT DISTINCT ON (task_id) task_id, to_state FROM tasker_task_transitions ORDER BY task_id, sort_key DESC ) current_transitions ON current_transitions.task_id = tasker_tasks.task_id SQL if state.present? relation.where(current_transitions: { to_state: state }) else relation end } |
.completed_since ⇒ ActiveRecord::Relation
Scopes tasks completed within a specific time period
152 153 154 155 156 157 |
# File 'app/models/tasker/task.rb', line 152 scope :completed_since, lambda { |since_time| joins(workflow_steps: :workflow_step_transitions) .where('tasker_workflow_step_transitions.to_state = ? AND tasker_workflow_step_transitions.most_recent = ?', 'complete', true) .where('tasker_workflow_step_transitions.created_at > ?', since_time) .distinct } |
.create_with_defaults!(task_request) ⇒ Tasker::Task
Creates a task with default values from a task request and saves it to the database
239 240 241 242 243 |
# File 'app/models/tasker/task.rb', line 239 def self.create_with_defaults!(task_request) task = from_task_request(task_request) task.save! task end |
.created_since ⇒ ActiveRecord::Relation
Scopes tasks created within a specific time period
143 144 145 |
# File 'app/models/tasker/task.rb', line 143 scope :created_since, lambda { |since_time| where('tasker_tasks.created_at > ?', since_time) } |
.failed_since ⇒ ActiveRecord::Relation
Scopes tasks that have failed within a specific time period This scope identifies tasks that are actually in a failed state (task status = 'error'), not just tasks that have some failed steps but may still be progressing.
166 167 168 169 170 171 172 173 174 175 176 |
# File 'app/models/tasker/task.rb', line 166 scope :failed_since, lambda { |since_time| joins(<<-SQL.squish) INNER JOIN ( SELECT DISTINCT ON (task_id) task_id, to_state, created_at FROM tasker_task_transitions ORDER BY task_id, sort_key DESC ) current_transitions ON current_transitions.task_id = tasker_tasks.task_id SQL .where(current_transitions: { to_state: Tasker::Constants::TaskStatuses::ERROR }) .where('current_transitions.created_at > ?', since_time) } |
.from_task_request(task_request) ⇒ Tasker::Task
Creates a new unsaved task instance from a task request
249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'app/models/tasker/task.rb', line 249 def self.from_task_request(task_request) named_task = Tasker::NamedTask.find_or_create_by_full_name!(name: task_request.name, namespace_name: task_request.namespace, version: task_request.version) # Extract values from task_request, removing nils request_values = (task_request) # Merge defaults with request values = (named_task).merge(request_values) task = new() task.named_task = named_task task end |
.get_default_task_request_options(named_task) ⇒ Hash
Provides default options for a task
282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'app/models/tasker/task.rb', line 282 def self.(named_task) { initiator: Tasker::Constants::UNKNOWN, source_system: Constants::UNKNOWN, reason: Tasker::Constants::UNKNOWN, complete: false, tags: [], bypass_steps: [], requested_at: Time.zone.now, named_task_id: named_task.named_task_id } end |
.get_request_options(task_request) ⇒ Hash
Extracts and compacts options from a task request
266 267 268 269 270 271 272 273 274 275 276 |
# File 'app/models/tasker/task.rb', line 266 def self.(task_request) { initiator: task_request.initiator, source_system: task_request.source_system, reason: task_request.reason, tags: task_request., bypass_steps: task_request.bypass_steps, requested_at: task_request.requested_at, context: task_request.context }.compact end |
.in_namespace ⇒ ActiveRecord::Relation
Scopes tasks by namespace name through the named_task association
202 203 204 205 |
# File 'app/models/tasker/task.rb', line 202 scope :in_namespace, lambda { |namespace_name| joins(named_task: :task_namespace) .where(tasker_task_namespaces: { name: namespace_name }) } |
.unique_task_types_count ⇒ Integer
Class method for counting unique task types
230 231 232 |
# File 'app/models/tasker/task.rb', line 230 def self.unique_task_types_count joins(:named_task).distinct.count('tasker_named_tasks.name') end |
.with_task_name ⇒ ActiveRecord::Relation
Scopes tasks by task name through the named_task association
212 213 214 215 |
# File 'app/models/tasker/task.rb', line 212 scope :with_task_name, lambda { |task_name| joins(:named_task) .where(tasker_named_tasks: { name: task_name }) } |
.with_version ⇒ ActiveRecord::Relation
Scopes tasks by version through the named_task association
222 223 224 225 |
# File 'app/models/tasker/task.rb', line 222 scope :with_version, lambda { |version| joins(:named_task) .where(tasker_named_tasks: { version: version }) } |
Instance Method Details
#all_steps_complete? ⇒ Boolean
Checks if all steps in the task are complete
318 319 320 |
# File 'app/models/tasker/task.rb', line 318 def all_steps_complete? Tasker::StepReadinessStatus.all_steps_complete_for_task?(self) end |
#dependency_graph ⇒ Hash
Provides runtime dependency graph analysis Delegates to RuntimeGraphAnalyzer for graph-based analysis
311 312 313 |
# File 'app/models/tasker/task.rb', line 311 def dependency_graph runtime_analyzer.analyze end |
#get_step_by_name(name) ⇒ Tasker::WorkflowStep?
Finds a workflow step by its name
299 300 301 |
# File 'app/models/tasker/task.rb', line 299 def get_step_by_name(name) workflow_steps.includes(:named_step).where(named_step: { name: name }).first end |
#reload ⇒ Object
326 327 328 329 |
# File 'app/models/tasker/task.rb', line 326 def reload super @task_execution_context = nil end |
#runtime_analyzer ⇒ Object
303 304 305 |
# File 'app/models/tasker/task.rb', line 303 def runtime_analyzer @runtime_analyzer ||= Tasker::Analysis::RuntimeGraphAnalyzer.new(task: self) end |
#state_machine ⇒ Object
State machine integration
70 71 72 73 74 75 76 |
# File 'app/models/tasker/task.rb', line 70 def state_machine @state_machine ||= Tasker::StateMachine::TaskStateMachine.new( self, transition_class: Tasker::TaskTransition, association_name: :task_transitions ) end |
#status ⇒ Object
Status is now entirely managed by the state machine
79 80 81 82 83 84 85 86 87 |
# File 'app/models/tasker/task.rb', line 79 def status if new_record? # For new records, return the initial state Tasker::Constants::TaskStatuses::PENDING else # For persisted records, use state machine state_machine.current_state end end |
#task_execution_context ⇒ Object
322 323 324 |
# File 'app/models/tasker/task.rb', line 322 def task_execution_context @task_execution_context ||= Tasker::TaskExecutionContext.new(task_id) end |
#with_all_associated ⇒ ActiveRecord::Relation
Includes all associated models for efficient querying
129 130 131 132 133 134 |
# File 'app/models/tasker/task.rb', line 129 scope :with_all_associated, lambda { includes(named_task: [:task_namespace]) .includes(workflow_steps: %i[named_step parents children]) .includes(task_annotations: %i[annotation_type]) .includes(:task_transitions) } |