Class: Checkoff::Tasks

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Includes:
Logging
Defined in:
lib/checkoff/tasks.rb,
sig/checkoff.rbs

Overview

Pull tasks from Asana

Constant Summary collapse

MINUTE =

Returns:

  • (Object)
60
HOUR =

Returns:

  • (Object)
MINUTE * 60
DAY =

Returns:

  • (Object)
24 * HOUR
REALLY_LONG_CACHE_TIME =

Returns:

  • (Object)
MINUTE * 30
LONG_CACHE_TIME =

Returns:

  • (Object)
MINUTE * 15
SHORT_CACHE_TIME =

Returns:

  • (Object)
MINUTE * 5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config: Checkoff::Internal::ConfigLoader.load(:asana), client: Checkoff::Clients.new(config:).client, workspaces: Checkoff::Workspaces.new(config:, client:), sections: Checkoff::Sections.new(config:, client:), portfolios: Checkoff::Portfolios.new(config:, client:), custom_fields: Checkoff::CustomFields.new(config:, client:), time_class: Time, date_class: Date, asana_task: Asana::Resources::Task) ⇒ Object

sord warn - Asana::Client wasn't able to be resolved to a constant in this project sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project @param config

@param client

@param workspaces

@param sections

@param portfolios

@param custom_fields

@param time_class

@param date_class

@param asana_task



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/checkoff/tasks.rb', line 44

def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
               client: Checkoff::Clients.new(config:).client,
               workspaces: Checkoff::Workspaces.new(config:,
                                                    client:),
               sections: Checkoff::Sections.new(config:,
                                                client:),
               portfolios: Checkoff::Portfolios.new(config:,
                                                    client:),
               custom_fields: Checkoff::CustomFields.new(config:,
                                                         client:),
               time_class: Time,
               date_class: Date,
               asana_task: Asana::Resources::Task)
  @config = config
  @sections = sections
  @time_class = time_class
  @date_class = date_class
  @asana_task = asana_task
  @client = client
  @portfolios = portfolios
  @custom_fields = custom_fields
  @workspaces = workspaces
  @timing = Checkoff::Timing.new(today_getter: date_class, now_getter: time_class)
end

Instance Attribute Details

#clientAsana::Client (readonly)

sord warn - Asana::Client wasn't able to be resolved to a constant in this project

Returns:

  • (Asana::Client)


376
377
378
# File 'lib/checkoff/tasks.rb', line 376

def client
  @client
end

#custom_fieldsCheckoff::CustomFields (readonly)



382
383
384
# File 'lib/checkoff/tasks.rb', line 382

def custom_fields
  @custom_fields
end

#timingCheckoff::Timing (readonly)

Returns:



379
380
381
# File 'lib/checkoff/tasks.rb', line 379

def timing
  @timing
end

Instance Method Details

#add_task(name, workspace_gid: @workspaces.default_workspace_gid, assignee_gid: default_assignee_gid) ⇒ Asana::Resources::Task

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project Add a task

@sg-ignore

@param name

@param workspace_gid

@param assignee_gid

Parameters:

  • name (String)
  • workspace_gid: (String) (defaults to: @workspaces.default_workspace_gid)
  • assignee_gid: (String) (defaults to: default_assignee_gid)

Returns:

  • (Asana::Resources::Task)


185
186
187
188
189
190
191
# File 'lib/checkoff/tasks.rb', line 185

def add_task(name,
             workspace_gid: @workspaces.default_workspace_gid,
             assignee_gid: default_assignee_gid)
  @asana_task.create(client,
                     assignee: assignee_gid,
                     workspace: workspace_gid, name:)
end

#all_dependent_tasks(task, extra_task_fields: []) ⇒ ::Array[Asana::Resources::Task]

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project @param task

@param extra_task_fields

Parameters:

  • task (Asana::Resources::Task)
  • extra_task_fields: (::Array[String]) (defaults to: [])

Returns:

  • (::Array[Asana::Resources::Task])


240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/checkoff/tasks.rb', line 240

