checkoff 0.243.0 → 0.244.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/checkoff.gemspec +39 -22
  3. data/lib/checkoff/attachments.rb +9 -9
  4. data/lib/checkoff/cli.rb +28 -16
  5. data/lib/checkoff/clients.rb +2 -0
  6. data/lib/checkoff/custom_fields.rb +5 -8
  7. data/lib/checkoff/events.rb +0 -2
  8. data/lib/checkoff/internal/asana_event_enrichment.rb +6 -4
  9. data/lib/checkoff/internal/asana_event_filter.rb +0 -3
  10. data/lib/checkoff/internal/config_loader.rb +1 -1
  11. data/lib/checkoff/internal/logging.rb +2 -0
  12. data/lib/checkoff/internal/project_hashes.rb +0 -1
  13. data/lib/checkoff/internal/project_timing.rb +0 -5
  14. data/lib/checkoff/internal/search_url/custom_field_param_converter.rb +6 -5
  15. data/lib/checkoff/internal/search_url/custom_field_variant.rb +0 -1
  16. data/lib/checkoff/internal/search_url/date_param_converter.rb +2 -5
  17. data/lib/checkoff/internal/search_url/parser.rb +4 -0
  18. data/lib/checkoff/internal/search_url/results_merger.rb +1 -0
  19. data/lib/checkoff/internal/search_url/simple_param_converter.rb +2 -4
  20. data/lib/checkoff/internal/selector_classes/common.rb +19 -1
  21. data/lib/checkoff/internal/selector_classes/function_evaluator.rb +1 -1
  22. data/lib/checkoff/internal/selector_classes/project.rb +8 -0
  23. data/lib/checkoff/internal/selector_classes/section.rb +2 -2
  24. data/lib/checkoff/internal/selector_classes/task/function_evaluator.rb +9 -0
  25. data/lib/checkoff/internal/selector_classes/task.rb +28 -27
  26. data/lib/checkoff/internal/selector_evaluator.rb +2 -0
  27. data/lib/checkoff/internal/task_hashes.rb +0 -3
  28. data/lib/checkoff/internal/task_timing.rb +4 -6
  29. data/lib/checkoff/internal/thread_local.rb +1 -1
  30. data/lib/checkoff/monkeypatches/resource_marshalling.rb +0 -2
  31. data/lib/checkoff/my_tasks.rb +2 -1
  32. data/lib/checkoff/portfolios.rb +1 -0
  33. data/lib/checkoff/projects.rb +5 -5
  34. data/lib/checkoff/resources.rb +0 -2
  35. data/lib/checkoff/section_selectors.rb +0 -1
  36. data/lib/checkoff/sections.rb +18 -14
  37. data/lib/checkoff/subtasks.rb +1 -1
  38. data/lib/checkoff/tags.rb +2 -4
  39. data/lib/checkoff/task_searches.rb +2 -8
  40. data/lib/checkoff/task_selectors.rb +2 -3
  41. data/lib/checkoff/tasks.rb +18 -11
  42. data/lib/checkoff/timelines.rb +12 -6
  43. data/lib/checkoff/timing.rb +5 -8
  44. data/lib/checkoff/version.rb +1 -1
  45. data/lib/checkoff/workspaces.rb +5 -4
  46. data/rbi/checkoff.rbi +618 -483
  47. data/sig/checkoff.rbs +574 -481
  48. metadata +1 -1
@@ -97,7 +97,7 @@ module Checkoff
97
97
  end
98
98
 
99
99
  # pulls an Asana API project class given a name
100
- # @param [String] workspace_name
100
+ # @param [String, Symbol] workspace_name
101
101
  # @param [String,Symbol] project_name - :my_tasks or a project name
102
102
  # @param [Array<String>] extra_fields
103
103
  #
@@ -116,10 +116,11 @@ module Checkoff
116
116
  end
117
117
  cache_method :project, REALLY_LONG_CACHE_TIME
118
118
 
119
- # @param workspace_name [String]
119
+ # @param workspace_name [String, Symbol]
120
120
  # @param project_name [String,Symbol] - :my_tasks or a project name
