Class: Checkoff::Projects

Inherits:
Object
  • Object
show all
Extended by:
CacheMethod::ClassMethods
Includes:
Logging
Defined in:
lib/checkoff/projects.rb

Overview

Work with projects in Asana

Constant Summary collapse

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

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:).client, workspaces: Checkoff::Workspaces.new(config:, client:), project_hashes: Checkoff::Internal::ProjectHashes.new, project_timing: Checkoff::Internal::ProjectTiming.new(client:), timing: Checkoff::Timing.new) ⇒ Projects

Returns a new instance of Projects.

Parameters:



42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/checkoff/projects.rb', line 42

def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
               client: Checkoff::Clients.new(config:).client,
               workspaces: Checkoff::Workspaces.new(config:,
                                                    client:),
               project_hashes: Checkoff::Internal::ProjectHashes.new,
               project_timing: Checkoff::Internal::ProjectTiming.new(client:),
               timing: Checkoff::Timing.new)
  @config = config
  @workspaces = workspaces
  @client = client
  @project_hashes = project_hashes
  @project_timing = project_timing
  @timing = timing
end

Instance Method Details

#active_tasks(tasks) ⇒ Enumerable<Asana::Resources::Task>

find uncompleted tasks in a list

Parameters:

  • tasks (Enumerable<Asana::Resources::Task>)

Returns:

  • (Enumerable<Asana::Resources::Task>)


147
148
149
# File 'lib/checkoff/projects.rb', line 147

def active_tasks(tasks)
  tasks.select { |task| task.completed_at.nil? }
end

#as_cache_keyHash

Returns:

  • (Hash)


231
232
233
# File 'lib/checkoff/projects.rb', line 231

def as_cache_key
  {}
end

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

Parameters:

  • project (Asana::Resources::Project)
  • field_name (Symbol, Array)
  • period (Symbol, Array(Symbol,Integer))

    See Checkoff::Timing#in_period? - :now_or_before,:this_week

Returns:

  • (Boolean)


223
224
225
226
227
228
# File 'lib/checkoff/projects.rb', line 223

def in_period?(project, field_name, period)
  # @type [Date,Time,nil]
  project_date = project_timing.date_or_time_field_by_name(project, field_name)

  timing.in_period?(project_date, period)
end

#project(workspace_name, project_name, extra_fields: []) ⇒ Asana::Resources::Project?

pulls an Asana API project class given a name

Parameters:

  • workspace_name (String)
  • project_name (String, Symbol)
    • :my_tasks or a project name

  • extra_fields (Array<String>) (defaults to: [])

Returns:

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


105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/checkoff/projects.rb', line 105

def project(workspace_name, project_name, extra_fields: [])
  if project_name.is_a?(Symbol) && project_name.to_s.start_with?('my_tasks')
    my_tasks(workspace_name)
  else
    # @type [Enumerable<Asana::Resources::Project>]
    ps = projects_by_workspace_name(workspace_name, extra_fields:)
    # @type <Asana::Resources::Project,nil>
    # @sg-ignore
    project = ps.find { _1.name == project_name }
    project_by_gid(project.gid, extra_fields:) unless project.nil?
  end
end

#project_by_gid(gid, extra_fields: []) ⇒ Asana::Resources::Project?

Parameters:

  • gid (String)
  • extra_fields (Array<String>) (defaults to: [])

Returns:

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


136
137
138
139
140
141
# File 'lib/checkoff/projects.rb', line 136

def project_by_gid(gid, extra_fields: [])
  projects.find_by_id(gid, options: project_options(extra_project_fields: extra_fields))
rescue Asana::Errors::NotFound => e
  debug e
  nil
end

#project_fields(extra_project_fields: []) ⇒ Array<String>

Parameters:

  • extra_project_fields (Array<String>) (defaults to: [])

Returns:

  • (Array<String>)


86
87
88
# File 'lib/checkoff/projects.rb', line 86

def project_fields(extra_project_fields: [])
  (%w[name custom_fields] + extra_project_fields).sort.uniq
end

#project_options(extra_project_fields: []) ⇒ Hash{Symbol => Object}

Default options used in Asana API to pull projects

Parameters:

  • extra_project_fields (Array<String>) (defaults to: [])

Returns:

  • (Hash{Symbol => Object})


95
96
97
# File 'lib/checkoff/projects.rb', line 95

def project_options(extra_project_fields: [])
  { fields: project_fields(extra_project_fields:) }
end

#project_or_raise(workspace_name, project_name, extra_fields: []) ⇒ Asana::Resources::Project

Parameters:

  • workspace_name (String)
  • project_name (String, Symbol)
    • :my_tasks or a project name

  • extra_fields (Array<String>) (defaults to: [])

Returns:

  • (Asana::Resources::Project)