def all_dependent_tasks(task, extra_task_fields: [])
  # @type [Array<Asana::Resources::Task>]
  dependent_tasks = T.let([], T::Array[Asana::Resources::Task])
  # See note above - same applies as does in @dependencies
  #
  # @type [Array<Hash>]
  dependents = task.instance_variable_get(:@dependents) || []
  dependents.each do |dependent_task_hash_or_obj|
    # seems like if we ever .inspect the task, it stashes the task
    # object instead of the hash.  Try to avoid this - but maybe we
    # need to convert if it does happen.
    raise 'Found dependent task object!' if dependent_task_hash_or_obj.is_a?(Asana::Resources::Task)

    dependent_task_hash = dependent_task_hash_or_obj

    dependent_task = task_by_gid(dependent_task_hash.fetch('gid'),
                                 only_uncompleted: true,
                                 extra_fields: ['dependents'] + extra_task_fields)
    debug { "#{task.name} has dependent task #{T.must(dependent_task).name}" }
    next if dependent_task.nil?

    dependent_tasks << dependent_task
    dependent_tasks += all_dependent_tasks(dependent_task,
                                           extra_task_fields:)
  end
  dependent_tasks
end

#as_cache_keyHash

Returns:

  • (Hash)


335
336
337
# File 'lib/checkoff/tasks.rb', line 335

def as_cache_key
  {}
end

#date_or_time_field_by_name(task, field_name) ⇒ Date, ...

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project @param task

@param field_name — :start - start_at or start_on (first set) :due - due_at or due_on (first set) :ready - start_at or start_on or due_at or due_on (first set) :modified - modified_at [:custom_field, "foo"] - 'Date' custom field type named 'foo'

Parameters:

  • task (Asana::Resources::Task)
  • field_name (Symbol, ::Array[untyped])

Returns:

  • (Date, Time, nil)


107
108
109
# File 'lib/checkoff/tasks.rb', line 107

def date_or_time_field_by_name(task, field_name)
  task_timing.date_or_time_field_by_name(task, field_name)
end

#debugvoid

This method returns an undefined value.

@param message

Parameters:

  • message (Object, nil)


562
# File 'sig/checkoff.rbs', line 562

def debug: (?Object? message) -> void

#default_assignee_gidString

@sg-ignore

Returns:

  • (String)


391
392
393
# File 'lib/checkoff/tasks.rb', line 391

def default_assignee_gid
  @config.fetch(:default_assignee_gid)
end

#errorvoid

This method returns an undefined value.

@param message

Parameters:

  • message (Object, nil)


553
# File 'sig/checkoff.rbs', line 553

def error: (?Object? message) -> void

#finervoid

This method returns an undefined value.

@param message

Parameters:

  • message (Object, nil)


565
# File 'sig/checkoff.rbs', line 565

def finer: (?Object? message) -> void

#gid_for_task(workspace_name, project_name, section_name, task_name) ⇒ Object

@param workspace_name

@param project_name

@param section_name

@param task_name



143
144
145
146
147
148
149
150
151
152
# File 'lib/checkoff/tasks.rb', line 143

def gid_for_task(workspace_name, project_name, section_name, task_name)
  t = tasks(workspace_name,
            project_name,
            section_name:,
            only_uncompleted: false)
  task_for_gid = t.find { |task| task.name == task_name }
  return nil if task_for_gid.nil?

  task_for_gid.gid
end

#h_to_task(task_data) ⇒ Asana::Resources::Task

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project @param task_data

Parameters:

  • task_data (::Hash[untyped, untyped])

Returns:

  • (Asana::Resources::Task)


289
290
291
# File 'lib/checkoff/tasks.rb', line 289

def h_to_task(task_data)
  task_hashes.h_to_task(task_data, client:)
end

#in_period?(task, field_name, period) ⇒ Boolean

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project @param task

@param field_name

@param period — :now_or_before, :this_week - See Checkoff::Timing#in_period?_

Parameters:

  • task (Asana::Resources::Task)
  • field_name (Symbol, ::Array[untyped])
  • period (Symbol, ::Array[untyped])

Returns:

  • (Boolean)


91
92
93
94
95
96
# File 'lib/checkoff/tasks.rb', line 91

def in_period?(task, field_name, period)
  # @type [Date,Time,nil]
  task_date_or_time = task_timing.date_or_time_field_by_name(task, field_name)

  timing.in_period?(task_date_or_time, period)
end

#in_portfolio_more_than_once?(task, portfolio_name, workspace_name: @workspaces.default_workspace.name) ⇒ Boolean

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project True if the task is in a project which is in the given portfolio

@param task

@param portfolio_name

@param workspace_name

Parameters:

  • task (Asana::Resources::Task)
  • portfolio_name (String)
  • workspace_name: (String) (defaults to: @workspaces.default_workspace.name)

Returns:

  • (Boolean)


317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/checkoff/tasks.rb', line 317