121
121
  # @param [Array<String>] extra_fields
122
122
  #
123
+ # @sg-ignore
123
124
  # @return [Asana::Resources::Project]
124
125
  def project_or_raise(workspace_name, project_name, extra_fields: [])
125
126
  p = project(workspace_name, project_name, extra_fields:)
@@ -182,7 +183,7 @@ module Checkoff
182
183
  end
183
184
  cache_method :tasks_from_project_gid, REALLY_LONG_CACHE_TIME
184
185
 
185
- # @param [String] workspace_name
186
+ # @param [String, Symbol] workspace_name
186
187
  # @param [Array<String>] extra_fields
187
188
  # @return [Enumerable<Asana::Resources::Project>]
188
189
  def projects_by_workspace_name(workspace_name, extra_fields: [])
@@ -254,12 +255,11 @@ module Checkoff
254
255
  end
255
256
  cache_method :projects, LONG_CACHE_TIME
256
257
 
257
- # @param [String] workspace_name
258
+ # @param [String, Symbol] workspace_name
258
259
  #
259
260
  # @return [Asana::Resources::Project]
260
261
  def my_tasks(workspace_name)
261
262
  workspace = @workspaces.workspace_or_raise(workspace_name)
262
- # @sg-ignore
263
263
  result = client.user_task_lists.get_user_task_list_for_user(user_gid: 'me',
264
264
  workspace: workspace.gid)
265
265
  gid = result.gid
@@ -113,10 +113,8 @@ module Checkoff
113
113
  class << self
114
114
  # @return [void]
115
115
  def run
116
- # # @sg-ignore
117
116
  # # @type [String]
118
117
  # workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
119
- # # @sg-ignore
120
118
  # # @type [String]
121
119
  # resource_name = ARGV[1] || raise('Please pass resource name as second argument')
122
120
  # resources = Checkoff::Resources.new
@@ -43,7 +43,6 @@ module Checkoff
43
43
  # section details. Examples: [:tag, 'foo'] [:not, [:tag, 'foo']] [:tag, 'foo']
44
44
  # @return [Boolean]
45
45
  def filter_via_section_selector(section, section_selector)
46
- # @sg-ignore
47
46
  evaluator = SectionSelectorEvaluator.new(section:, sections:, custom_fields:,
48
47
  client:)
49
48
  !!evaluator.evaluate(section_selector)
@@ -55,7 +55,7 @@ module Checkoff
55
55
  end
56
56
 
57
57
  # Returns a list of Asana API section objects for a given project
58
- # @param workspace_name [String]
58
+ # @param workspace_name [String, Symbol]
59
59
  # @param project_name [String, Symbol]
60
60
  # @param extra_fields [Array<String>]
61
61
  #
@@ -115,9 +115,9 @@ module Checkoff
115
115
 
116
116
  # Pulls task objects from a specified section
117
117
  #
118
- # @param workspace_name [String]
118
+ # @param workspace_name [String, Symbol]
119
119
  # @param project_name [String, Symbol]
120
- # @param section_name [String, nil]
120
+ # @param section_name [String, nil, Symbol]
121
121
  # @param only_uncompleted [Boolean]
122
122
  # @param extra_fields [Array<String>]
123
123
  #
@@ -141,8 +141,8 @@ module Checkoff
141
141
  # @param project_name [String, Symbol]
142
142
  # @param section_name [String, nil]
143
143
  #
144
- # @sg-ignore
145
144
  # @return [Array<String>]
145
+ # @sg-ignore
146
146
  def section_task_names(workspace_name, project_name, section_name)
147
147
  task_array = tasks(workspace_name, project_name, section_name)
148
148
  # @type [Array<String>]
@@ -150,13 +150,13 @@ module Checkoff
150
150
  end
151
151
  cache_method :section_task_names, SHORT_CACHE_TIME
152
152
 
153
- # @param workspace_name [String]
153
+ # @param workspace_name [String, Symbol]
154
154
  # @param project_name [String, Symbol]
155
- # @param section_name [String, nil]
155
+ # @param section_name [String, nil, Symbol]
156
156
  # @param extra_section_fields [Array<String>]
