checkoff 0.257.0 → 0.260.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 -26
- data/lib/checkoff/internal/asana_event_enrichment.rb +11 -13
- data/lib/checkoff/internal/asana_event_filter.rb +1 -1
- data/lib/checkoff/internal/config_loader.rb +0 -2
- data/lib/checkoff/internal/logging.rb +0 -2
- data/lib/checkoff/internal/project_hashes.rb +5 -4
- data/lib/checkoff/internal/search_url/custom_field_param_converter.rb +9 -9
- data/lib/checkoff/internal/search_url/custom_field_variant.rb +7 -0
- data/lib/checkoff/internal/search_url/date_param_converter.rb +13 -4
- data/lib/checkoff/internal/search_url/parser.rb +2 -7
- data/lib/checkoff/internal/search_url/simple_param_converter.rb +11 -5
- data/lib/checkoff/internal/section_selector_evaluator.rb +1 -1
- data/lib/checkoff/internal/selector_classes/common/function_evaluator.rb +5 -2
- data/lib/checkoff/internal/selector_classes/common.rb +13 -31
- data/lib/checkoff/internal/selector_classes/function_evaluator.rb +20 -4
- data/lib/checkoff/internal/selector_classes/project/function_evaluator.rb +11 -2
- data/lib/checkoff/internal/selector_classes/project.rb +5 -13
- data/lib/checkoff/internal/selector_classes/section/function_evaluator.rb +8 -5
- data/lib/checkoff/internal/selector_classes/section.rb +2 -5
- data/lib/checkoff/internal/selector_classes/task/function_evaluator.rb +2 -2
- data/lib/checkoff/internal/selector_classes/task.rb +24 -23
- data/lib/checkoff/internal/selector_evaluator.rb +8 -9
- 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 +13 -19
- 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 +8 -16
- data/lib/checkoff/timelines.rb +5 -10
- 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 +122 -179
- data/sig/checkoff.rbs +83 -181
- metadata +2 -2
|
@@ -105,12 +105,9 @@ module Checkoff
|
|
|
105
105
|
# @param custom_field_name [String]
|
|
106
106
|
# @return [String, nil]
|
|
107
107
|
def evaluate(resource, custom_field_name)
|
|
108
|
-
|
|
109
|
-
custom_field = @custom_fields.resource_custom_field_by_name(resource, custom_field_name)
|
|
110
|
-
# @sg-ignore
|
|
108
|
+
custom_field = custom_fields.resource_custom_field_by_name(resource, custom_field_name)
|
|
111
109
|
return nil if custom_field.nil?
|
|
112
110
|
|
|
113
|
-
# @sg-ignore
|
|
114
111
|
custom_field['display_value']
|
|
115
112
|
end
|
|
116
113
|
end
|
|
@@ -125,17 +122,13 @@ module Checkoff
|
|
|
125
122
|
false
|
|
126
123
|
end
|
|
127
124
|
|
|
128
|
-
# @sg-ignore
|
|
129
125
|
# @param resource [Asana::Resources::Task,Asana::Resources::Project]
|
|
130
|
-
# @sg-ignore
|
|
131
126
|
# @param custom_field_gid [String]
|
|
132
127
|
# @return [String, nil]
|
|
133
|
-
# @sg-ignore
|
|
134
128
|
def evaluate(resource, custom_field_gid)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
custom_field['display_value']
|
|
129
|
+
custom_field = custom_fields.resource_custom_field_by_gid_or_raise(resource, custom_field_gid)
|
|
130
|
+
display_value = custom_field['display_value']
|
|
131
|
+
display_value.nil? ? nil : T.cast(display_value, String)
|
|
139
132
|
end
|
|
140
133
|
end
|
|
141
134
|
|
|
@@ -152,17 +145,13 @@ module Checkoff
|
|
|
152
145
|
end
|
|
153
146
|
|
|
154
147
|
# @param resource [Asana::Resources::Task,Asana::Resources::Project]
|
|
155
|
-
# @sg-ignore
|
|
156
148
|
# @param custom_field_gid [String]
|
|
157
149
|
# @param custom_field_values_gids [Array<String>]
|
|
158
150
|
# @return [Boolean]
|
|
159
|
-
# @sg-ignore
|
|
160
151
|
def evaluate(resource, custom_field_gid, custom_field_values_gids)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
custom_field_gid)
|
|
152
|
+
actual_custom_field_values_gids = custom_fields.resource_custom_field_values_gids_or_raise(resource,
|
|
153
|
+
custom_field_gid)
|
|
164
154
|
|
|
165
|
-
# @sg-ignore
|
|
166
155
|
actual_custom_field_values_gids.intersect?(custom_field_values_gids)
|
|
167
156
|
end
|
|
168
157
|
end
|
|
@@ -181,17 +170,13 @@ module Checkoff
|
|
|
181
170
|
|
|
182
171
|
# @param resource [Asana::Resources::Task,Asana::Resources::Project]
|
|
183
172
|
# @param custom_field_name [String]
|
|
184
|
-
# @sg-ignore
|
|
185
173
|
# @param custom_field_value_names [Array<String>]
|
|
186
|
-
# @sg-ignore
|
|
187
174
|
# @return [Boolean]
|
|
188
175
|
def evaluate(resource, custom_field_name, custom_field_value_names)
|
|
189
176
|
actual_custom_field_values_names =
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
custom_field_name)
|
|
177
|
+
custom_fields.resource_custom_field_values_names_by_name(resource,
|
|
178
|
+
custom_field_name)
|
|
193
179
|
|
|
194
|
-
# @sg-ignore
|
|
195
180
|
actual_custom_field_values_names.intersect?(custom_field_value_names)
|
|
196
181
|
end
|
|
197
182
|
end
|
|
@@ -214,12 +199,10 @@ module Checkoff
|
|
|
214
199
|
# @return [Boolean]
|
|
215
200
|
def evaluate(resource, custom_field_gid, custom_field_values_gids)
|
|
216
201
|
actual_custom_field_values_gids =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
custom_field_gid)
|
|
202
|
+
custom_fields.resource_custom_field_values_gids_or_raise(resource,
|
|
203
|
+
custom_field_gid)
|
|
220
204
|
|
|
221
205
|
custom_field_values_gids.all? do |custom_field_value|
|
|
222
|
-
# @sg-ignore
|
|
223
206
|
actual_custom_field_values_gids.include?(custom_field_value)
|
|
224
207
|
end
|
|
225
208
|
end
|
|
@@ -240,8 +223,6 @@ module Checkoff
|
|
|
240
223
|
# @param resource [Asana::Resources::Task, Asana::Resources::Project]
|
|
241
224
|
# @param prefix [String]
|
|
242
225
|
# @return [boolish]
|
|
243
|
-
# @sg-ignore Checkoff::SelectorClasses::Common::NameStartsWithPFunctionEvaluator#evaluate
|
|
244
|
-
# return type could not be inferred
|
|
245
226
|
def evaluate(resource, prefix)
|
|
246
227
|
resource.name&.start_with?(prefix)
|
|
247
228
|
end
|
|
@@ -255,9 +236,10 @@ module Checkoff
|
|
|
255
236
|
|
|
256
237
|
# @param _resource [Asana::Resources::Task,Asana::Resources::Project]
|
|
257
238
|
# @return [String]
|
|
258
|
-
# @sg-ignore
|
|
259
239
|
def evaluate(_resource)
|
|
260
|
-
|
|
240
|
+
raise "Expected a String selector, got #{selector.inspect}" unless selector.is_a?(String)
|
|
241
|
+
|
|
242
|
+
selector
|
|
261
243
|
end
|
|
262
244
|
end
|
|
263
245
|
end
|
|
@@ -9,13 +9,23 @@ module Checkoff
|
|
|
9
9
|
class FunctionEvaluator
|
|
10
10
|
include Logging
|
|
11
11
|
|
|
12
|
+
# Concrete subclasses each declare their own initialize with their
|
|
13
|
+
# specific keyword args (and set @selector themselves before calling
|
|
14
|
+
# super()); this signature exists only so Solargraph can resolve
|
|
15
|
+
# `evaluator_class.new(selector:, **kwargs)` call sites that hold a
|
|
16
|
+
# `Class<FunctionEvaluator>` reference rather than a concrete subclass -
|
|
17
|
+
# deliberately a no-op so a bare `super()` call from those subclasses
|
|
18
|
+
# can't clobber the @selector they already set.
|
|
19
|
+
# @param selector [Symbol, Array, String, nil]
|
|
20
|
+
# @param _kwargs [Hash]
|
|
21
|
+
def initialize(selector: nil, **_kwargs); end
|
|
22
|
+
|
|
12
23
|
# @param _index [Integer]
|
|
13
24
|
def evaluate_arg?(_index)
|
|
14
25
|
true
|
|
15
26
|
end
|
|
16
27
|
|
|
17
28
|
# @return [Boolean]
|
|
18
|
-
# @sg-ignore return type could not be inferred — abstract method body is only `raise`
|
|
19
29
|
def matches?
|
|
20
30
|
raise 'Override me!'
|
|
21
31
|
end
|
|
@@ -23,17 +33,23 @@ module Checkoff
|
|
|
23
33
|
# @param _task [Asana::Resources::Task]
|
|
24
34
|
# @param _args [Array<Object>]
|
|
25
35
|
# @return [Object]
|
|
26
|
-
# @sg-ignore return type could not be inferred — abstract method body is only `raise`
|
|
27
36
|
def evaluate(_task, *_args)
|
|
28
37
|
raise 'Implement me!'
|
|
29
38
|
end
|
|
30
39
|
|
|
31
40
|
private
|
|
32
41
|
|
|
33
|
-
# @param object [
|
|
42
|
+
# @param object [Array<Symbol, Array>, Symbol]
|
|
34
43
|
# @param fn_name [Symbol]
|
|
44
|
+
# @return [Boolean]
|
|
35
45
|
def fn?(object, fn_name)
|
|
36
|
-
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
|
|
37
53
|
end
|
|
38
54
|
end
|
|
39
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]
|
|
@@ -25,9 +25,18 @@ module Checkoff
|
|
|
25
25
|
super()
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# @return [Checkoff::Projects]
|
|
29
|
+
attr_reader :projects
|
|
30
|
+
|
|
31
|
+
# @return [Checkoff::Portfolios]
|
|
32
|
+
attr_reader :portfolios
|
|
33
|
+
|
|
34
|
+
# @return [Checkoff::Workspaces]
|
|
35
|
+
attr_reader :workspaces
|
|
36
|
+
|
|
28
37
|
private
|
|
29
38
|
|
|
30
|
-
# @return [Array
|
|
39
|
+
# @return [Array<Symbol, Array>, Symbol]
|
|
31
40
|
attr_reader :selector
|
|
32
41
|
end
|
|
33
42
|
end
|
|
@@ -17,7 +17,6 @@ module Checkoff
|
|
|
17
17
|
|
|
18
18
|
# @param resource [Asana::Resources::Project]
|
|
19
19
|
# @return [String, nil]
|
|
20
|
-
# @sg-ignore
|
|
21
20
|
def evaluate(resource)
|
|
22
21
|
resource.due_date
|
|
23
22
|
end
|
|
@@ -33,11 +32,9 @@ module Checkoff
|
|
|
33
32
|
|
|
34
33
|
# @param project [Asana::Resources::Project]
|
|
35
34
|
# @param period [Symbol] - :now_or_before or :this_week
|
|
36
|
-
# @sg-ignore
|
|
37
35
|
# @return [Boolean]
|
|
38
36
|
def evaluate(project, period = :now_or_before)
|
|
39
|
-
|
|
40
|
-
@projects.project_ready?(project, period:)
|
|
37
|
+
projects.project_ready?(project, period:)
|
|
41
38
|
end
|
|
42
39
|
end
|
|
43
40
|
|
|
@@ -53,19 +50,14 @@ module Checkoff
|
|
|
53
50
|
# @param portfolio_name [String]
|
|
54
51
|
# @param workspace_name [String, nil]
|
|
55
52
|
# @param extra_project_fields [Array<String>]
|
|
56
|
-
# @sg-ignore
|
|
57
53
|
#
|
|
58
|
-
# @sg-ignore
|
|
59
54
|
# @return [Boolean]
|
|
60
55
|
def evaluate(project, portfolio_name, workspace_name: nil, extra_project_fields: [])
|
|
61
56
|
workspace_name ||= project.workspace&.name
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
extra_project_fields:)
|
|
67
|
-
# @sg-ignore
|
|
68
|
-
projects.any? { |p| p.name == project.name }
|
|
57
|
+
workspace_name ||= workspaces.default_workspace.name
|
|
58
|
+
matching_projects = portfolios.projects_in_portfolio(T.must(workspace_name), portfolio_name,
|
|
59
|
+
extra_project_fields:)
|
|
60
|
+
matching_projects.any? { |p| p.name == project.name }
|
|
69
61
|
end
|
|
70
62
|
end
|
|
71
63
|
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]
|
|
@@ -22,13 +22,16 @@ module Checkoff
|
|
|
22
22
|
super()
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# @return [Array(Symbol, Array)]
|
|
28
|
-
attr_reader :selector
|
|
25
|
+
# @return [Checkoff::Sections]
|
|
26
|
+
attr_reader :sections
|
|
29
27
|
|
|
30
28
|
# @return [Asana::Client]
|
|
31
29
|
attr_reader :client
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# @return [Array<Symbol, Array>, Symbol]
|
|
34
|
+
attr_reader :selector
|
|
32
35
|
end
|
|
33
36
|
end
|
|
34
37
|
end
|
|
@@ -18,12 +18,11 @@ module Checkoff
|
|
|
18
18
|
# @param section [Asana::Resources::Section]
|
|
19
19
|
#
|
|
20
20
|
# @return [Boolean]
|
|
21
|
-
# @sg-ignore
|
|
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
|
|
|
@@ -37,11 +36,9 @@ module Checkoff
|
|
|
37
36
|
|
|
38
37
|
# @param section [Asana::Resources::Section]
|
|
39
38
|
#
|
|
40
|
-
# @sg-ignore
|
|
41
39
|
# @return [Boolean]
|
|
42
40
|
def evaluate(section)
|
|
43
|
-
|
|
44
|
-
@sections.tasks_by_section_gid(section.gid).any?
|
|
41
|
+
sections.tasks_by_section_gid(section.gid).any?
|
|
45
42
|
end
|
|
46
43
|
end
|
|
47
44
|
end
|
|
@@ -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
|
|
@@ -45,10 +45,14 @@ module Checkoff
|
|
|
45
45
|
# @param task [Asana::Resources::Task]
|
|
46
46
|
# @param section_name_prefix [String]
|
|
47
47
|
# @return [Boolean]
|
|
48
|
-
# @sg-ignore
|
|
49
48
|
def evaluate(task, section_name_prefix)
|
|
49
|
+
# @type [Hash{'unwrapped' => Hash}]
|
|
50
50
|
task_data = tasks.task_to_h(task)
|
|
51
|
-
|
|
51
|
+
# @type [Hash{'membership_by_section_name' => Hash}]
|
|
52
|
+
unwrapped = task_data.fetch('unwrapped')
|
|
53
|
+
# @type [Array]
|
|
54
|
+
section_names = unwrapped.fetch('membership_by_section_name').keys
|
|
55
|
+
section_names.any? do |section_name|
|
|
52
56
|
String(section_name).start_with?(section_name_prefix)
|
|
53
57
|
end
|
|
54
58
|
end
|
|
@@ -65,13 +69,17 @@ module Checkoff
|
|
|
65
69
|
false
|
|
66
70
|
end
|
|
67
71
|
|
|
68
|
-
# @sg-ignore
|
|
69
72
|
# @param task [Asana::Resources::Task]
|
|
70
73
|
# @param section_name [String]
|
|
71
74
|
# @return [Boolean]
|
|
72
75
|
def evaluate(task, section_name)
|
|
76
|
+
# @type [Hash{'unwrapped' => Hash}]
|
|
73
77
|
task_data = tasks.task_to_h(task)
|
|
74
|
-
|
|
78
|
+
# @type [Hash{'membership_by_section_name' => Hash}]
|
|
79
|
+
unwrapped = task_data.fetch('unwrapped')
|
|
80
|
+
# @type [Array]
|
|
81
|
+
section_names = unwrapped.fetch('membership_by_section_name').keys
|
|
82
|
+
section_names.any?(section_name)
|
|
75
83
|
end
|
|
76
84
|
end
|
|
77
85
|
|
|
@@ -89,13 +97,11 @@ module Checkoff
|
|
|
89
97
|
# @param task [Asana::Resources::Task]
|
|
90
98
|
# @param project_name [String]
|
|
91
99
|
# @return [Boolean]
|
|
92
|
-
# @sg-ignore
|
|
93
100
|
def evaluate(task, project_name)
|
|
94
101
|
project_names = task.memberships.map do |membership|
|
|
95
|
-
|
|
96
|
-
T.cast(m.fetch('project'), T::Hash[String, T.untyped]).fetch('name')
|
|
102
|
+
membership.fetch('project').fetch('name')
|
|
97
103
|
end
|
|
98
|
-
|
|
104
|
+
project_names.include?(project_name)
|
|
99
105
|
end
|
|
100
106
|
end
|
|
101
107
|
|
|
@@ -132,9 +138,11 @@ module Checkoff
|
|
|
132
138
|
# @param task [Asana::Resources::Task]
|
|
133
139
|
# @param tag_name [String]
|
|
134
140
|
# @return [Boolean]
|
|
135
|
-
# @sg-ignore
|
|
136
141
|
def evaluate(task, tag_name)
|
|
137
|
-
|
|
142
|
+
# @type [Array<Asana::Resources::Tag>]
|
|
143
|
+
tags = task.tags.to_a
|
|
144
|
+
tag_names = tags.map(&:name)
|
|
145
|
+
tag_names.include?(tag_name)
|
|
138
146
|
end
|
|
139
147
|
end
|
|
140
148
|
|
|
@@ -190,10 +198,8 @@ module Checkoff
|
|
|
190
198
|
|
|
191
199
|
# @param task [Asana::Resources::Task]
|
|
192
200
|
# @return [Boolean]
|
|
193
|
-
# @sg-ignore Checkoff::SelectorClasses::Task::UnassignedPFunctionEvaluator#evaluate
|
|
194
|
-
# return type could not be inferred
|
|
195
201
|
def evaluate(task)
|
|
196
|
-
|
|
202
|
+
task.assignee.nil?
|
|
197
203
|
end
|
|
198
204
|
end
|
|
199
205
|
|
|
@@ -207,10 +213,8 @@ module Checkoff
|
|
|
207
213
|
|
|
208
214
|
# @param task [Asana::Resources::Task]
|
|
209
215
|
# @return [Boolean]
|
|
210
|
-
# @sg-ignore Checkoff::SelectorClasses::Task::DueDateSetPFunctionEvaluator#evaluate
|
|
211
|
-
# return type could not be inferred
|
|
212
216
|
def evaluate(task)
|
|
213
|
-
|
|
217
|
+
!(task.due_at.nil? && task.due_on.nil?)
|
|
214
218
|
end
|
|
215
219
|
end
|
|
216
220
|
|
|
@@ -235,13 +239,11 @@ module Checkoff
|
|
|
235
239
|
|
|
236
240
|
# @type [Array<Asana::Resources::Story>]
|
|
237
241
|
stories = task.stories(per_page: 100).to_a.reject do |story|
|
|
238
|
-
# @sg-ignore
|
|
239
242
|
excluding_resource_subtypes.include? story.resource_subtype
|
|
240
243
|
end
|
|
241
244
|
return true if stories.empty? # no stories == infinitely old!
|
|
242
245
|
|
|
243
|
-
last_story = stories.last
|
|
244
|
-
# @sg-ignore
|
|
246
|
+
last_story = T.must(stories.last)
|
|
245
247
|
last_story_created_at = Time.parse(last_story.created_at)
|
|
246
248
|
n_days_ago = Time.at(Time.now.to_i - (num_days * 86_400))
|
|
247
249
|
last_story_created_at < n_days_ago
|
|
@@ -362,12 +364,11 @@ module Checkoff
|
|
|
362
364
|
# @param task [Asana::Resources::Task]
|
|
363
365
|
#
|
|
364
366
|
# @return [Boolean]
|
|
365
|
-
# @sg-ignore Checkoff::SelectorClasses::Task::MilestonePFunctionEvaluator#evaluate
|
|
366
|
-
# return type could not be inferred
|
|
367
367
|
def evaluate(task)
|
|
368
|
-
|
|
368
|
+
resource_subtype = task.resource_subtype
|
|
369
|
+
raise 'Please add resource_subtype to extra_fields' if resource_subtype.nil?
|
|
369
370
|
|
|
370
|
-
|
|
371
|
+
resource_subtype == 'milestone'
|
|
371
372
|
end
|
|
372
373
|
end
|
|
373
374
|
|
|
@@ -1,23 +1,21 @@
|
|
|
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?
|
|
11
11
|
|
|
12
12
|
function_evaluators.each do |evaluator_class|
|
|
13
13
|
# @type [Checkoff::SelectorClasses::FunctionEvaluator]
|
|
14
|
-
# @sg-ignore
|
|
15
14
|
evaluator = evaluator_class.new(selector:,
|
|
16
15
|
**initializer_kwargs)
|
|
17
16
|
|
|
18
17
|
next unless evaluator.matches?
|
|
19
18
|
|
|
20
|
-
# @sg-ignore
|
|
21
19
|
return try_this_evaluator(selector, evaluator)
|
|
22
20
|
end
|
|
23
21
|
|
|
@@ -32,19 +30,20 @@ module Checkoff
|
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
# @return [Array<Class<Checkoff::SelectorClasses::FunctionEvaluator>>]
|
|
35
|
-
# @sg-ignore
|
|
36
33
|
def function_evaluators
|
|
37
34
|
raise 'Implement me!'
|
|
38
35
|
end
|
|
39
36
|
|
|
40
|
-
# @param selector [Array]
|
|
37
|
+
# @param selector [Symbol, Array]
|
|
41
38
|
# @param evaluator [Checkoff::SelectorClasses::FunctionEvaluator]
|
|
42
39
|
# @return [Array]
|
|
43
40
|
def evaluate_args(selector, evaluator)
|
|
44
41
|
return [] unless selector.is_a?(Array)
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
rest = selector[1..]
|
|
44
|
+
return [] unless rest
|
|
45
|
+
|
|
46
|
+
rest.map.with_index do |item, index|
|
|
48
47
|
if evaluator.evaluate_arg?(index)
|
|
49
48
|
evaluate(item)
|
|
50
49
|
else
|
|
@@ -53,7 +52,7 @@ module Checkoff
|
|
|
53
52
|
end
|
|
54
53
|
end
|
|
55
54
|
|
|
56
|
-
# @param selector [Array]
|
|
55
|
+
# @param selector [Symbol, Array]
|
|
57
56
|
# @param evaluator [Checkoff::SelectorClasses::FunctionEvaluator]
|
|
58
57
|
# @return [Boolean, Object, nil]
|
|
59
58
|
def try_this_evaluator(selector, evaluator)
|
|
@@ -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)
|