def in_portfolio_more_than_once?(task,
                                 portfolio_name,
                                 workspace_name: @workspaces.default_workspace.name)
  portfolio_projects = @portfolios.projects_in_portfolio(workspace_name, portfolio_name)
  portfolio_project_gids = portfolio_projects.map(&:gid)
  seen = T.let(false, T::Boolean)
  task.memberships.each do |membership|
    m = T.cast(membership, T::Hash[String, T.untyped])
    project_gid = T.cast(m.fetch('project'), T::Hash[String, T.untyped]).fetch('gid')
    next unless portfolio_project_gids.include?(project_gid)
    return true if seen

    seen = true
  end
  false
end

#in_portfolio_named?(task, portfolio_name, workspace_name: @workspaces.default_workspace.name) ⇒ Boolean

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project True if the task is in a project which is in the given portfolio

@sg-ignore Checkoff::Tasks#in_portfolio_named? return type could not be inferred

@param task

@param portfolio_name

@param workspace_name

Parameters:

  • task (Asana::Resources::Task)
  • portfolio_name (String)
  • workspace_name: (String) (defaults to: @workspaces.default_workspace.name)

Returns:

  • (Boolean)


300
301
302
303
304
305
306
307
308
309
# File 'lib/checkoff/tasks.rb', line 300

def in_portfolio_named?(task,
                        portfolio_name,
                        workspace_name: @workspaces.default_workspace.name)
  portfolio_projects = @portfolios.projects_in_portfolio(workspace_name, portfolio_name)
  task.memberships.any? do |membership|
    m = T.cast(membership, T::Hash[String, T.untyped])
    project_gid = T.cast(m.fetch('project'), T::Hash[String, T.untyped]).fetch('gid')
    portfolio_projects.any? { |portfolio_project| portfolio_project.gid == project_gid }
  end == true
end

#incomplete_dependencies?(task) ⇒ Boolean

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project True if any of the task's dependencies are marked incomplete

@sg-ignore Include 'dependencies.gid' in extra_fields of task passed in.

@param task

Parameters:

  • task (Asana::Resources::Task)

Returns:

  • (Boolean)


208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/checkoff/tasks.rb', line 208

def incomplete_dependencies?(task)
  # Avoid a redundant fetch.  Unfortunately, Ruby SDK allows
  # dependencies to be fetched along with other attributes--but
  # then doesn't use it and does another HTTP GET!  At least this
  # way we can skip the extra HTTP GET in the common case when
  # there are no dependencies.
  #
  # https://github.com/Asana/ruby-asana/issues/125

  # @type [Enumerable<Asana::Resources::Task>, nil]
  dependencies = task.instance_variable_get(:@dependencies) || []

  dependencies.any? do |parent_task_info|
    # the real bummer though is that asana doesn't let you fetch
    # the completion status of dependencies, so we need to do this
    # regardless:
    # @sg-ignore
    parent_task_gid = parent_task_info.fetch('gid')

    parent_task = task_by_gid(parent_task_gid, only_uncompleted: false)
    # Embedded dependency GIDs can outlive the parent fetch (timing) or disagree
    # with cache_method TTL/coherency; treat unfetchable as incomplete.
    next true if parent_task.nil?

    parent_task.completed_at.nil?
  end
end

#infovoid

This method returns an undefined value.

@param message

Parameters:

  • message (Object, nil)


559
# File 'sig/checkoff.rbs', line 559

def info: (?Object? message) -> void

#log_levelSymbol

@sg-ignore

Returns:

  • (Symbol)


568
# File 'sig/checkoff.rbs', line 568

def log_level: () -> Symbol

#logger::Logger

Returns:

  • (::Logger)


550
# File 'sig/checkoff.rbs', line 550

def logger: () -> ::Logger

#projectsCheckoff::Projects

Returns:



385
386
387
# File 'lib/checkoff/tasks.rb', line 385

def projects
  @projects ||= @sections.projects
end

#task(workspace_name, project_name, task_name, section_name: :unspecified, only_uncompleted: true, extra_fields: []) ⇒ Object

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project Pull a specific task by name

@param workspace_name

@param project_name

@param section_name

@param task_name

@param only_uncompleted

@param extra_fields



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/checkoff/tasks.rb', line 120

def task(workspace_name, project_name, task_name,
         section_name: :unspecified,
         only_uncompleted: true,
         extra_fields: [])
  thread_local = Checkoff::Internal::ThreadLocal.new
  # @type [String]
  # @sg-ignore
  task_gid = thread_local.with_thread_local_variable(:suppress_asana_webhook_watch_creation,
                                                     true) do
    gid_for_task(workspace_name, project_name, section_name, task_name)
  end
  return nil if task_gid.nil?

  task_by_gid(task_gid, only_uncompleted:, extra_fields:)