157
157
  #
158
- # @sg-ignore
159
158
  # @return [Asana::Resources::Section]
159
+ # @sg-ignore section() is nil-checked below
160
160
  def section_or_raise(workspace_name, project_name, section_name, extra_section_fields: [])
161
161
  s = section(workspace_name, project_name, section_name,
162
162
  extra_section_fields:)
@@ -171,7 +171,7 @@ module Checkoff
171
171
  end
172
172
  cache_method :section_or_raise, LONG_CACHE_TIME
173
173
 
174
- # @param name [String]
174
+ # @param name [String, nil]
175
175
  # @return [String, nil]
176
176
  def section_key(name)
177
177
  inbox_section_names = ['(no section)', 'Untitled section', 'Inbox', 'Recently assigned']
@@ -187,11 +187,13 @@ module Checkoff
187
187
  sections = sections_by_project_gid(section.project.fetch('gid'))
188
188
 
189
189
  # @type [Array<Asana::Resources::Section>]
190
+ # @sg-ignore
190
191
  sections = sections.to_a
191
192
 
192
193
  index = sections.find_index { |s| s.gid == section.gid }
193
194
  return nil if index.nil? || index.zero?
194
195
 
196
+ # @sg-ignore
195
197
  sections[index - 1]
196
198
  end
197
199
  cache_method :previous_section, SHORT_CACHE_TIME
@@ -214,10 +216,9 @@ module Checkoff
214
216
  {}
215
217
  end
216
218
 
217
- # @sg-ignore
218
- # @param workspace_name [String]
219
+ # @param workspace_name [String, Symbol]
219
220
  # @param project_name [String, Symbol]
220
- # @param section_name [String, nil]
221
+ # @param section_name [String, nil, Symbol]
221
222
  # @param extra_section_fields [Array<String>]
222
223
  #
223
224
  # @return [Asana::Resources::Section, nil]
@@ -268,7 +269,6 @@ module Checkoff
268
269
  # @return [Hash{String, nil => Enumerable<Asana::Resources::Task>}]
269
270
  def by_section(tasks, project_gid)
270
271
  by_section = {}
271
- # @sg-ignore
272
272
  sections = client.sections.get_sections_for_project(project_gid:,
273
273
  options: { fields: ['name'] })
274
274
  sections.each_entry { |section| by_section[section_key(section.name)] = [] }
@@ -282,7 +282,10 @@ module Checkoff
282
282
  # @param project_gid [String]
283
283
  # @return [void]
284
284
  def file_task_by_section(by_section, task, project_gid)
285
- membership = task.memberships.find { |m| T.cast(m['project'], T::Hash[String, T.untyped])['gid'] == project_gid }
285
+ membership = task.memberships.find do |m|
286
+ # @sg-ignore
287
+ T.cast(m['project'], T::Hash[String, T.untyped])['gid'] == project_gid
288
+ end
286
289
  raise "Could not find task in project_gid #{project_gid}: #{task}" if membership.nil?
287
290
 
288
291
  section = T.cast(membership['section'], T::Hash[String, T.untyped])
@@ -295,9 +298,10 @@ module Checkoff
295
298
  by_section.fetch(current_section) << task
296
299
  end
297
300
 
298
- # @param workspace_name [String]
301
+ # @param workspace_name [String, Symbol]
299
302
  # @param project_name [String, Symbol]
300
303
  # @return [Asana::Resources::Project]
304
+ # @sg-ignore projects.project may be nil but is raised below
301
305
  def project_or_raise(workspace_name, project_name)
302
306
  raise ArgumentError, 'Provide nil project_name' if T.unsafe(project_name).nil?
303
307
 
@@ -46,7 +46,6 @@ module Checkoff
46
46
  current_section = nil
47
47
  by_section = { nil => [] }
48
48
  tasks.each do |task|
49
- # @sg-ignore
50
49
  current_section, by_section = file_task_by_section(current_section,
51
50
  by_section, task)
52
51
  end
@@ -90,6 +89,7 @@ module Checkoff
90
89
  # as memberships with a separate API within a task.
91
90
  #