124
125
126
127
128
129
# File 'lib/checkoff/projects.rb', line 124

def project_or_raise(workspace_name, project_name, extra_fields: [])
  p = project(workspace_name, project_name, extra_fields:)
  raise "Could not find project #{project_name.inspect} under workspace #{workspace_name}." if p.nil?

  p
end

#project_ready?(project, period: :now_or_before) ⇒ Boolean

Indicates a project 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 project is ready if there is no start date, or if the start date is today or in the past.

Parameters:

  • project (Asana::Resources::Project)
  • period (Symbol, Array(Symbol, Integer)) (defaults to: :now_or_before)

    See Checkoff::Timing#in_period? - :now_or_before,:this_week

Returns:

  • (Boolean)


216
217
218
# File 'lib/checkoff/projects.rb', line 216

def project_ready?(project, period: :now_or_before)
  in_period?(project, :ready, period)
end

#project_to_h(project_obj, project: :not_specified) ⇒ Hash

Parameters:

  • project_obj (Asana::Resources::Project)
  • project (String, Symbol) (defaults to: :not_specified)
    • :not_specified, :my_tasks or a project name

Returns:

  • (Hash)


203
204
205
# File 'lib/checkoff/projects.rb', line 203

def project_to_h(project_obj, project: :not_specified)
  project_hashes.project_to_h(project_obj, project:)
end

#projects_by_workspace_name(workspace_name, extra_fields: []) ⇒ Enumerable<Asana::Resources::Project>

Parameters:

  • workspace_name (String)
  • extra_fields (Array<String>) (defaults to: [])

Returns:

  • (Enumerable<Asana::Resources::Project>)


188
189
190
191
192
193
194
195
196
# File 'lib/checkoff/projects.rb', line 188

def projects_by_workspace_name(workspace_name, extra_fields: [])
  workspace = @workspaces.workspace_or_raise(workspace_name)
  # 15 minute cache resulted in 'Your pagination token has
  # expired', so let's cache this a super long time and force
  # evaluation
  projects.find_by_workspace(workspace: workspace.gid,
                             per_page: 100,
                             options: project_options(extra_project_fields: extra_fields)).to_a
end

#task_fields(extra_fields: []) ⇒ Array<String>

Parameters:

  • extra_fields (Array<String>) (defaults to: [])

Returns:

  • (Array<String>)


60
61
62
63
64
# File 'lib/checkoff/projects.rb', line 60

def task_fields(extra_fields: [])
  (%w[name completed_at start_at start_on due_at due_on tags
      memberships.project.gid memberships.project.name
      memberships.section.name dependencies] + extra_fields).sort.uniq
end

#task_options(extra_fields: [], only_uncompleted: false) ⇒ Hash{Symbol => undefined}

Default options used in Asana API to pull tasks

Parameters:

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

Returns:

  • (Hash{Symbol => undefined})


72
73
74
75
76
77
78
79
80
81
# File 'lib/checkoff/projects.rb', line 72

def task_options(extra_fields: [], only_uncompleted: false)
  options = {
    per_page: 100,
    options: {
      fields: task_fields(extra_fields:),
    },
  }
  options[:completed_since] = '9999-12-01' if only_uncompleted
  options
end

#tasks_from_project(project, only_uncompleted: true, extra_fields: []) ⇒ Enumerable<Asana::Resources::Task>

Pull task objects from a named project

Parameters:

  • project (Asana::Resources::Project)
  • only_uncompleted (Boolean) (defaults to: true)
  • extra_fields (Array<String>) (defaults to: [])

Returns:

  • (Enumerable<Asana::Resources::Task>)


158
159
160
161
162
163
164
# File 'lib/checkoff/projects.rb', line 158

def tasks_from_project(project,
                       only_uncompleted: true,
                       extra_fields: [])
  tasks_from_project_gid(project.gid,
                         only_uncompleted:,
                         extra_fields:)
end

#tasks_from_project_gid(project_gid, only_uncompleted: true, extra_fields: []) ⇒ Enumerable<Asana::Resources::Task>

Pull task objects from a project identified by a gid

Parameters:

  • project_gid (String)
  • only_uncompleted (Boolean) (defaults to: true)
  • extra_fields (Array<String>) (defaults to: [])

Returns:

  • (Enumerable<Asana::Resources::Task>)


173
174
175
176
177
178
179
180
181
182
# File 'lib/checkoff/projects.rb', line 173

def tasks_from_project_gid(project_gid,
                           only_uncompleted: true,
                           extra_fields: [])
  options = task_options(extra_fields:, only_uncompleted:)
  options[:project] = project_gid
  # Note: 30 minute cache time on a raw Enumerable from SDK gives
  # 'Your pagination token has expired' errors.  So we go ahead
  # and eagerly evaluate here so we can enjoy the cache.
  client.tasks.find_all(**options).to_a
end