end

#task_by_gid(task_gid, extra_fields: [], only_uncompleted: true) ⇒ Asana::Resources::Task?

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project Pull a specific task by GID

@param task_gid

@param extra_fields

@param only_uncompleted

Parameters:

  • task_gid (String)
  • extra_fields: (::Array[String]) (defaults to: [])
  • only_uncompleted: (Boolean) (defaults to: true)

Returns:

  • (Asana::Resources::Task, nil)


162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/checkoff/tasks.rb', line 162

def task_by_gid(task_gid,
                extra_fields: [],
                only_uncompleted: true)
  all_options = projects.task_options(extra_fields:,
                                      only_uncompleted:)
  # @type [Hash]
  options = all_options.fetch(:options) { {} }
  options[:completed_since] = all_options[:completed_since] unless all_options[:completed_since].nil?
  client.tasks.find_by_id(task_gid, options:)
rescue Asana::Errors::NotFound => e
  debug e
  nil
end

#task_hashesCheckoff::Internal::TaskHashes



349
350
351
# File 'lib/checkoff/tasks.rb', line 349

def task_hashes
  @task_hashes ||= Checkoff::Internal::TaskHashes.new
end

#task_ready?(task, period: :now_or_before, ignore_dependencies: false) ⇒ Boolean

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project Indicates a task is ready for a person to work on it. This is subtly different than what is used by Asana to mark a date as red/green! A task is ready if it is not dependent on an incomplete task and one of these is true:

  • start is null and due on is today
  • start is null and due at is before now
  • start on is today
  • start at is before now

@param task

@param period — - :now_or_before or :this_week

@param ignore_dependencies

Parameters:

  • task (Asana::Resources::Task)
  • period: (Symbol) (defaults to: :now_or_before)
  • ignore_dependencies: (Boolean) (defaults to: false)

Returns:

  • (Boolean)


82
83
84
85
86
# File 'lib/checkoff/tasks.rb', line 82

def task_ready?(task, period: :now_or_before, ignore_dependencies: false)
  return false if !ignore_dependencies && incomplete_dependencies?(task)

  in_period?(task, :ready, period)
end

#task_timingCheckoff::Internal::TaskTiming



342
343
344
345
346
# File 'lib/checkoff/tasks.rb', line 342

def task_timing
  @task_timing ||= Checkoff::Internal::TaskTiming.new(time_class: @time_class, date_class: @date_class,
                                                      client:,
                                                      custom_fields:)
end

#task_to_h(task) ⇒ ::Hash[untyped, untyped]

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project Builds on the standard API representation of an Asana task with some convenience keys:

+ unwrapped: membership_by_section_gid: Hash{String => Hash (membership)> membership_by_project_gid: Hash{String => Hash (membership)> membership_by_project_name: Hash{String => Hash (membership)> task: String (name)

@param task

Parameters:

  • task (Asana::Resources::Task)

Returns:

  • (::Hash[untyped, untyped])


282
283
284
# File 'lib/checkoff/tasks.rb', line 282

def task_to_h(task)
  task_hashes.task_to_h(task)
end

#tasks(workspace_name, project_name, only_uncompleted:, extra_fields: [], section_name: :unspecified) ⇒ Object

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project @param workspace_name

@param project_name

@param section_name — - :unspecified

@param only_uncompleted

@param extra_fields



360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/checkoff/tasks.rb', line 360

def tasks(workspace_name, project_name,
          only_uncompleted:, extra_fields: [], section_name: :unspecified)
  if section_name == :unspecified
    project = projects.project_or_raise(workspace_name, project_name)
    projects.tasks_from_project(project,
                                only_uncompleted:,
                                extra_fields:)
  else
    @sections.tasks(workspace_name, project_name, section_name,
                    only_uncompleted:,
                    extra_fields:)
  end
end

#url_of_task(task) ⇒ String

sord warn - Asana::Resources::Task wasn't able to be resolved to a constant in this project Return user-accessible URL for a task

@param task

@return — end-user URL to the task in question

Parameters:

  • task (Asana::Resources::Task)

Returns:

  • (String)


198
199
200
# File 'lib/checkoff/tasks.rb', line 198

def url_of_task(task)
  "https://app.asana.com/0/0/#{task.gid}/f"
end

#warnvoid

This method returns an undefined value.

@param message

Parameters:

  • message (Object, nil)


556
# File 'sig/checkoff.rbs', line 556

def warn: (?Object? message) -> void