92
91
  # @param subtask [Asana::Resources::Task]
92
+ # @sg-ignore
93
93
  def subtask_section?(subtask)
94
94
  subtask.is_rendered_as_separator
95
95
  end
data/lib/checkoff/tags.rb CHANGED
@@ -79,6 +79,7 @@ module Checkoff
79
79
  # @param tag_name [String]
80
80
  #
81
81
  # @return [Asana::Resources::Tag]
82
+ # @sg-ignore
82
83
  def tag_or_raise(workspace_name, tag_name)
83
84
  t = tag(workspace_name, tag_name)
84
85
 
@@ -92,7 +93,6 @@ module Checkoff
92
93
  # @param tag_name [String]
93
94
  #
94
95
  # @return [Asana::Resources::Tag,nil]
95
- # @sg-ignore
96
96
  def tag(workspace_name, tag_name)
97
97
  workspace = workspaces.workspace_or_raise(workspace_name)
98
98
  tags = client.tags.get_tags_for_workspace(workspace_gid: workspace.gid)
@@ -111,10 +111,10 @@ module Checkoff
111
111
 
112
112
  # @param options [Hash{Symbol => Object}]
113
113
  #
114
- # @sg-ignore
115
114
  # @return [Hash{Symbol => Object}]
116
115
  def build_params(options)
117
116
  { limit: options[:per_page], completed_since: options[:completed_since] }.reject do |_, v|
117
+ # @sg-ignore
118
118
  v.nil? || Array(v).empty?
119
119
  end
120
120
  end
@@ -135,10 +135,8 @@ module Checkoff
135
135
  class << self
136
136
  # @return [void]
137
137
  def run
138
- # @sg-ignore
139
138
  # @type [String]
140
139
  workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
141
- # @sg-ignore
142
140
  # @type [String]
143
141
  tag_name = ARGV[1] || raise('Please pass tag name as second argument')
144
142
  tags = Checkoff::Tags.new
@@ -71,9 +71,9 @@ module Checkoff
71
71
  # @return [Enumerable<Asana::Resources::Task>]
72
72
  def task_search(workspace_name, url, extra_fields: [])
73
73
  workspace = workspaces.workspace_or_raise(workspace_name)
74
- # @sg-ignore
75
74
  api_params, task_selector = @search_url_parser.convert_params(url)
76
75
  debug { "Task search params: api_params=#{api_params}, task_selector=#{task_selector}" }
76
+ # @sg-ignore
77
77
  raw_task_search(api_params, workspace_gid: workspace.gid, task_selector:,
78
78
  extra_fields:)
79
79
  end
@@ -93,13 +93,9 @@ module Checkoff
93
93
  def raw_task_search(api_params,
94
94
  workspace_gid:, extra_fields: [], task_selector: [],
95
95
  fetch_all: true)
96
- # @sg-ignore
97
96
  tasks = api_task_search_request(api_params, workspace_gid:, extra_fields:)
98
97
 
99
- if fetch_all && tasks.count == 100
100
- # @sg-ignore
101
- tasks = iterated_raw_task_search(api_params, workspace_gid:, extra_fields:)
102
- end
98
+ tasks = iterated_raw_task_search(api_params, workspace_gid:, extra_fields:) if fetch_all && tasks.count == 100
103
99
 
104
100
  debug { "#{tasks.count} raw tasks returned" }
105
101
 
@@ -203,10 +199,8 @@ module Checkoff
203
199
  class << self
204
200
  # @return [void]
205
201
  def run
206
- # @sg-ignore
207
202
  # @type [String]
208
203
  workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
209
- # @sg-ignore
210
204
  # @type [String]
211
205
  url = ARGV[1] || raise('Please pass task search URL as second argument')
212
206
  task_searches = Checkoff::TaskSearches.new
@@ -20,7 +20,6 @@ module Checkoff
20
20
  LONG_CACHE_TIME = MINUTE * 15
21
21
  SHORT_CACHE_TIME = MINUTE
22
22
 
23
- # @sg-ignore
24
23
  # @param [Hash,Checkoff::Internal::EnvFallbackConfigLoader] config
