checkoff 0.58.1 → 0.59.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/checkoff/internal/project_selector_evaluator.rb +40 -0
- data/lib/checkoff/internal/selector_classes/common/function_evaluator.rb +158 -0
- data/lib/checkoff/internal/selector_classes/common.rb +226 -0
- data/lib/checkoff/internal/selector_classes/function_evaluator.rb +36 -0
- data/lib/checkoff/internal/selector_classes/project/function_evaluator.rb +161 -0
- data/lib/checkoff/internal/selector_classes/project.rb +10 -0
- data/lib/checkoff/internal/selector_classes/task/function_evaluator.rb +161 -0
- data/lib/checkoff/internal/selector_classes/task.rb +310 -0
- data/lib/checkoff/internal/selector_evaluator.rb +66 -0
- data/lib/checkoff/internal/task_selector_evaluator.rb +38 -729
- data/lib/checkoff/project_selectors.rb +9 -15
- data/lib/checkoff/tasks.rb +3 -0
- data/lib/checkoff/version.rb +1 -1
- metadata +11 -2
@@ -1,751 +1,60 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
class FunctionEvaluator
|
7
|
-
# @param task_selector [Array<(Symbol, Array)>,String]
|
8
|
-
# @param tasks [Checkoff::Tasks]
|
9
|
-
def initialize(task_selector:,
|
10
|
-
tasks:)
|
11
|
-
@task_selector = task_selector
|
12
|
-
@tasks = tasks
|
13
|
-
end
|
14
|
-
|
15
|
-
# @sg-ignore
|
16
|
-
# @param _index [Integer]
|
17
|
-
def evaluate_arg?(_index)
|
18
|
-
true
|
19
|
-
end
|
20
|
-
|
21
|
-
# @sg-ignore
|
22
|
-
# @return [Boolean]
|
23
|
-
def matches?
|
24
|
-
raise 'Override me!'
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
# @param object [Object]
|
30
|
-
# @param fn_name [Symbol]
|
31
|
-
def fn?(object, fn_name)
|
32
|
-
object.is_a?(Array) && !object.empty? && [fn_name, fn_name.to_s].include?(object[0])
|
33
|
-
end
|
34
|
-
|
35
|
-
# @param task [Asana::Resources::Task]
|
36
|
-
# @param field_name [Symbol]
|
37
|
-
#
|
38
|
-
# @sg-ignore
|
39
|
-
# @return [Date, nil]
|
40
|
-
def pull_date_field_by_name(task, field_name)
|
41
|
-
if field_name == :modified
|
42
|
-
return Time.parse(task.modified_at).to_date unless task.modified_at.nil?
|
43
|
-
|
44
|
-
return nil
|
45
|
-
end
|
46
|
-
|
47
|
-
if field_name == :due
|
48
|
-
return Time.parse(task.due_at).to_date unless task.due_at.nil?
|
49
|
-
|
50
|
-
return Date.parse(task.due_on) unless task.due_on.nil?
|
51
|
-
|
52
|
-
return nil
|
53
|
-
end
|
54
|
-
|
55
|
-
raise "Teach me how to handle field #{field_name}"
|
56
|
-
end
|
57
|
-
|
58
|
-
# @param task [Asana::Resources::Task]
|
59
|
-
# @param field_name [Symbol]
|
60
|
-
#
|
61
|
-
# @sg-ignore
|
62
|
-
# @return [Date, Time, nil]
|
63
|
-
def pull_date_or_time_field_by_name(task, field_name)
|
64
|
-
if field_name == :due
|
65
|
-
return Time.parse(task.due_at) unless task.due_at.nil?
|
66
|
-
|
67
|
-
return Date.parse(task.due_on) unless task.due_on.nil?
|
68
|
-
|
69
|
-
return nil
|
70
|
-
end
|
71
|
-
|
72
|
-
if field_name == :start
|
73
|
-
return Time.parse(task.start_at) unless task.start_at.nil?
|
74
|
-
|
75
|
-
return Date.parse(task.start_on) unless task.start_on.nil?
|
76
|
-
|
77
|
-
return nil
|
78
|
-
end
|
79
|
-
|
80
|
-
raise "Teach me how to handle field #{field_name}"
|
81
|
-
end
|
82
|
-
|
83
|
-
# @sg-ignore
|
84
|
-
# @param task [Asana::Resources::Task]
|
85
|
-
# @param custom_field_gid [String]
|
86
|
-
# @return [Hash]
|
87
|
-
def pull_custom_field_or_raise(task, custom_field_gid)
|
88
|
-
# @type [Array<Hash>]
|
89
|
-
custom_fields = task.custom_fields
|
90
|
-
if custom_fields.nil?
|
91
|
-
raise "Could not find custom_fields under task (was 'custom_fields' included in 'extra_fields'?)"
|
92
|
-
end
|
93
|
-
|
94
|
-
# @sg-ignore
|
95
|
-
# @type [Hash, nil]
|
96
|
-
matched_custom_field = custom_fields.find { |data| data.fetch('gid') == custom_field_gid }
|
97
|
-
if matched_custom_field.nil?
|
98
|
-
raise "Could not find custom field with gid #{custom_field_gid} " \
|
99
|
-
"in task #{task.gid} with custom fields #{custom_fields}"
|
100
|
-
end
|
101
|
-
|
102
|
-
matched_custom_field
|
103
|
-
end
|
104
|
-
|
105
|
-
# @return [Array<(Symbol, Array)>]
|
106
|
-
attr_reader :task_selector
|
107
|
-
|
108
|
-
# @sg-ignore
|
109
|
-
# @param custom_field [Hash]
|
110
|
-
# @return [Array<String>]
|
111
|
-
def pull_enum_values(custom_field)
|
112
|
-
resource_subtype = custom_field.fetch('resource_subtype')
|
113
|
-
case resource_subtype
|
114
|
-
when 'enum'
|
115
|
-
[custom_field.fetch('enum_value')]
|
116
|
-
when 'multi_enum'
|
117
|
-
custom_field.fetch('multi_enum_values')
|
118
|
-
else
|
119
|
-
raise "Teach me how to handle resource_subtype #{resource_subtype}"
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
# @param custom_field [Hash]
|
124
|
-
# @param enum_value [Object, nil]
|
125
|
-
# @return [Array<String>]
|
126
|
-
def find_gids(custom_field, enum_value)
|
127
|
-
if enum_value.nil?
|
128
|
-
[]
|
129
|
-
else
|
130
|
-
raise "Unexpected enabled value on custom field: #{custom_field}" if enum_value.fetch('enabled') == false
|
131
|
-
|
132
|
-
[enum_value.fetch('gid')]
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# @param task [Asana::Resources::Task]
|
137
|
-
# @param custom_field_gid [String]
|
138
|
-
# @return [Array<String>]
|
139
|
-
def pull_custom_field_values_gids(task, custom_field_gid)
|
140
|
-
custom_field = pull_custom_field_or_raise(task, custom_field_gid)
|
141
|
-
pull_enum_values(custom_field).flat_map do |enum_value|
|
142
|
-
find_gids(custom_field, enum_value)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
# @sg-ignore
|
147
|
-
# @param task [Asana::Resources::Task]
|
148
|
-
# @param custom_field_name [String]
|
149
|
-
# @return [Hash, nil]
|
150
|
-
def pull_custom_field_by_name(task, custom_field_name)
|
151
|
-
custom_fields = task.custom_fields
|
152
|
-
if custom_fields.nil?
|
153
|
-
raise "custom fields not found on task - did you add 'custom_fields' in your extra_fields argument?"
|
154
|
-
end
|
155
|
-
|
156
|
-
# @sg-ignore
|
157
|
-
# @type [Hash, nil]
|
158
|
-
custom_fields.find { |field| field.fetch('name') == custom_field_name }
|
159
|
-
end
|
160
|
-
|
161
|
-
# @param task [Asana::Resources::Task]
|
162
|
-
# @param custom_field_name [String]
|
163
|
-
# @return [Hash]
|
164
|
-
def pull_custom_field_by_name_or_raise(task, custom_field_name)
|
165
|
-
custom_field = pull_custom_field_by_name(task, custom_field_name)
|
166
|
-
if custom_field.nil?
|
167
|
-
raise "Could not find custom field with name #{custom_field_name} " \
|
168
|
-
"in task #{task.gid} with custom fields #{task.custom_fields}"
|
169
|
-
end
|
170
|
-
custom_field
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
# :and function
|
175
|
-
class AndFunctionEvaluator < FunctionEvaluator
|
176
|
-
FUNCTION_NAME = :and
|
177
|
-
|
178
|
-
def matches?
|
179
|
-
fn?(task_selector, FUNCTION_NAME)
|
180
|
-
end
|
181
|
-
|
182
|
-
# @param _task [Asana::Resources::Task]
|
183
|
-
# @param lhs [Object]
|
184
|
-
# @param rhs [Object]
|
185
|
-
# @return [Object]
|
186
|
-
def evaluate(_task, lhs, rhs)
|
187
|
-
lhs && rhs
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# :or function
|
192
|
-
#
|
193
|
-
# Does not yet shortcut, but may in future - be careful with side
|
194
|
-
# effects!
|
195
|
-
class OrFunctionEvaluator < FunctionEvaluator
|
196
|
-
FUNCTION_NAME = :or
|
197
|
-
|
198
|
-
def matches?
|
199
|
-
fn?(task_selector, FUNCTION_NAME)
|
200
|
-
end
|
201
|
-
|
202
|
-
# @param _task [Asana::Resources::Task]
|
203
|
-
# @param lhs [Object]
|
204
|
-
# @param rhs [Object]
|
205
|
-
# @return [Object]
|
206
|
-
def evaluate(_task, lhs, rhs)
|
207
|
-
lhs || rhs
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
# :not function
|
212
|
-
class NotFunctionEvaluator < FunctionEvaluator
|
213
|
-
FUNCTION_NAME = :not
|
214
|
-
|
215
|
-
def matches?
|
216
|
-
fn?(task_selector, FUNCTION_NAME)
|
217
|
-
end
|
218
|
-
|
219
|
-
# @param _task [Asana::Resources::Task]
|
220
|
-
# @param subvalue [Object]
|
221
|
-
# @return [Boolean]
|
222
|
-
def evaluate(_task, subvalue)
|
223
|
-
!subvalue
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
# :nil? function
|
228
|
-
class NilPFunctionEvaluator < FunctionEvaluator
|
229
|
-
def matches?
|
230
|
-
fn?(task_selector, :nil?)
|
231
|
-
end
|
232
|
-
|
233
|
-
# @param _task [Asana::Resources::Task]
|
234
|
-
# @param subvalue [Object]
|
235
|
-
# @return [Boolean]
|
236
|
-
def evaluate(_task, subvalue)
|
237
|
-
subvalue.nil?
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
# :equals? function
|
242
|
-
class EqualsPFunctionEvaluator < FunctionEvaluator
|
243
|
-
FUNCTION_NAME = :equals?
|
244
|
-
|
245
|
-
def matches?
|
246
|
-
fn?(task_selector, FUNCTION_NAME)
|
247
|
-
end
|
248
|
-
|
249
|
-
# @param _task [Asana::Resources::Task]
|
250
|
-
# @param lhs [Object]
|
251
|
-
# @param rhs [Object]
|
252
|
-
# @return [Boolean]
|
253
|
-
def evaluate(_task, lhs, rhs)
|
254
|
-
lhs == rhs
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
# :tag function
|
259
|
-
class TagPFunctionEvaluator < FunctionEvaluator
|
260
|
-
def matches?
|
261
|
-
fn?(task_selector, :tag)
|
262
|
-
end
|
263
|
-
|
264
|
-
# @param _index [Integer]
|
265
|
-
def evaluate_arg?(_index)
|
266
|
-
false
|
267
|
-
end
|
268
|
-
|
269
|
-
# @sg-ignore
|
270
|
-
# @param task [Asana::Resources::Task]
|
271
|
-
# @param tag_name [String]
|
272
|
-
# @return [Boolean]
|
273
|
-
def evaluate(task, tag_name)
|
274
|
-
task.tags.map(&:name).include? tag_name
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
# :due function
|
279
|
-
class DuePFunctionEvaluator < FunctionEvaluator
|
280
|
-
def matches?
|
281
|
-
fn?(task_selector, :due)
|
282
|
-
end
|
283
|
-
|
284
|
-
# @param task [Asana::Resources::Task]
|
285
|
-
# @param ignore_dependencies [Boolean]
|
286
|
-
# @return [Boolean]
|
287
|
-
def evaluate(task, ignore_dependencies: false)
|
288
|
-
@tasks.task_ready?(task, ignore_dependencies: ignore_dependencies)
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
# :unassigned function
|
293
|
-
class UnassignedPFunctionEvaluator < FunctionEvaluator
|
294
|
-
def matches?
|
295
|
-
fn?(task_selector, :unassigned)
|
296
|
-
end
|
297
|
-
|
298
|
-
# @param task [Asana::Resources::Task]
|
299
|
-
# @return [Boolean]
|
300
|
-
def evaluate(task)
|
301
|
-
task.assignee.nil?
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
# :due_date_set function
|
306
|
-
class DueDateSetPFunctionEvaluator < FunctionEvaluator
|
307
|
-
FUNCTION_NAME = :due_date_set
|
308
|
-
|
309
|
-
def matches?
|
310
|
-
fn?(task_selector, FUNCTION_NAME)
|
311
|
-
end
|
312
|
-
|
313
|
-
# @sg-ignore
|
314
|
-
# @param task [Asana::Resources::Task]
|
315
|
-
# @return [Boolean]
|
316
|
-
def evaluate(task)
|
317
|
-
!task.due_at.nil? || !task.due_on.nil?
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
# :due_between_n_days function
|
322
|
-
class DueBetweenRelativePFunctionEvaluator < FunctionEvaluator
|
323
|
-
FUNCTION_NAME = :due_between_relative
|
324
|
-
|
325
|
-
def matches?
|
326
|
-
fn?(task_selector, FUNCTION_NAME)
|
327
|
-
end
|
328
|
-
|
329
|
-
# @param _index [Integer]
|
330
|
-
def evaluate_arg?(_index)
|
331
|
-
false
|
332
|
-
end
|
333
|
-
|
334
|
-
# @param task [Asana::Resources::Task]
|
335
|
-
# @param beginning_num_days_from_now [Integer]
|
336
|
-
# @param end_num_days_from_now [Integer]
|
337
|
-
# @param ignore_dependencies [Boolean]
|
338
|
-
#
|
339
|
-
# @return [Boolean]
|
340
|
-
def evaluate(task, beginning_num_days_from_now, end_num_days_from_now, ignore_dependencies: false)
|
341
|
-
beginning_n_days_from_now_time = (Time.now + (beginning_num_days_from_now * 24 * 60 * 60))
|
342
|
-
end_n_days_from_now_time = (Time.now + (end_num_days_from_now * 24 * 60 * 60))
|
343
|
-
|
344
|
-
# @type [Date, Time, nil]
|
345
|
-
task_date_or_time = pull_date_or_time_field_by_name(task, :start) ||
|
346
|
-
pull_date_or_time_field_by_name(task, :due)
|
347
|
-
|
348
|
-
return false if task_date_or_time.nil?
|
349
|
-
|
350
|
-
# if time
|
351
|
-
in_range = if task_date_or_time.is_a?(Time)
|
352
|
-
task_date_or_time > beginning_n_days_from_now_time &&
|
353
|
-
task_date_or_time <= end_n_days_from_now_time
|
354
|
-
else
|
355
|
-
# if date
|
356
|
-
task_date_or_time > beginning_n_days_from_now_time.to_date &&
|
357
|
-
task_date_or_time <= end_n_days_from_now_time.to_date
|
358
|
-
end
|
359
|
-
|
360
|
-
return false unless in_range
|
361
|
-
|
362
|
-
return false if !ignore_dependencies && @tasks.incomplete_dependencies?(task)
|
363
|
-
|
364
|
-
true
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
# :custom_field_value function
|
369
|
-
class CustomFieldValueFunctionEvaluator < FunctionEvaluator
|
370
|
-
FUNCTION_NAME = :custom_field_value
|
371
|
-
|
372
|
-
def matches?
|
373
|
-
fn?(task_selector, FUNCTION_NAME)
|
374
|
-
end
|
375
|
-
|
376
|
-
# @param _index [Integer]
|
377
|
-
def evaluate_arg?(_index)
|
378
|
-
false
|
379
|
-
end
|
380
|
-
|
381
|
-
# @param task [Asana::Resources::Task]
|
382
|
-
# @param custom_field_name [String]
|
383
|
-
# @return [String, nil]
|
384
|
-
def evaluate(task, custom_field_name)
|
385
|
-
custom_field = pull_custom_field_by_name(task, custom_field_name)
|
386
|
-
return nil if custom_field.nil?
|
3
|
+
require_relative 'selector_classes/common'
|
4
|
+
require_relative 'selector_classes/task'
|
5
|
+
require_relative 'selector_evaluator'
|
387
6
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
# :custom_field_gid_value function
|
393
|
-
class CustomFieldGidValueFunctionEvaluator < FunctionEvaluator
|
394
|
-
def matches?
|
395
|
-
fn?(task_selector, :custom_field_gid_value)
|
396
|
-
end
|
397
|
-
|
398
|
-
def evaluate_arg?(_index)
|
399
|
-
false
|
400
|
-
end
|
401
|
-
|
402
|
-
# @sg-ignore
|
403
|
-
# @param task [Asana::Resources::Task]
|
404
|
-
# @param custom_field_gid [String]
|
405
|
-
# @return [String, nil]
|
406
|
-
def evaluate(task, custom_field_gid)
|
407
|
-
custom_field = pull_custom_field_or_raise(task, custom_field_gid)
|
408
|
-
custom_field['display_value']
|
409
|
-
end
|
410
|
-
end
|
411
|
-
|
412
|
-
# :custom_field_gid_value_contains_any_gid function
|
413
|
-
class CustomFieldGidValueContainsAnyGidFunctionEvaluator < FunctionEvaluator
|
414
|
-
FUNCTION_NAME = :custom_field_gid_value_contains_any_gid
|
415
|
-
|
416
|
-
def matches?
|
417
|
-
fn?(task_selector, FUNCTION_NAME)
|
418
|
-
end
|
419
|
-
|
420
|
-
def evaluate_arg?(_index)
|
421
|
-
false
|
422
|
-
end
|
423
|
-
|
424
|
-
# @param task [Asana::Resources::Task]
|
425
|
-
# @param custom_field_gid [String]
|
426
|
-
# @param custom_field_values_gids [Array<String>]
|
427
|
-
# @return [Boolean]
|
428
|
-
def evaluate(task, custom_field_gid, custom_field_values_gids)
|
429
|
-
actual_custom_field_values_gids = pull_custom_field_values_gids(task, custom_field_gid)
|
430
|
-
|
431
|
-
actual_custom_field_values_gids.any? do |custom_field_value|
|
432
|
-
custom_field_values_gids.include?(custom_field_value)
|
433
|
-
end
|
434
|
-
end
|
435
|
-
end
|
436
|
-
|
437
|
-
# :custom_field_gid_value_contains_all_gids function
|
438
|
-
class CustomFieldGidValueContainsAllGidsFunctionEvaluator < FunctionEvaluator
|
439
|
-
FUNCTION_NAME = :custom_field_gid_value_contains_all_gids
|
440
|
-
|
441
|
-
def matches?
|
442
|
-
fn?(task_selector, FUNCTION_NAME)
|
443
|
-
end
|
444
|
-
|
445
|
-
def evaluate_arg?(_index)
|
446
|
-
false
|
447
|
-
end
|
448
|
-
|
449
|
-
# @param task [Asana::Resources::Task]
|
450
|
-
# @param custom_field_gid [String]
|
451
|
-
# @param custom_field_values_gids [Array<String>]
|
452
|
-
# @return [Boolean]
|
453
|
-
def evaluate(task, custom_field_gid, custom_field_values_gids)
|
454
|
-
actual_custom_field_values_gids = pull_custom_field_values_gids(task, custom_field_gid)
|
455
|
-
|
456
|
-
custom_field_values_gids.all? do |custom_field_value|
|
457
|
-
actual_custom_field_values_gids.include?(custom_field_value)
|
458
|
-
end
|
459
|
-
end
|
460
|
-
end
|
461
|
-
|
462
|
-
# :field_less_than_n_days_ago
|
463
|
-
class FieldLessThanNDaysAgoPFunctionEvaluator < FunctionEvaluator
|
464
|
-
FUNCTION_NAME = :field_less_than_n_days_ago
|
465
|
-
|
466
|
-
def matches?
|
467
|
-
fn?(task_selector, FUNCTION_NAME)
|
468
|
-
end
|
469
|
-
|
470
|
-
def evaluate_arg?(_index)
|
471
|
-
false
|
472
|
-
end
|
473
|
-
|
474
|
-
# @param task [Asana::Resources::Task]
|
475
|
-
# @param field_name [Symbol]
|
476
|
-
# @param num_days [Integer]
|
477
|
-
#
|
478
|
-
# @return [Boolean]
|
479
|
-
def evaluate(task, field_name, num_days)
|
480
|
-
date = pull_date_field_by_name(task, field_name)
|
481
|
-
|
482
|
-
return false if date.nil?
|
483
|
-
|
484
|
-
# @sg-ignore
|
485
|
-
n_days_ago = Date.today - num_days
|
486
|
-
# @sg-ignore
|
487
|
-
date < n_days_ago
|
488
|
-
end
|
489
|
-
end
|
490
|
-
|
491
|
-
# :field_greater_than_or_equal_to_n_days_from_today
|
492
|
-
class FieldGreaterThanOrEqualToNDaysFromTodayPFunctionEvaluator < FunctionEvaluator
|
493
|
-
FUNCTION_NAME = :field_greater_than_or_equal_to_n_days_from_today
|
494
|
-
|
495
|
-
def matches?
|
496
|
-
fn?(task_selector, FUNCTION_NAME)
|
497
|
-
end
|
498
|
-
|
499
|
-
def evaluate_arg?(_index)
|
500
|
-
false
|
501
|
-
end
|
502
|
-
|
503
|
-
# @param task [Asana::Resources::Task]
|
504
|
-
# @param field_name [Symbol]
|
505
|
-
# @param num_days [Integer]
|
506
|
-
#
|
507
|
-
# @return [Boolean]
|
508
|
-
def evaluate(task, field_name, num_days)
|
509
|
-
date = pull_date_field_by_name(task, field_name)
|
510
|
-
|
511
|
-
return false if date.nil?
|
512
|
-
|
513
|
-
# @sg-ignore
|
514
|
-
n_days_from_today = Date.today + num_days
|
515
|
-
# @sg-ignore
|
516
|
-
date >= n_days_from_today
|
517
|
-
end
|
518
|
-
end
|
519
|
-
|
520
|
-
# :custom_field_less_than_n_days_from_now function
|
521
|
-
class CustomFieldLessThanNDaysFromNowFunctionEvaluator < FunctionEvaluator
|
522
|
-
FUNCTION_NAME = :custom_field_less_than_n_days_from_now
|
523
|
-
|
524
|
-
def matches?
|
525
|
-
fn?(task_selector, FUNCTION_NAME)
|
526
|
-
end
|
527
|
-
|
528
|
-
def evaluate_arg?(_index)
|
529
|
-
false
|
530
|
-
end
|
531
|
-
|
532
|
-
# @param task [Asana::Resources::Task]
|
533
|
-
# @param custom_field_name [String]
|
534
|
-
# @param num_days [Integer]
|
535
|
-
# @return [Boolean]
|
536
|
-
def evaluate(task, custom_field_name, num_days)
|
537
|
-
custom_field = pull_custom_field_by_name_or_raise(task, custom_field_name)
|
538
|
-
|
539
|
-
# @sg-ignore
|
540
|
-
# @type [String, nil]
|
541
|
-
time_str = custom_field.fetch('display_value')
|
542
|
-
return false if time_str.nil?
|
543
|
-
|
544
|
-
time = Time.parse(time_str)
|
545
|
-
n_days_from_now = (Time.now + (num_days * 24 * 60 * 60))
|
546
|
-
time < n_days_from_now
|
547
|
-
end
|
548
|
-
end
|
549
|
-
|
550
|
-
# :custom_field_greater_than_or_equal_to_n_days_from_now function
|
551
|
-
class CustomFieldGreaterThanOrEqualToNDaysFromNowFunctionEvaluator < FunctionEvaluator
|
552
|
-
FUNCTION_NAME = :custom_field_greater_than_or_equal_to_n_days_from_now
|
553
|
-
|
554
|
-
def matches?
|
555
|
-
fn?(task_selector, FUNCTION_NAME)
|
556
|
-
end
|
557
|
-
|
558
|
-
def evaluate_arg?(_index)
|
559
|
-
false
|
560
|
-
end
|
561
|
-
|
562
|
-
# @param task [Asana::Resources::Task]
|
563
|
-
# @param custom_field_name [String]
|
564
|
-
# @param num_days [Integer]
|
565
|
-
# @return [Boolean]
|
566
|
-
def evaluate(task, custom_field_name, num_days)
|
567
|
-
custom_field = pull_custom_field_by_name_or_raise(task, custom_field_name)
|
568
|
-
|
569
|
-
# @sg-ignore
|
570
|
-
# @type [String, nil]
|
571
|
-
time_str = custom_field.fetch('display_value')
|
572
|
-
return false if time_str.nil?
|
573
|
-
|
574
|
-
time = Time.parse(time_str)
|
575
|
-
n_days_from_now = (Time.now + (num_days * 24 * 60 * 60))
|
576
|
-
time >= n_days_from_now
|
577
|
-
end
|
578
|
-
end
|
579
|
-
|
580
|
-
# :last_story_created_less_than_n_days_ago function
|
581
|
-
class LastStoryCreatedLessThanNDaysAgoFunctionEvaluator < FunctionEvaluator
|
582
|
-
FUNCTION_NAME = :last_story_created_less_than_n_days_ago
|
583
|
-
|
584
|
-
def matches?
|
585
|
-
fn?(task_selector, FUNCTION_NAME)
|
586
|
-
end
|
587
|
-
|
588
|
-
def evaluate_arg?(_index)
|
589
|
-
false
|
590
|
-
end
|
591
|
-
|
592
|
-
# @param task [Asana::Resources::Task]
|
593
|
-
# @param num_days [Integer]
|
594
|
-
# @param excluding_resource_subtypes [Array<String>]
|
595
|
-
# @return [Boolean]
|
596
|
-
def evaluate(task, num_days, excluding_resource_subtypes)
|
597
|
-
# for whatever reason, .last on the enumerable does not impose ordering; .to_a does!
|
598
|
-
|
599
|
-
# @type [Array<Asana::Resources::Story>]
|
600
|
-
stories = task.stories(per_page: 100).to_a.reject do |story|
|
601
|
-
excluding_resource_subtypes.include? story.resource_subtype
|
602
|
-
end
|
603
|
-
return true if stories.empty? # no stories == infinitely old!
|
604
|
-
|
605
|
-
last_story = stories.last
|
606
|
-
last_story_created_at = Time.parse(last_story.created_at)
|
607
|
-
n_days_ago = Time.now - (num_days * 24 * 60 * 60)
|
608
|
-
last_story_created_at < n_days_ago
|
609
|
-
end
|
610
|
-
end
|
611
|
-
|
612
|
-
# String literals
|
613
|
-
class StringLiteralEvaluator < FunctionEvaluator
|
614
|
-
def matches?
|
615
|
-
task_selector.is_a?(String)
|
616
|
-
end
|
617
|
-
|
618
|
-
# @sg-ignore
|
619
|
-
# @param _task [Asana::Resources::Task]
|
620
|
-
# @return [String]
|
621
|
-
def evaluate(_task)
|
622
|
-
task_selector
|
623
|
-
end
|
624
|
-
end
|
625
|
-
|
626
|
-
# :estimate_exceeds_duration
|
627
|
-
class EstimateExceedsDurationFunctionEvaluator < FunctionEvaluator
|
628
|
-
FUNCTION_NAME = :estimate_exceeds_duration
|
629
|
-
|
630
|
-
def matches?
|
631
|
-
fn?(task_selector, FUNCTION_NAME)
|
632
|
-
end
|
633
|
-
|
634
|
-
# @param task [Asana::Resources::Task]
|
635
|
-
# @return [Float]
|
636
|
-
def calculate_allocated_hours(task)
|
637
|
-
due_on = nil
|
638
|
-
start_on = nil
|
639
|
-
start_on = Date.parse(task.start_on) unless task.start_on.nil?
|
640
|
-
due_on = Date.parse(task.due_on) unless task.due_on.nil?
|
641
|
-
allocated_hours = 8.0
|
642
|
-
# @sg-ignore
|
643
|
-
allocated_hours = (due_on - start_on + 1).to_i * 8.0 if start_on && due_on
|
644
|
-
allocated_hours
|
645
|
-
end
|
646
|
-
|
647
|
-
# @param task [Asana::Resources::Task]
|
648
|
-
# @return [Boolean]
|
649
|
-
def evaluate(task)
|
650
|
-
custom_field = pull_custom_field_by_name_or_raise(task, 'Estimated time')
|
651
|
-
|
652
|
-
# @sg-ignore
|
653
|
-
# @type [Integer, nil]
|
654
|
-
estimate_minutes = custom_field.fetch('number_value')
|
655
|
-
|
656
|
-
# no estimate set
|
657
|
-
return false if estimate_minutes.nil?
|
658
|
-
|
659
|
-
estimate_hours = estimate_minutes / 60.0
|
660
|
-
|
661
|
-
allocated_hours = calculate_allocated_hours(task)
|
662
|
-
|
663
|
-
estimate_hours > allocated_hours
|
664
|
-
end
|
665
|
-
end
|
666
|
-
end
|
667
|
-
|
668
|
-
# Evaluator task selectors against a task
|
669
|
-
class TaskSelectorEvaluator
|
7
|
+
module Checkoff
|
8
|
+
# Evaluates task selectors against a task
|
9
|
+
class TaskSelectorEvaluator < SelectorEvaluator
|
670
10
|
# @param task [Asana::Resources::Task]
|
671
11
|
# @param tasks [Checkoff::Tasks]
|
672
12
|
def initialize(task:,
|
673
13
|
tasks: Checkoff::Tasks.new)
|
674
|
-
@
|
14
|
+
@item = task
|
675
15
|
@tasks = tasks
|
16
|
+
super()
|
676
17
|
end
|
677
18
|
|
19
|
+
private
|
20
|
+
|
678
21
|
FUNCTION_EVALUTORS = [
|
679
|
-
Checkoff::
|
680
|
-
Checkoff::
|
681
|
-
Checkoff::
|
682
|
-
Checkoff::
|
683
|
-
Checkoff::
|
684
|
-
Checkoff::
|
685
|
-
Checkoff::
|
686
|
-
Checkoff::
|
687
|
-
Checkoff::
|
688
|
-
Checkoff::
|
689
|
-
Checkoff::
|
690
|
-
Checkoff::
|
691
|
-
Checkoff::
|
692
|
-
Checkoff::
|
693
|
-
Checkoff::
|
694
|
-
Checkoff::
|
695
|
-
Checkoff::
|
696
|
-
Checkoff::
|
697
|
-
Checkoff::
|
698
|
-
Checkoff::
|
699
|
-
Checkoff::
|
22
|
+
Checkoff::SelectorClasses::Common::NotFunctionEvaluator,
|
23
|
+
Checkoff::SelectorClasses::Common::NilPFunctionEvaluator,
|
24
|
+
Checkoff::SelectorClasses::Common::EqualsPFunctionEvaluator,
|
25
|
+
Checkoff::SelectorClasses::Task::TagPFunctionEvaluator,
|
26
|
+
Checkoff::SelectorClasses::Common::CustomFieldValueFunctionEvaluator,
|
27
|
+
Checkoff::SelectorClasses::Common::CustomFieldGidValueFunctionEvaluator,
|
28
|
+
Checkoff::SelectorClasses::Common::CustomFieldGidValueContainsAnyGidFunctionEvaluator,
|
29
|
+
Checkoff::SelectorClasses::Common::CustomFieldGidValueContainsAllGidsFunctionEvaluator,
|
30
|
+
Checkoff::SelectorClasses::Common::AndFunctionEvaluator,
|
31
|
+
Checkoff::SelectorClasses::Common::OrFunctionEvaluator,
|
32
|
+
Checkoff::SelectorClasses::Task::DuePFunctionEvaluator,
|
33
|
+
Checkoff::SelectorClasses::Task::DueBetweenRelativePFunctionEvaluator,
|
34
|
+
Checkoff::SelectorClasses::Task::UnassignedPFunctionEvaluator,
|
35
|
+
Checkoff::SelectorClasses::Task::DueDateSetPFunctionEvaluator,
|
36
|
+
Checkoff::SelectorClasses::Task::FieldLessThanNDaysAgoPFunctionEvaluator,
|
37
|
+
Checkoff::SelectorClasses::Task::FieldGreaterThanOrEqualToNDaysFromTodayPFunctionEvaluator,
|
38
|
+
Checkoff::SelectorClasses::Task::CustomFieldLessThanNDaysFromNowFunctionEvaluator,
|
39
|
+
Checkoff::SelectorClasses::Task::CustomFieldGreaterThanOrEqualToNDaysFromNowFunctionEvaluator,
|
40
|
+
Checkoff::SelectorClasses::Task::LastStoryCreatedLessThanNDaysAgoFunctionEvaluator,
|
41
|
+
Checkoff::SelectorClasses::Common::StringLiteralEvaluator,
|
42
|
+
Checkoff::SelectorClasses::Task::EstimateExceedsDurationFunctionEvaluator,
|
700
43
|
].freeze
|
701
44
|
|
702
|
-
# @
|
703
|
-
|
704
|
-
|
705
|
-
return true if task_selector.empty?
|
706
|
-
|
707
|
-
# @param evaluator_class [Class<TaskSelectorClasses::FunctionEvaluator>]
|
708
|
-
FUNCTION_EVALUTORS.each do |evaluator_class|
|
709
|
-
# @sg-ignore
|
710
|
-
evaluator = evaluator_class.new(task_selector: task_selector,
|
711
|
-
tasks: tasks)
|
712
|
-
|
713
|
-
next unless evaluator.matches?
|
714
|
-
|
715
|
-
return try_this_evaluator(task_selector, evaluator)
|
716
|
-
end
|
717
|
-
|
718
|
-
raise "Syntax issue trying to handle #{task_selector.inspect}"
|
45
|
+
# @return [Array<Class<TaskSelectorClasses::FunctionEvaluator>>]
|
46
|
+
def function_evaluators
|
47
|
+
FUNCTION_EVALUTORS
|
719
48
|
end
|
720
49
|
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
# @param task_selector [Array]
|
725
|
-
# @param evaluator [TaskSelectorClasses::FunctionEvaluator]
|
726
|
-
# @return [Boolean, Object, nil]
|
727
|
-
def try_this_evaluator(task_selector, evaluator)
|
728
|
-
# if task_selector is an array
|
729
|
-
evaluated_args = if task_selector.is_a?(Array)
|
730
|
-
task_selector[1..].map.with_index do |item, index|
|
731
|
-
if evaluator.evaluate_arg?(index)
|
732
|
-
evaluate(item)
|
733
|
-
else
|
734
|
-
item
|
735
|
-
end
|
736
|
-
end
|
737
|
-
else
|
738
|
-
[]
|
739
|
-
end
|
740
|
-
|
741
|
-
evaluator.evaluate(task, *evaluated_args)
|
50
|
+
# @return [Hash]
|
51
|
+
def initializer_kwargs
|
52
|
+
{ tasks: tasks }
|
742
53
|
end
|
743
54
|
|
744
55
|
# @return [Asana::Resources::Task]
|
745
|
-
attr_reader :
|
56
|
+
attr_reader :item
|
746
57
|
# @return [Checkoff::Tasks]
|
747
58
|
attr_reader :tasks
|
748
|
-
# @return [Array<(Symbol, Array)>]
|
749
|
-
attr_reader :task_selector
|
750
59
|
end
|
751
60
|
end
|