checkoff 0.258.0 → 0.261.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.
- checksums.yaml +4 -4
- data/lib/checkoff/attachments.rb +14 -15
- data/lib/checkoff/cli.rb +7 -12
- data/lib/checkoff/clients.rb +0 -2
- data/lib/checkoff/custom_fields.rb +13 -24
- data/lib/checkoff/internal/asana_event_enrichment.rb +11 -21
- data/lib/checkoff/internal/asana_event_filter.rb +1 -5
- data/lib/checkoff/internal/config_loader.rb +0 -2
- data/lib/checkoff/internal/logging.rb +0 -2
- data/lib/checkoff/internal/project_hashes.rb +26 -4
- data/lib/checkoff/internal/search_url/custom_field_param_converter.rb +9 -10
- data/lib/checkoff/internal/search_url/custom_field_variant.rb +7 -2
- data/lib/checkoff/internal/search_url/date_param_converter.rb +13 -10
- data/lib/checkoff/internal/search_url/parser.rb +2 -7
- data/lib/checkoff/internal/search_url/simple_param_converter.rb +1 -4
- data/lib/checkoff/internal/section_selector_evaluator.rb +1 -1
- data/lib/checkoff/internal/selector_classes/common/function_evaluator.rb +2 -2
- data/lib/checkoff/internal/selector_classes/common.rb +2 -3
- data/lib/checkoff/internal/selector_classes/function_evaluator.rb +9 -4
- data/lib/checkoff/internal/selector_classes/project/function_evaluator.rb +2 -2
- data/lib/checkoff/internal/selector_classes/project.rb +0 -1
- data/lib/checkoff/internal/selector_classes/section/function_evaluator.rb +2 -2
- data/lib/checkoff/internal/selector_classes/section.rb +1 -2
- data/lib/checkoff/internal/selector_classes/task/function_evaluator.rb +2 -2
- data/lib/checkoff/internal/selector_classes/task.rb +2 -26
- data/lib/checkoff/internal/selector_evaluator.rb +6 -5
- data/lib/checkoff/internal/task_hashes.rb +17 -9
- data/lib/checkoff/internal/task_timing.rb +0 -4
- data/lib/checkoff/internal/thread_local.rb +0 -2
- data/lib/checkoff/monkeypatches/resource_marshalling.rb +3 -3
- data/lib/checkoff/my_tasks.rb +1 -4
- data/lib/checkoff/portfolios.rb +0 -1
- data/lib/checkoff/project_selectors.rb +1 -1
- data/lib/checkoff/projects.rb +19 -8
- data/lib/checkoff/resources.rb +14 -3
- data/lib/checkoff/sections.rb +9 -9
- data/lib/checkoff/subtasks.rb +1 -2
- data/lib/checkoff/tags.rb +3 -4
- data/lib/checkoff/task_searches.rb +1 -8
- data/lib/checkoff/task_selectors.rb +25 -12
- data/lib/checkoff/tasks.rb +6 -14
- data/lib/checkoff/timelines.rb +3 -20
- data/lib/checkoff/timing.rb +1 -2
- data/lib/checkoff/version.rb +1 -1
- data/lib/checkoff/workspaces.rb +1 -7
- data/rbi/checkoff.rbi +99 -146
- data/sig/checkoff.rbs +86 -153
- metadata +2 -2
|
@@ -8,7 +8,7 @@ module Checkoff
|
|
|
8
8
|
module Common
|
|
9
9
|
# Base class to evaluate a project selector function given fully evaluated arguments
|
|
10
10
|
class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
|
|
11
|
-
# @param selector [Array
|
|
11
|
+
# @param selector [Array<Symbol, Array>, Symbol]
|
|
12
12
|
# @param custom_fields [Checkoff::CustomFields]
|
|
13
13
|
# @param _kwargs [Hash]
|
|
14
14
|
def initialize(selector:, custom_fields:, **_kwargs)
|
|
@@ -22,7 +22,7 @@ module Checkoff
|
|
|
22
22
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
|
-
# @return [Array
|
|
25
|
+
# @return [Array<Symbol, Array>, Symbol]
|
|
26
26
|
attr_reader :selector
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -223,8 +223,6 @@ module Checkoff
|
|
|
223
223
|
# @param resource [Asana::Resources::Task, Asana::Resources::Project]
|
|
224
224
|
# @param prefix [String]
|
|
225
225
|
# @return [boolish]
|
|
226
|
-
# @sg-ignore Checkoff::SelectorClasses::Common::NameStartsWithPFunctionEvaluator#evaluate
|
|
227
|
-
# return type could not be inferred
|
|
228
226
|
def evaluate(resource, prefix)
|
|
229
227
|
resource.name&.start_with?(prefix)
|
|
230
228
|
end
|
|
@@ -238,8 +236,9 @@ module Checkoff
|
|
|
238
236
|
|
|
239
237
|
# @param _resource [Asana::Resources::Task,Asana::Resources::Project]
|
|
240
238
|
# @return [String]
|
|
241
|
-
# @sg-ignore selector is only String here per matches?'s is_a?(String) check, in another method
|
|
242
239
|
def evaluate(_resource)
|
|
240
|
+
raise "Expected a String selector, got #{selector.inspect}" unless selector.is_a?(String)
|
|
241
|
+
|
|
243
242
|
selector
|
|
244
243
|
end
|
|
245
244
|
end
|
|
@@ -26,7 +26,6 @@ module Checkoff
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# @return [Boolean]
|
|
29
|
-
# @sg-ignore return type could not be inferred — abstract method body is only `raise`
|
|
30
29
|
def matches?
|
|
31
30
|
raise 'Override me!'
|
|
32
31
|
end
|
|
@@ -34,17 +33,23 @@ module Checkoff
|
|
|
34
33
|
# @param _task [Asana::Resources::Task]
|
|
35
34
|
# @param _args [Array<Object>]
|
|
36
35
|
# @return [Object]
|
|
37
|
-
# @sg-ignore return type could not be inferred — abstract method body is only `raise`
|
|
38
36
|
def evaluate(_task, *_args)
|
|
39
37
|
raise 'Implement me!'
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
private
|
|
43
41
|
|
|
44
|
-
# @param object [
|
|
42
|
+
# @param object [Array<Symbol, Array>, Symbol]
|
|
45
43
|
# @param fn_name [Symbol]
|
|
44
|
+
# @return [Boolean]
|
|
46
45
|
def fn?(object, fn_name)
|
|
47
|
-
object.is_a?(Array) && !object.empty?
|
|
46
|
+
if object.is_a?(Array) && !object.empty?
|
|
47
|
+
# @type [Array<Symbol, Array>]
|
|
48
|
+
array = object
|
|
49
|
+
[fn_name, fn_name.to_s].include?(array[0])
|
|
50
|
+
else
|
|
51
|
+
false
|
|
52
|
+
end
|
|
48
53
|
end
|
|
49
54
|
end
|
|
50
55
|
end
|
|
@@ -8,7 +8,7 @@ module Checkoff
|
|
|
8
8
|
module Project
|
|
9
9
|
# Base class to evaluate a project selector function given fully evaluated arguments
|
|
10
10
|
class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
|
|
11
|
-
# @param selector [Array
|
|
11
|
+
# @param selector [Array<Symbol, Array>, Symbol]
|
|
12
12
|
# @param projects [Checkoff::Projects]
|
|
13
13
|
# @param portfolios [Checkoff::Portfolios]
|
|
14
14
|
# @param workspaces [Checkoff::Workspaces]
|
|
@@ -36,7 +36,7 @@ module Checkoff
|
|
|
36
36
|
|
|
37
37
|
private
|
|
38
38
|
|
|
39
|
-
# @return [Array
|
|
39
|
+
# @return [Array<Symbol, Array>, Symbol]
|
|
40
40
|
attr_reader :selector
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -8,7 +8,7 @@ module Checkoff
|
|
|
8
8
|
module Section
|
|
9
9
|
# Base class to evaluate a project selector function given fully evaluated arguments
|
|
10
10
|
class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
|
|
11
|
-
# @param selector [Array
|
|
11
|
+
# @param selector [Array<Symbol, Array>, Symbol]
|
|
12
12
|
# @param client [Asana::Client]
|
|
13
13
|
# @param sections [Checkoff::Sections]
|
|
14
14
|
# @param kwargs [Hash]
|
|
@@ -30,7 +30,7 @@ module Checkoff
|
|
|
30
30
|
|
|
31
31
|
private
|
|
32
32
|
|
|
33
|
-
# @return [Array
|
|
33
|
+
# @return [Array<Symbol, Array>, Symbol]
|
|
34
34
|
attr_reader :selector
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -18,12 +18,11 @@ module Checkoff
|
|
|
18
18
|
# @param section [Asana::Resources::Section]
|
|
19
19
|
#
|
|
20
20
|
# @return [Boolean]
|
|
21
|
-
# @sg-ignore String, nil safe-nav chain isn't inferred as returning Boolean
|
|
22
21
|
def evaluate(section)
|
|
23
22
|
tasks = client.tasks.get_tasks(section: section.gid,
|
|
24
23
|
per_page: 100,
|
|
25
24
|
options: { fields: ['resource_subtype'] })
|
|
26
|
-
|
|
25
|
+
tasks.last&.resource_subtype == 'milestone'
|
|
27
26
|
end
|
|
28
27
|
end
|
|
29
28
|
|
|
@@ -8,7 +8,7 @@ module Checkoff
|
|
|
8
8
|
module Task
|
|
9
9
|
# Base class to evaluate a task selector function given fully evaluated arguments
|
|
10
10
|
class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
|
|
11
|
-
# @param selector [Array
|
|
11
|
+
# @param selector [Array<Symbol, Array>, Symbol]
|
|
12
12
|
# @param tasks [Checkoff::Tasks]
|
|
13
13
|
# @param timelines [Checkoff::Timelines]
|
|
14
14
|
# @param custom_fields [Checkoff::CustomFields]
|
|
@@ -36,7 +36,7 @@ module Checkoff
|
|
|
36
36
|
|
|
37
37
|
private
|
|
38
38
|
|
|
39
|
-
# @return [Array
|
|
39
|
+
# @return [Array<Symbol, Array>, Symbol]
|
|
40
40
|
attr_reader :selector
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -24,12 +24,8 @@ module Checkoff
|
|
|
24
24
|
# @type [Hash{'unwrapped' => Hash}]
|
|
25
25
|
task_data = tasks.task_to_h(task)
|
|
26
26
|
# @type [Hash{'membership_by_project_name' => Hash}]
|
|
27
|
-
# @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
|
|
28
|
-
# https://github.com/castwide/solargraph/pull/1228
|
|
29
27
|
unwrapped = task_data.fetch('unwrapped')
|
|
30
28
|
# @type [Array]
|
|
31
|
-
# @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
|
|
32
|
-
# https://github.com/castwide/solargraph/pull/1228
|
|
33
29
|
projects = unwrapped.fetch('membership_by_project_name').keys
|
|
34
30
|
!(projects - [:my_tasks]).empty?
|
|
35
31
|
end
|
|
@@ -53,12 +49,8 @@ module Checkoff
|
|
|
53
49
|
# @type [Hash{'unwrapped' => Hash}]
|
|
54
50
|
task_data = tasks.task_to_h(task)
|
|
55
51
|
# @type [Hash{'membership_by_section_name' => Hash}]
|
|
56
|
-
# @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
|
|
57
|
-
# https://github.com/castwide/solargraph/pull/1228
|
|
58
52
|
unwrapped = task_data.fetch('unwrapped')
|
|
59
53
|
# @type [Array]
|
|
60
|
-
# @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
|
|
61
|
-
# https://github.com/castwide/solargraph/pull/1228
|
|
62
54
|
section_names = unwrapped.fetch('membership_by_section_name').keys
|
|
63
55
|
section_names.any? do |section_name|
|
|
64
56
|
String(section_name).start_with?(section_name_prefix)
|
|
@@ -84,12 +76,8 @@ module Checkoff
|
|
|
84
76
|
# @type [Hash{'unwrapped' => Hash}]
|
|
85
77
|
task_data = tasks.task_to_h(task)
|
|
86
78
|
# @type [Hash{'membership_by_section_name' => Hash}]
|
|
87
|
-
# @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
|
|
88
|
-
# https://github.com/castwide/solargraph/pull/1228
|
|
89
79
|
unwrapped = task_data.fetch('unwrapped')
|
|
90
80
|
# @type [Array]
|
|
91
|
-
# @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
|
|
92
|
-
# https://github.com/castwide/solargraph/pull/1228
|
|
93
81
|
section_names = unwrapped.fetch('membership_by_section_name').keys
|
|
94
82
|
section_names.any?(section_name)
|
|
95
83
|
end
|
|
@@ -109,12 +97,8 @@ module Checkoff
|
|
|
109
97
|
# @param task [Asana::Resources::Task]
|
|
110
98
|
# @param project_name [String]
|
|
111
99
|
# @return [Boolean]
|
|
112
|
-
# @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
|
|
113
|
-
# https://github.com/castwide/solargraph/pull/1228
|
|
114
100
|
def evaluate(task, project_name)
|
|
115
101
|
project_names = task.memberships.map do |membership|
|
|
116
|
-
# @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
|
|
117
|
-
# https://github.com/castwide/solargraph/pull/1228
|
|
118
102
|
membership.fetch('project').fetch('name')
|
|
119
103
|
end
|
|
120
104
|
project_names.include?(project_name)
|
|
@@ -214,10 +198,8 @@ module Checkoff
|
|
|
214
198
|
|
|
215
199
|
# @param task [Asana::Resources::Task]
|
|
216
200
|
# @return [Boolean]
|
|
217
|
-
# @sg-ignore Checkoff::SelectorClasses::Task::UnassignedPFunctionEvaluator#evaluate
|
|
218
|
-
# return type could not be inferred
|
|
219
201
|
def evaluate(task)
|
|
220
|
-
|
|
202
|
+
task.assignee.nil?
|
|
221
203
|
end
|
|
222
204
|
end
|
|
223
205
|
|
|
@@ -231,10 +213,8 @@ module Checkoff
|
|
|
231
213
|
|
|
232
214
|
# @param task [Asana::Resources::Task]
|
|
233
215
|
# @return [Boolean]
|
|
234
|
-
# @sg-ignore Checkoff::SelectorClasses::Task::DueDateSetPFunctionEvaluator#evaluate
|
|
235
|
-
# return type could not be inferred
|
|
236
216
|
def evaluate(task)
|
|
237
|
-
|
|
217
|
+
!(task.due_at.nil? && task.due_on.nil?)
|
|
238
218
|
end
|
|
239
219
|
end
|
|
240
220
|
|
|
@@ -259,8 +239,6 @@ module Checkoff
|
|
|
259
239
|
|
|
260
240
|
# @type [Array<Asana::Resources::Story>]
|
|
261
241
|
stories = task.stories(per_page: 100).to_a.reject do |story|
|
|
262
|
-
# @sg-ignore story's inferred block param type regressed on rbs >= 4.1.0, fix in progress upstream
|
|
263
|
-
# https://github.com/castwide/solargraph/pull/1228
|
|
264
242
|
excluding_resource_subtypes.include? story.resource_subtype
|
|
265
243
|
end
|
|
266
244
|
return true if stories.empty? # no stories == infinitely old!
|
|
@@ -386,8 +364,6 @@ module Checkoff
|
|
|
386
364
|
# @param task [Asana::Resources::Task]
|
|
387
365
|
#
|
|
388
366
|
# @return [Boolean]
|
|
389
|
-
# @sg-ignore Checkoff::SelectorClasses::Task::MilestonePFunctionEvaluator#evaluate
|
|
390
|
-
# return type could not be inferred
|
|
391
367
|
def evaluate(task)
|
|
392
368
|
resource_subtype = task.resource_subtype
|
|
393
369
|
raise 'Please add resource_subtype to extra_fields' if resource_subtype.nil?
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: true
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
module Checkoff
|
|
5
5
|
# Base class to evaluate Asana resource selectors against an Asana resource
|
|
6
6
|
class SelectorEvaluator
|
|
7
|
-
# @param selector [Symbol, Array<Symbol, Integer, Array>]
|
|
7
|
+
# @param selector [Symbol, Array<Symbol, String, Integer, Array>]
|
|
8
8
|
# @return [Boolean, Object, Array, nil]
|
|
9
9
|
def evaluate(selector)
|
|
10
10
|
return true if selector.empty?
|
|
@@ -30,7 +30,6 @@ module Checkoff
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# @return [Array<Class<Checkoff::SelectorClasses::FunctionEvaluator>>]
|
|
33
|
-
# @sg-ignore abstract method always raises; declared type documents the override contract, not this body
|
|
34
33
|
def function_evaluators
|
|
35
34
|
raise 'Implement me!'
|
|
36
35
|
end
|
|
@@ -41,8 +40,10 @@ module Checkoff
|
|
|
41
40
|
def evaluate_args(selector, evaluator)
|
|
42
41
|
return [] unless selector.is_a?(Array)
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
rest = selector[1..]
|
|
44
|
+
return [] unless rest
|
|
45
|
+
|
|
46
|
+
rest.map.with_index do |item, index|
|
|
46
47
|
if evaluator.evaluate_arg?(index)
|
|
47
48
|
evaluate(item)
|
|
48
49
|
else
|
|
@@ -9,7 +9,7 @@ module Checkoff
|
|
|
9
9
|
# @param task [Asana::Resources::Task]
|
|
10
10
|
# @return [Hash]
|
|
11
11
|
def task_to_h(task)
|
|
12
|
-
# @type [Hash]
|
|
12
|
+
# @type [Hash{String => Object}]
|
|
13
13
|
task_hash = task.to_h
|
|
14
14
|
task_hash['unwrapped'] = {}
|
|
15
15
|
unwrap_custom_fields(task_hash)
|
|
@@ -18,7 +18,7 @@ module Checkoff
|
|
|
18
18
|
task_hash
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
# @param task_data [Hash]
|
|
21
|
+
# @param task_data [Hash{String => Object}]
|
|
22
22
|
# @param client [Asana::Client]
|
|
23
23
|
#
|
|
24
24
|
# @return [Asana::Resources::Task]
|
|
@@ -34,7 +34,7 @@ module Checkoff
|
|
|
34
34
|
# @param task_hash [Hash]
|
|
35
35
|
# @return [void]
|
|
36
36
|
def unwrap_custom_fields(task_hash)
|
|
37
|
-
# @type [Array<Hash>,nil]
|
|
37
|
+
# @type [Array<Hash{String => Object}>,nil]
|
|
38
38
|
custom_fields = task_hash.fetch('custom_fields', nil)
|
|
39
39
|
|
|
40
40
|
return if custom_fields.nil?
|
|
@@ -53,8 +53,7 @@ module Checkoff
|
|
|
53
53
|
return unless task_hash.key? 'assignee_section'
|
|
54
54
|
|
|
55
55
|
assignee_section = task_hash.fetch('assignee_section')
|
|
56
|
-
|
|
57
|
-
assignee = T.cast(task_hash.fetch('assignee'), T::Hash[String, String])
|
|
56
|
+
assignee = assignee_hash(task_hash)
|
|
58
57
|
memberships << {
|
|
59
58
|
'section' => assignee_section.dup,
|
|
60
59
|
'project' => {
|
|
@@ -64,24 +63,33 @@ module Checkoff
|
|
|
64
63
|
}
|
|
65
64
|
end
|
|
66
65
|
|
|
67
|
-
# @param task_hash [Hash]
|
|
66
|
+
# @param task_hash [Hash{String => String, Hash, Array}]
|
|
67
|
+
# @return [Hash{String => Object}]
|
|
68
|
+
def assignee_hash(task_hash)
|
|
69
|
+
assignee = task_hash.fetch('assignee')
|
|
70
|
+
raise "Expected assignee to be a Hash, got #{assignee.class}" unless assignee.is_a?(Hash)
|
|
71
|
+
|
|
72
|
+
assignee
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @param task_hash [Hash{String => Object}]
|
|
68
76
|
# @param resource [String]
|
|
69
77
|
# @param memberships [Array<Hash>]
|
|
70
78
|
# @param key [String]
|
|
71
79
|
#
|
|
72
80
|
# @return [void]
|
|
73
81
|
def unwrap_memberships(task_hash, memberships, resource, key)
|
|
74
|
-
# @type [Hash]
|
|
82
|
+
# @type [Hash{String => Object}]
|
|
75
83
|
unwrapped = task_hash.fetch('unwrapped')
|
|
76
84
|
unwrapped["membership_by_#{resource}_#{key}"] = memberships.group_by do |membership|
|
|
77
85
|
membership[resource][key]
|
|
78
86
|
end.transform_values(&:first)
|
|
79
87
|
end
|
|
80
88
|
|
|
81
|
-
# @param task_hash [Hash]
|
|
89
|
+
# @param task_hash [Hash{String => Object}]
|
|
82
90
|
# @return [void]
|
|
83
91
|
def unwrap_all_memberships(task_hash)
|
|
84
|
-
# @type [Array<Hash>]
|
|
92
|
+
# @type [Array<Hash{String => Object}>]
|
|
85
93
|
memberships = task_hash.fetch('memberships', []).dup
|
|
86
94
|
add_user_task_list(task_hash, memberships)
|
|
87
95
|
unwrap_memberships(task_hash, memberships, 'section', 'gid')
|
|
@@ -19,16 +19,12 @@ module Checkoff
|
|
|
19
19
|
|
|
20
20
|
# @param task [Asana::Resources::Task]
|
|
21
21
|
# @return [Time, nil]
|
|
22
|
-
# @sg-ignore return type could not be inferred — #to_time called via &. on a Date|Time
|
|
23
|
-
# union receiver
|
|
24
22
|
def start_time(task)
|
|
25
23
|
date_or_time_field_by_name(task, :start)&.to_time
|
|
26
24
|
end
|
|
27
25
|
|
|
28
26
|
# @param task [Asana::Resources::Task]
|
|
29
27
|
# @return [Time, nil]
|
|
30
|
-
# @sg-ignore return type could not be inferred — #to_time called via &. on a Date|Time
|
|
31
|
-
# union receiver
|
|
32
28
|
def due_time(task)
|
|
33
29
|
date_or_time_field_by_name(task, :due)&.to_time
|
|
34
30
|
end
|
|
@@ -10,8 +10,6 @@ module Checkoff
|
|
|
10
10
|
# @param value [Object,Boolean]
|
|
11
11
|
# @yieldreturn [generic<T>]
|
|
12
12
|
# @return [generic<T>]
|
|
13
|
-
# @sg-ignore return type could not be inferred — Solargraph's generic/yield-return
|
|
14
|
-
# inference gap for #with_thread_local_variable
|
|
15
13
|
def with_thread_local_variable(name, value, &block)
|
|
16
14
|
old_value = Thread.current[name]
|
|
17
15
|
Thread.current[name] = value
|
|
@@ -20,19 +20,19 @@ module Asana
|
|
|
20
20
|
# Public: The base resource class which provides some sugar over common
|
|
21
21
|
# resource functionality.
|
|
22
22
|
class Resource
|
|
23
|
-
# @return [Hash]
|
|
23
|
+
# @return [Hash{String => Object}]
|
|
24
24
|
def marshal_dump
|
|
25
25
|
{ 'data' => @_data,
|
|
26
26
|
'client' => @_client }
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# @param data [Hash]
|
|
29
|
+
# @param data [Hash{String => Object}]
|
|
30
30
|
#
|
|
31
31
|
# @return [void]
|
|
32
32
|
def marshal_load(data)
|
|
33
33
|
# @type [Asana::Client]
|
|
34
34
|
@_client = data.fetch('client')
|
|
35
|
-
# @type [Hash]
|
|
35
|
+
# @type [Hash{String => Object}]
|
|
36
36
|
@_data = data.fetch('data')
|
|
37
37
|
@_data.each do |k, v|
|
|
38
38
|
if respond_to?(k)
|
data/lib/checkoff/my_tasks.rb
CHANGED
|
@@ -67,10 +67,7 @@ module Checkoff
|
|
|
67
67
|
sections.each_entry { |section| by_section[section_key(section.name)] = [] }
|
|
68
68
|
tasks.each do |task|
|
|
69
69
|
assignee_section = task.assignee_section
|
|
70
|
-
|
|
71
|
-
# dereferenced without a nil guard — looks like a real gap, flagging for the
|
|
72
|
-
# follow-up code PR
|
|
73
|
-
current_section = section_key(assignee_section.name)
|
|
70
|
+
current_section = section_key(assignee_section&.name)
|
|
74
71
|
by_section[current_section] ||= []
|
|
75
72
|
by_section[current_section] << task
|
|
76
73
|
end
|
data/lib/checkoff/portfolios.rb
CHANGED
|
@@ -47,7 +47,6 @@ module Checkoff
|
|
|
47
47
|
# @param portfolio_name [String]
|
|
48
48
|
#
|
|
49
49
|
# @return [Asana::Resources::Portfolio]
|
|
50
|
-
# @sg-ignore nil check above is not flow-sensitive
|
|
51
50
|
def portfolio_or_raise(workspace_name, portfolio_name)
|
|
52
51
|
portfolio_obj = portfolio(workspace_name, portfolio_name)
|
|
53
52
|
raise "Could not find portfolio #{portfolio_name} under workspace #{workspace_name}." if portfolio_obj.nil?
|
|
@@ -42,7 +42,7 @@ module Checkoff
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# @param [Asana::Resources::Project] project
|
|
45
|
-
# @param [Array
|
|
45
|
+
# @param [Array<Symbol, String, Array>] project_selector Filter based on
|
|
46
46
|
# project details. Examples: [:tag, 'foo'] [:not, [:tag, 'foo']] [:tag, 'foo']
|
|
47
47
|
# @return [Boolean]
|
|
48
48
|
def filter_via_project_selector(project, project_selector)
|
data/lib/checkoff/projects.rb
CHANGED
|
@@ -22,12 +22,19 @@ require 'asana'
|
|
|
22
22
|
module Checkoff
|
|
23
23
|
# Work with projects in Asana
|
|
24
24
|
class Projects
|
|
25
|
+
# @return [Integer]
|
|
25
26
|
MINUTE = 60
|
|
27
|
+
# @return [Integer]
|
|
26
28
|
HOUR = MINUTE * 60
|
|
29
|
+
# @return [Integer]
|
|
27
30
|
DAY = 24 * HOUR
|
|
31
|
+
# @return [Integer]
|
|
28
32
|
REALLY_LONG_CACHE_TIME = MINUTE * 30
|
|
33
|
+
# @return [Integer]
|
|
29
34
|
LONG_CACHE_TIME = MINUTE * 15
|
|
35
|
+
# @return [Integer]
|
|
30
36
|
MEDIUM_CACHE_TIME = MINUTE * 5
|
|
37
|
+
# @return [Integer]
|
|
31
38
|
SHORT_CACHE_TIME = MINUTE
|
|
32
39
|
|
|
33
40
|
# @!parse
|
|
@@ -65,6 +72,12 @@ module Checkoff
|
|
|
65
72
|
|
|
66
73
|
# Default options used in Asana API to pull tasks
|
|
67
74
|
#
|
|
75
|
+
# Keys are :per_page (Integer), :options (a Hash whose :fields is an
|
|
76
|
+
# Array<String>) and, when only_uncompleted is set, :completed_since
|
|
77
|
+
# (String). The value type is genuinely per-key, which a single YARD
|
|
78
|
+
# Hash value type cannot express.
|
|
79
|
+
# Callers who want the field list should call #task_fields directly.
|
|
80
|
+
#
|
|
68
81
|
# @param extra_fields [Array<String>]
|
|
69
82
|
# @param [Boolean] only_uncompleted
|
|
70
83
|
#
|
|
@@ -109,9 +122,7 @@ module Checkoff
|
|
|
109
122
|
# @type [Enumerable<Asana::Resources::Project>]
|
|
110
123
|
ps = projects_by_workspace_name(workspace_name, extra_fields:)
|
|
111
124
|
# @type [Asana::Resources::Project,nil]
|
|
112
|
-
|
|
113
|
-
# doesn't fully support numbered block parameters
|
|
114
|
-
project = ps.find { _1.name == project_name }
|
|
125
|
+
project = ps.find { |p| p.name == project_name }
|
|
115
126
|
project_by_gid(project.gid, extra_fields:) unless project.nil?
|
|
116
127
|
end
|
|
117
128
|
end
|
|
@@ -122,7 +133,6 @@ module Checkoff
|
|
|
122
133
|
# @param [Array<String>] extra_fields
|
|
123
134
|
#
|
|
124
135
|
# @return [Asana::Resources::Project]
|
|
125
|
-
# @sg-ignore nil check above is not flow-sensitive
|
|
126
136
|
def project_or_raise(workspace_name, project_name, extra_fields: [])
|
|
127
137
|
p = project(workspace_name, project_name, extra_fields:)
|
|
128
138
|
raise "Could not find project #{project_name.inspect} under workspace #{workspace_name}." if p.nil?
|
|
@@ -199,9 +209,10 @@ module Checkoff
|
|
|
199
209
|
cache_method :projects_by_workspace_name, REALLY_LONG_CACHE_TIME
|
|
200
210
|
|
|
201
211
|
# @param project_obj [Asana::Resources::Project]
|
|
202
|
-
# @param project [String,
|
|
212
|
+
# @param project [String, :not_specified, :my_tasks, nil] - a String is a
|
|
213
|
+
# project name
|
|
203
214
|
#
|
|
204
|
-
# @return
|
|
215
|
+
# @return (see Checkoff::Internal::ProjectHashes#project_to_h)
|
|
205
216
|
def project_to_h(project_obj, project: :not_specified)
|
|
206
217
|
project_hashes.project_to_h(project_obj, project:)
|
|
207
218
|
end
|
|
@@ -214,14 +225,14 @@ module Checkoff
|
|
|
214
225
|
# date is today or in the past.
|
|
215
226
|
#
|
|
216
227
|
# @param project [Asana::Resources::Project]
|
|
217
|
-
# @param period [Symbol, Array
|
|
228
|
+
# @param period [Symbol, Array<Symbol, Integer>] See Checkoff::Timing#in_period? - :now_or_before,:this_week
|
|
218
229
|
def project_ready?(project, period: :now_or_before)
|
|
219
230
|
in_period?(project, :ready, period)
|
|
220
231
|
end
|
|
221
232
|
|
|
222
233
|
# @param project [Asana::Resources::Project]
|
|
223
234
|
# @param field_name [Symbol,Array]
|
|
224
|
-
# @param period [Symbol, Array
|
|
235
|
+
# @param period [Symbol, Array<Symbol, Integer>] See Checkoff::Timing#in_period? - :now_or_before,:this_week
|
|
225
236
|
def in_period?(project, field_name, period)
|
|
226
237
|
# @type [Date,Time,nil]
|
|
227
238
|
project_date = project_timing.date_or_time_field_by_name(project, field_name)
|
data/lib/checkoff/resources.rb
CHANGED
|
@@ -62,9 +62,7 @@ module Checkoff
|
|
|
62
62
|
%w[task section project].each do |resource_type_to_try|
|
|
63
63
|
next unless [resource_type_to_try, nil].include?(resource_type)
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
resource = T.cast(method(:"fetch_#{resource_type_to_try}_gid").call(gid),
|
|
67
|
-
T.nilable(Asana::Resources::Resource))
|
|
65
|
+
resource = fetch_gid_by_type(resource_type_to_try, gid)
|
|
68
66
|
return [resource, resource_type_to_try] if resource
|
|
69
67
|
end
|
|
70
68
|
[nil, nil]
|
|
@@ -72,6 +70,19 @@ module Checkoff
|
|
|
72
70
|
|
|
73
71
|
private
|
|
74
72
|
|
|
73
|
+
# @param resource_type [String]
|
|
74
|
+
# @param gid [String]
|
|
75
|
+
#
|
|
76
|
+
# @return [Asana::Resources::Resource, nil]
|
|
77
|
+
def fetch_gid_by_type(resource_type, gid)
|
|
78
|
+
case resource_type
|
|
79
|
+
when 'task' then fetch_task_gid(gid)
|
|
80
|
+
when 'section' then fetch_section_gid(gid)
|
|
81
|
+
when 'project' then fetch_project_gid(gid)
|
|
82
|
+
else raise "Unexpected resource_type: #{resource_type}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
75
86
|
# @param gid [String]
|
|
76
87
|
#
|
|
77
88
|
# @return [Asana::Resources::Task, nil]
|
data/lib/checkoff/sections.rb
CHANGED
|
@@ -14,10 +14,15 @@ module Checkoff
|
|
|
14
14
|
# @!parse
|
|
15
15
|
# extend CacheMethod::ClassMethods
|
|
16
16
|
|
|
17
|
+
# @return [Integer]
|
|
17
18
|
MINUTE = 60
|
|
19
|
+
# @return [Integer]
|
|
18
20
|
HOUR = MINUTE * 60
|
|
21
|
+
# @return [Integer]
|
|
19
22
|
REALLY_LONG_CACHE_TIME = MINUTE * 30
|
|
23
|
+
# @return [Integer]
|
|
20
24
|
LONG_CACHE_TIME = MINUTE * 15
|
|
25
|
+
# @return [Integer]
|
|
21
26
|
SHORT_CACHE_TIME = MINUTE * 5
|
|
22
27
|
|
|
23
28
|
extend Forwardable
|
|
@@ -142,10 +147,9 @@ module Checkoff
|
|
|
142
147
|
# @param section_name [String, nil]
|
|
143
148
|
#
|
|
144
149
|
# @return [Array<String>]
|
|
145
|
-
# @sg-ignore Task#name is nilable; task names are assumed present here
|
|
146
150
|
def section_task_names(workspace_name, project_name, section_name)
|
|
147
151
|
task_array = tasks(workspace_name, project_name, section_name)
|
|
148
|
-
|
|
152
|
+
task_array.filter_map(&:name)
|
|
149
153
|
end
|
|
150
154
|
cache_method :section_task_names, SHORT_CACHE_TIME
|
|
151
155
|
|
|
@@ -155,7 +159,6 @@ module Checkoff
|
|
|
155
159
|
# @param extra_section_fields [Array<String>]
|
|
156
160
|
#
|
|
157
161
|
# @return [Asana::Resources::Section]
|
|
158
|
-
# @sg-ignore section() is nil-checked below
|
|
159
162
|
def section_or_raise(workspace_name, project_name, section_name, extra_section_fields: [])
|
|
160
163
|
s = section(workspace_name, project_name, section_name,
|
|
161
164
|
extra_section_fields:)
|
|
@@ -280,14 +283,12 @@ module Checkoff
|
|
|
280
283
|
# @return [void]
|
|
281
284
|
def file_task_by_section(by_section, task, project_gid)
|
|
282
285
|
membership = task.memberships.find do |m|
|
|
283
|
-
|
|
284
|
-
T.cast(membership_hash['project'], T::Hash[String, T.untyped])['gid'] == project_gid
|
|
286
|
+
m.fetch('project')['gid'] == project_gid
|
|
285
287
|
end
|
|
286
288
|
raise "Could not find task in project_gid #{project_gid}: #{task}" if membership.nil?
|
|
287
289
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
section_name = T.cast(section['name'], String)
|
|
290
|
+
section = membership.fetch('section')
|
|
291
|
+
section_name = section.fetch('name')
|
|
291
292
|
|
|
292
293
|
# @type [String, nil]
|
|
293
294
|
current_section = section_key(section_name)
|
|
@@ -298,7 +299,6 @@ module Checkoff
|
|
|
298
299
|
# @param workspace_name [String, Symbol]
|
|
299
300
|
# @param project_name [String, Symbol]
|
|
300
301
|
# @return [Asana::Resources::Project]
|
|
301
|
-
# @sg-ignore projects.project may be nil but is raised below
|
|
302
302
|
def project_or_raise(workspace_name, project_name)
|
|
303
303
|
raise ArgumentError, 'Provide nil project_name' if T.unsafe(project_name).nil?
|
|
304
304
|
|
data/lib/checkoff/subtasks.rb
CHANGED
|
@@ -72,7 +72,7 @@ module Checkoff
|
|
|
72
72
|
def subtasks_by_gid(task_gid,
|
|
73
73
|
extra_fields: [],
|
|
74
74
|
only_uncompleted: true)
|
|
75
|
-
# @type [Hash]
|
|
75
|
+
# @type [Hash{Symbol => undefined}]
|
|
76
76
|
all_options = projects.task_options(extra_fields: extra_fields + %w[is_rendered_as_separator],
|
|
77
77
|
only_uncompleted:)
|
|
78
78
|
options = all_options.fetch(:options, {})
|
|
@@ -90,7 +90,6 @@ module Checkoff
|
|
|
90
90
|
#
|
|
91
91
|
# @param subtask [Asana::Resources::Task]
|
|
92
92
|
# @return [Boolean, nil]
|
|
93
|
-
# @sg-ignore Checkoff::Subtasks#subtask_section? return type could not be inferred
|
|
94
93
|
def subtask_section?(subtask)
|
|
95
94
|
subtask.is_rendered_as_separator
|
|
96
95
|
end
|
data/lib/checkoff/tags.rb
CHANGED
|
@@ -79,7 +79,6 @@ module Checkoff
|
|
|
79
79
|
# @param tag_name [String]
|
|
80
80
|
#
|
|
81
81
|
# @return [Asana::Resources::Tag]
|
|
82
|
-
# @sg-ignore nil check above is not flow-sensitive
|
|
83
82
|
def tag_or_raise(workspace_name, tag_name)
|
|
84
83
|
t = tag(workspace_name, tag_name)
|
|
85
84
|
|
|
@@ -113,9 +112,9 @@ module Checkoff
|
|
|
113
112
|
#
|
|
114
113
|
# @return [Hash{Symbol => Object}]
|
|
115
114
|
def build_params(options)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
# @type [Hash{Symbol => Object}]
|
|
116
|
+
params = { limit: options[:per_page], completed_since: options[:completed_since] }
|
|
117
|
+
params.reject do |_, v|
|
|
119
118
|
v.nil? || Array(v).empty?
|
|
120
119
|
end
|
|
121
120
|
end
|