25
24
  # @param [Asana::Client] client
26
25
  # @param [Checkoff::Tasks] tasks
@@ -64,8 +63,8 @@ module Checkoff
64
63
  # bundle exec ./task_selectors.rb
65
64
  # :nocov:
66
65
  class << self
67
- # @sg-ignore
68
66
  # @return [String]
67
+ # @sg-ignore
69
68
  def project_name
70
69
  ARGV[1] || raise('Please pass project name to pull tasks from as first argument')
71
70
  end
@@ -76,8 +75,8 @@ module Checkoff
76
75
  ARGV[0] || raise('Please pass workspace name as first argument')
77
76
  end
78
77
 
79
- # @sg-ignore
80
78
  # @return [Array(Symbol, Array)]
79
+ # @sg-ignore
81
80
  def task_selector
82
81
  task_selector_json = ARGV[2] || raise('Please pass task_selector in JSON form as third argument')
83
82
 
@@ -110,13 +110,12 @@ module Checkoff
110
110
 
111
111
  # Pull a specific task by name
112
112
  #
113
- # @param workspace_name [String]
113
+ # @param workspace_name [String, Symbol]
114
114
  # @param project_name [String, Symbol]
115
115
  # @param section_name [String, nil, Symbol]
116
116
  # @param task_name [String]
117
117
  # @param only_uncompleted [Boolean]
118
118
  # @param extra_fields [Array<String>]
119
- # @sg-ignore
120
119
  # @return [Asana::Resources::Task, nil]
121
120
  def task(workspace_name, project_name, task_name,
122
121
  section_name: :unspecified,
@@ -124,6 +123,7 @@ module Checkoff
124
123
  extra_fields: [])
125
124
  thread_local = Checkoff::Internal::ThreadLocal.new
126
125
  # @type [String]
126
+ # @sg-ignore
127
127
  task_gid = thread_local.with_thread_local_variable(:suppress_asana_webhook_watch_creation,
128
128
  true) do
129
129
  gid_for_task(workspace_name, project_name, section_name, task_name)
@@ -134,21 +134,21 @@ module Checkoff
134
134
  end
135
135
  cache_method :task, LONG_CACHE_TIME
136
136
 
137
- # @param workspace_name [String]
137
+ # @param workspace_name [String, Symbol]
138
138
  # @param project_name [String, Symbol]
139
139
  # @param section_name [String, nil, Symbol]
140
140
  # @param task_name [String]
141
141
  #
142
142
  # @return [String, nil]
143
- # @sg-ignore
144
143
  def gid_for_task(workspace_name, project_name, section_name, task_name)
145
- # @sg-ignore
146
144
  t = tasks(workspace_name,
147
145
  project_name,
148
146
  section_name:,
149
147
  only_uncompleted: false)
150
148
  task_for_gid = t.find { |task| task.name == task_name }
151
- task_for_gid&.gid
149
+ return nil if task_for_gid.nil?
150
+
151
+ task_for_gid.gid
152
152
  end
153
153
  cache_method :gid_for_task, REALLY_LONG_CACHE_TIME
154
154
 
@@ -179,6 +179,7 @@ module Checkoff
179
179
  # @param name [String]
180
180
  # @param workspace_gid [String]
181
181
  # @param assignee_gid [String]
182
+ # @sg-ignore
182
183
  #
183
184
  # @return [Asana::Resources::Task]
