Class: Checkoff::Tasks

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/checkoff/tasks.rb

Overview

Pull tasks from Asana

Constant Summary collapse

MINUTE =
60
HOUR =
MINUTE * 60
DAY =
24 * HOUR
REALLY_LONG_CACHE_TIME =
HOUR
LONG_CACHE_TIME =
MINUTE * 15
SHORT_CACHE_TIME =
MINUTE * 5

Instance Method Summary collapse

Methods included from Logging

#debug, #error, #finer, #info, #logger, #warn

Constructor Details

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

Returns a new instance of Tasks.

Parameters:

  • config (Hash<Symbol, Object>) (defaults to: Checkoff::Internal::ConfigLoader.load(:asana))
  • client (Asana::Client) (defaults to: Checkoff::Clients.new(config: config).client)
  • workspaces (Checkoff::Workspaces) (defaults to: Checkoff::Workspaces.new(config: config, client: client))
  • sections (Checkoff::Sections) (defaults to: Checkoff::Sections.new(config: config, client: client))
  • portfolios (Checkoff::Portfolios) (defaults to: Checkoff::Portfolios.new(config: config, client: client))
  • custom_fields (Checkoff::CustomFields) (defaults to: Checkoff::CustomFields.new(config: config, client: client))
  • time_class (Class<Time>) (defaults to: Time)
  • date_class (Class<Date>) (defaults to: Date)
  • asana_task (Class<Asana::Resources::Task>) (defaults to: Asana::Resources::Task)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/checkoff/tasks.rb', line 38

def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
               client: Checkoff::Clients.new(config: config).client,
               workspaces: Checkoff::Workspaces.new(config: config,
                                                    client: client),
               sections: Checkoff::Sections.new(config: config,
                                                client: client),
               portfolios: Checkoff::Portfolios.new(config: config,
                                                    client: client),
               custom_fields: Checkoff::CustomFields.new(config: config,
                                                         client: 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 Method Details

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

Add a task

Parameters:

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

Returns:

  • (Asana::Resources::Task)


154
155
156
157
158
159
160
# File 'lib/checkoff/tasks.rb', line 154

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: name)
end

#all_dependent_tasks(task, extra_task_fields: []) ⇒ Array<Hash>

Parameters:

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

Returns:

  • (Array<Hash>)


204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/checkoff/tasks.rb', line 204

def all_dependent_tasks(task, extra_task_fields: [])
  dependent_tasks = []
  # 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 #{dependent_task.name}" }
    unless dependent_task.nil?
      dependent_tasks << dependent_task
      dependent_tasks += all_dependent_tasks(dependent_task)
    end
  end
  dependent_tasks
end

#as_cache_keyHash

Returns:

  • (Hash)


273
274
275
# File 'lib/checkoff/tasks.rb', line 273

def as_cache_key
  {}
end

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

Parameters:

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

    :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’

Returns:

  • (Date, Time, nil)


101
102
103
# File 'lib/checkoff/tasks.rb', line 101

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

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

Parameters:

  • task_data (Hash)

Returns:

  • (Asana::Resources::Task)


251
252
253
# File 'lib/checkoff/tasks.rb', line 251

def h_to_task(task_data)
  Asana::Resources::Task.new(task_data, client: client)
end

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

Parameters:

  • task (Asana::Resources::Task)
  • field_name (Symbol, Array)
  • period (Symbol<:now_or_before,:this_week>, Array)

    See Checkoff::Timing#in_period?_

Returns:

  • (Boolean)


85
86
87
88
89
90
# File 'lib/checkoff/tasks.rb', line 85

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_named?(task, portfolio_name, workspace_name: @workspaces.default_workspace.name) ⇒ Boolean

True if the task is in a project which is in the given portfolio

Parameters:

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

Returns:

  • (Boolean)


260
261
262
263
264
265
266
267
268
269
270
# File 'lib/checkoff/tasks.rb', line 260

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|
    project_gid = membership.fetch('project').fetch('gid')
    portfolio_projects.any? do |portfolio_project|
      portfolio_project.gid == project_gid
    end
  end
end

#incomplete_dependencies?(task) ⇒ Boolean

True if any of the task’s dependencies are marked incomplete

Include ‘dependencies.gid’ in extra_fields of task passed in.

Parameters:

  • task (Asana::Resources::Task)

Returns:

  • (Boolean)


176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/checkoff/tasks.rb', line 176

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

  # @sg-ignore
  # @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:
    parent_task_gid = parent_task_info.fetch('gid')

    parent_task = task_by_gid(parent_task_gid, only_uncompleted: false)
    parent_task.completed_at.nil?
  end
end

#task(workspace_name, project_name, task_name, section_name: :unspecified, only_uncompleted: true, extra_fields: []) ⇒ Asana::Resources::Task?

Pull a specific task by name

@sg-ignore

Parameters:

  • workspace_name (String)
  • project_name (String, Symbol)
  • section_name (String, nil, Symbol) (defaults to: :unspecified)
  • task_name (String)
  • only_uncompleted (Boolean) (defaults to: true)
  • extra_fields (Array<String>) (defaults to: [])

Returns:

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


115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/checkoff/tasks.rb', line 115

def task(workspace_name, project_name, task_name,
         section_name: :unspecified,
         only_uncompleted: true,
         extra_fields: [])
  # @sg-ignore
  t = tasks(workspace_name,
            project_name,
            section_name: section_name,
            only_uncompleted: only_uncompleted,
            extra_fields: extra_fields)
  t.find { |task| task.name == task_name }
end

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

Pull a specific task by GID

Parameters:

  • task_gid (String)
  • extra_fields (Array<String>) (defaults to: [])
  • only_uncompleted (Boolean) (defaults to: false)

Returns:

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


136
137
138
139
140
141
142
143
144
# File 'lib/checkoff/tasks.rb', line 136

def task_by_gid(task_gid,
                extra_fields: [],
                only_uncompleted: false)
  # @type [Hash]
  options = projects.task_options.fetch(:options, {})
  options[:fields] += extra_fields
  options[:completed_since] = '9999-12-01' if only_uncompleted
  client.tasks.find_by_id(task_gid, options: options)
end

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

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

Parameters:

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

Returns:

  • (Boolean)


76
77
78
79
80
# File 'lib/checkoff/tasks.rb', line 76

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_to_h(task) ⇒ Hash

Builds on the standard API representation of an Asana task with some convenience keys:

<regular keys from API response> + 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)

Parameters:

  • task (Asana::Resources::Task)

Returns:

  • (Hash)


244
245
246
# File 'lib/checkoff/tasks.rb', line 244

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

#url_of_task(task) ⇒ String

Return user-accessible URL for a task

Parameters:

  • task (Asana::Resources::Task)

Returns:

  • (String)

    end-user URL to the task in question



167
168
169
# File 'lib/checkoff/tasks.rb', line 167

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