184
185
  def add_task(name,
@@ -200,6 +201,7 @@ module Checkoff
200
201
 
201
202
  # True if any of the task's dependencies are marked incomplete
202
203
  #
204
+ # @sg-ignore
203
205
  # Include 'dependencies.gid' in extra_fields of task passed in.
204
206
  #
205
207
  # @param task [Asana::Resources::Task]
@@ -212,7 +214,6 @@ module Checkoff
212
214
  #
213
215
  # https://github.com/Asana/ruby-asana/issues/125
214
216
 
215
- # @sg-ignore
216
217
  # @type [Enumerable<Asana::Resources::Task>, nil]
217
218
  dependencies = task.instance_variable_get(:@dependencies) || []
218
219
 
@@ -220,6 +221,7 @@ module Checkoff
220
221
  # the real bummer though is that asana doesn't let you fetch
221
222
  # the completion status of dependencies, so we need to do this
222
223
  # regardless:
224
+ # @sg-ignore
223
225
  parent_task_gid = parent_task_info.fetch('gid')
224
226
 
225
227
  parent_task = task_by_gid(parent_task_gid, only_uncompleted: false)
@@ -289,12 +291,15 @@ module Checkoff
289
291
  # @param task [Asana::Resources::Task]
290
292
  # @param portfolio_name [String]
291
293
  # @param workspace_name [String]
294
+ # @return [Boolean]
295
+ # @sg-ignore
292
296
  def in_portfolio_named?(task,
293
297
  portfolio_name,
294
298
  workspace_name: @workspaces.default_workspace.name)
295
299
  portfolio_projects = @portfolios.projects_in_portfolio(workspace_name, portfolio_name)
296
300
  task.memberships.any? do |membership|
297
- project_gid = membership.fetch('project').fetch('gid')
301
+ m = T.cast(membership, T::Hash[String, T.untyped])
302
+ project_gid = T.cast(m.fetch('project'), T::Hash[String, T.untyped]).fetch('gid')
298
303
  portfolio_projects.any? do |portfolio_project|
299
304
  portfolio_project.gid == project_gid
300
305
  end
@@ -306,6 +311,7 @@ module Checkoff
306
311
  # @param task [Asana::Resources::Task]
307
312
  # @param portfolio_name [String]
308
313
  # @param workspace_name [String]
314
+ # @return [Boolean]
309
315
  def in_portfolio_more_than_once?(task,
310
316
  portfolio_name,
311
317
  workspace_name: @workspaces.default_workspace.name)
@@ -313,7 +319,8 @@ module Checkoff
313
319
  portfolio_project_gids = portfolio_projects.map(&:gid)
314
320
  seen = T.let(false, T::Boolean)
315
321
  task.memberships.each do |membership|
316
- project_gid = membership.fetch('project').fetch('gid')
322
+ m = T.cast(membership, T::Hash[String, T.untyped])
323
+ project_gid = T.cast(m.fetch('project'), T::Hash[String, T.untyped]).fetch('gid')
317
324
  next unless portfolio_project_gids.include?(project_gid)
318
325
  return true if seen
319
326
 
@@ -341,7 +348,7 @@ module Checkoff
341
348
  @task_hashes ||= Checkoff::Internal::TaskHashes.new
342
349
  end
343
350
 
344
- # @param workspace_name [String]
351
+ # @param workspace_name [String, Symbol]
345
352
  # @param project_name [String, Symbol]
346
353
  # @param section_name [String, nil, Symbol] - :unspecified
347
354
  # @param only_uncompleted [Boolean]
@@ -377,8 +384,8 @@ module Checkoff
377
384
  @projects ||= @sections.projects
378
385
  end
379
386
 
380
- # @sg-ignore
381
387
  # @return [String]
388
+ # @sg-ignore
382
389
  def default_assignee_gid
383
390
  @config.fetch(:default_assignee_gid)
384
391
  end
@@ -47,9 +47,9 @@ module Checkoff
47
47
 
48
48
  # @param task [Asana::Resources::Task]
49
49
  # @param limit_to_portfolio_gid [String, nil]
50
+ # @return [Boolean]
50
51
  def task_dependent_on_previous_section_last_milestone?(task, limit_to_portfolio_gid: nil)
51
52
  task_data = @tasks.task_to_h(task)
52
- # @sg-ignore
53
53
  # @type [Array<Hash{String => Hash{String => String}}>]
54
54
  memberships_data = task_data.fetch('memberships')
55
55
  memberships_data.all? do |membership_data|
@@ -57,12 +57,15 @@ module Checkoff
57
57
  section_data = membership_data.fetch('section')
58
58
  section_gid = section_data.fetch('gid')
59
59
  section = @sections.section_by_gid(section_gid)
60
+ # @sg-ignore
60
61
  task_data_dependent_on_previous_section_last_milestone?(task_data, section)
61
62
  end
62
63
  end
63
64
 
64
65
  # @param task [Asana::Resources::Task]
65
66
  # @param limit_to_portfolio_name [String, nil]
67
+ # @return [Boolean]
68
+ # @sg-ignore
66
69
  def last_task_milestone_depends_on_this_task?(task, limit_to_portfolio_name: nil)
67
70
  unless limit_to_portfolio_name.nil?
68
71
  limit_to_projects = @portfolios.projects_in_portfolio(@workspaces.default_workspace.name,
@@ -72,12 +75,13 @@ module Checkoff
72
75
  all_dependent_task_gids = nil
73
76
  task.memberships.all? do |membership_data|
74
77
  unless limit_to_portfolio_name.nil?
78
+ # @sg-ignore
75
79
  project_gid = membership_data.fetch('project').fetch('gid')
76
80
  next true unless limit_to_projects.map(&:gid).include? project_gid
77
81
  end
78
- # @type [Hash{String => String}]
82
+ # @sg-ignore
79
83
  section_data = membership_data.fetch('section')
80
- # @type [String]
84
+ # @sg-ignore
81
85
  section_gid = section_data.fetch('gid')
82
86
 
83
87
  last_milestone = last_milestone_in_section(section_gid)
@@ -88,12 +92,15 @@ module Checkoff
88
92
 
89
93
  all_dependent_task_gids ||= @tasks.all_dependent_tasks(task).map(&:gid)
90
94
 
95
+ # @sg-ignore
91
96
  all_dependent_task_gids.include? last_milestone.gid
92
97
  end
93
98
  end
94
99
 
95
100
  # @param task [Asana::Resources::Task]
96
101
  # @param limit_to_portfolio_name [String, nil]
102
+ # @return [Boolean]
103
+ # @sg-ignore
97
104
  def any_milestone_depends_on_this_task?(task, limit_to_portfolio_name: nil)
98
105
  unless limit_to_portfolio_name.nil?
99
106
  limit_to_projects = @portfolios.projects_in_portfolio(@workspaces.default_workspace.name,
@@ -103,6 +110,7 @@ module Checkoff
103
110
  all_dependent_milestones = nil
104
111
  task.memberships.all? do |membership_data|
105
112
  unless limit_to_portfolio_name.nil?
113
+ # @sg-ignore
106
114
  project_gid = membership_data.fetch('project').fetch('gid')
107
115
  next true unless limit_to_projects.map(&:gid).include? project_gid
108
116
  end
@@ -116,6 +124,7 @@ module Checkoff
116
124
  dependent_task.resource_subtype == 'milestone'
117
125
  end
118
126
 
127
+ # @sg-ignore
119
128
  all_dependent_milestones.any? do |milestone|
120
129
  milestone.memberships.any? do |milestone_membership_data|
121
130
  milestone_membership_data.fetch('project').fetch('gid') == project_gid
@@ -148,7 +157,6 @@ module Checkoff
148
157
  previous_section_last_milestone = last_milestone_in_section(previous_section.gid)
149
158
  return true if previous_section_last_milestone.nil?
150
159
 
151
- # @sg-ignore
152
160
  # @type [Array<Hash{String => String}>]
153
161
  dependencies = task_data.fetch('dependencies')
154
162
  return false if dependencies.empty?
@@ -167,10 +175,8 @@ module Checkoff
167
175
  class << self
168
176
  # @return [void]
169
177
  def run
170
- # @sg-ignore
171
178
  # @type [String]
172
179
  # workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
173
- # @sg-ignore
174
180
  # @type [String]
175
181
  # timeline_name = ARGV[1] || raise('Please pass timeline name as second argument')
176
182
  # timelines = Checkoff::Timelines.new
@@ -43,6 +43,7 @@ module Checkoff
43
43
 
44
44
  return next_week?(date_or_time) if period == :next_week
45
45
 
46
+ # @sg-ignore
46
47
  return day_of_week?(date_or_time, period) if %i[monday tuesday wednesday thursday friday saturday
47
48
  sunday].include?(period)
48
49
 
@@ -71,9 +72,7 @@ module Checkoff
71
72
 
72
73
  date = date_or_time.to_date
73
74
 
74
- # @sg-ignore
75
75
  n_days_from_today = @today_getter.today + num_days
76
- # @sg-ignore
77
76
  date >= n_days_from_today
78
77
  end
79
78
 
@@ -92,9 +91,7 @@ module Checkoff
92
91
 
93
92
  date = date_or_time.to_date
94
93
 
95
- # @sg-ignore
96
94
  n_days_ago = @today_getter.today - num_days
97
- # @sg-ignore
98
95
  date < n_days_ago
99
96
  end
100
97
 
@@ -174,14 +171,15 @@ module Checkoff
174
171
  #
175
172
  # @return [Time]
176
173
  def n_days_from_now(num_days)
177
- (@now_getter.now + (num_days * 24 * 60 * 60))
174
+ Time.at(@now_getter.now.to_i + (num_days * 86_400))
178
175
  end
179
176
 
180
177
  # @param num_days [Integer]
181
178
  #
182
179
  # @return [Date]
180
+ # @sg-ignore
183
181
  def n_days_from_today(num_days)
184
- (@today_getter.today + num_days)
182
+ @today_getter.today + num_days
185
183
  end
186
184
 
187
185
  # @param date_or_time [Date,Time,nil]
@@ -212,7 +210,6 @@ module Checkoff
212
210
  # @param period_name [Symbol]
213
211
  # @param args [Object]
214
212
  def compound_in_period?(date_or_time, period_name, *args)
215
- # @sg-ignore
216
213
  return less_than_n_days_ago?(date_or_time, *args) if period_name == :less_than_n_days_ago
217
214
 
218
215
  return less_than_n_days_from_now?(date_or_time, *args) if period_name == :less_than_n_days_from_now
@@ -225,9 +222,9 @@ module Checkoff
225
222
  return greater_than_or_equal_to_n_days_from_today?(date_or_time, *args)
226
223
  end
227
224
 
228
- # @sg-ignore
229
225
  return between_relative_days?(date_or_time, *args) if period_name == :between_relative_days
230
226
 
227
+ # @sg-ignore
231
228
  raise "Teach me how to handle period [#{period_name.inspect}, #{args.map(&:inspect).join(', ')}]"
232
229
  end
233
230
 
@@ -4,5 +4,5 @@
4
4
  # Command-line and gem client for Asana (unofficial)
5
5
  module Checkoff
6
6
  # Version of library
7
- VERSION = '0.243.0'
7
+ VERSION = '0.244.0'
8
8
  end
@@ -34,8 +34,7 @@ module Checkoff
34
34
  end
35
35
 
36
36
  # Pulls an Asana workspace object
37
- # @param [String] workspace_name
38
- # @sg-ignore
37
+ # @param [String, Symbol] workspace_name
39
38
  # @return [Asana::Resources::Workspace, nil]
40
39
  def workspace(workspace_name)
41
40
  client.workspaces.find_all.find do |workspace|
@@ -45,13 +44,15 @@ module Checkoff
45
44
  cache_method :workspace, LONG_CACHE_TIME
46
45
 
47
46
  # @return [Asana::Resources::Workspace]
47
+ # @sg-ignore
48
48
  def default_workspace
49
49
  @asana_workspace.find_by_id(client, default_workspace_gid)
50
50
  end
51
51
  cache_method :default_workspace, REALLY_LONG_CACHE_TIME
52
52
 
53
- # @param [String] workspace_name
53
+ # @param [String, Symbol] workspace_name
54
54
  # @return [Asana::Resources::Workspace]
55
+ # @sg-ignore workspace() is nil-checked below
55
56
  def workspace_or_raise(workspace_name)
56
57
  w = workspace(workspace_name)
57
58
  raise "Could not find workspace #{workspace_name}" if w.nil?
@@ -59,8 +60,8 @@ module Checkoff
59
60
  w
60
61
  end
61
62
 
62
- # @sg-ignore
63
63
  # @return [String]
64
+ # @sg-ignore
64
65
  def default_workspace_gid
65
66
  @config.fetch(:default_workspace_gid)
66
67
  end