checkoff 0.58.1 → 0.60.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2b8e1f32a572bfcd4797199e3e8bf40a8bacfe3f20eb117a13f52e5f69feab5
4
- data.tar.gz: fe61f004dcc81088a23acee2f2fe03c8cec9fc107d1850cc6450d6bde99bdbdc
3
+ metadata.gz: 560f8d3dadb4f15e7b3329091bd82e301b23dd479f558e1a637a04aa8bc2067b
4
+ data.tar.gz: bdfbe8d442602eaa15bb0c995677d88ad4897b8b8f068f04e087ca28ed02d255
5
5
  SHA512:
6
- metadata.gz: 2e77e9c93328415564d60bc1b3b1ca29bb678ce425477137392acd26d92acd81823a9fa9e6db189ce2a22045ca6dc45df32a8548a9265889e95f86196d238b1e
7
- data.tar.gz: 516063b25c108b17ac6039c3890cf375a623078b913a33fecb019ae4c9a4f76e2a1f3995fae4cb3c529d5dddcb2d9076f5ec07e940a464ed13330c5b63ab3fbd
6
+ metadata.gz: 5044cbdbe5d31947c795bdf3ca9d90ab71614059779c1a3ac3a3c19430a227ca89a1ee9ff661b3c523eef9cc9b019843aae41c4240d321b91725efc7dacc1731
7
+ data.tar.gz: ab7bf654fdd30941dfe00ad78351b6b5aa36275b36103b491954f953cf10a643a16d6895cb8c091db52b3586ea3fbe7c12f0be94beecd2498a83242a07d781ab
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: .
14
14
  specs:
15
- checkoff (0.58.1)
15
+ checkoff (0.60.0)
16
16
  activesupport
17
17
  asana (> 0.10.0)
18
18
  cache_method
@@ -22,17 +22,24 @@ PATH
22
22
  GEM
23
23
  remote: https://rubygems.org/
24
24
  specs:
25
- activesupport (7.0.8)
25
+ activesupport (7.1.0)
26
+ base64
27
+ bigdecimal
26
28
  concurrent-ruby (~> 1.0, >= 1.0.2)
29
+ connection_pool (>= 2.2.5)
30
+ drb
27
31
  i18n (>= 1.6, < 2)
28
32
  minitest (>= 5.1)
33
+ mutex_m
29
34
  tzinfo (~> 2.0)
30
35
  addressable (2.8.1)
31
36
  public_suffix (>= 2.0.2, < 6.0)
32
37
  ansi (1.5.0)
33
38
  ast (2.4.2)
34
39
  backport (1.2.0)
40
+ base64 (0.1.1)
35
41
  benchmark (0.2.1)
42
+ bigdecimal (3.1.4)
36
43
  builder (3.2.4)
37
44
  bump (0.10.0)
38
45
  cache (0.4.1)
@@ -43,11 +50,14 @@ GEM
43
50
  childprocess (4.1.0)
44
51
  coderay (1.1.3)
45
52
  concurrent-ruby (1.2.2)
53
+ connection_pool (2.4.1)
46
54
  crack (0.4.5)
47
55
  rexml
48
56
  dalli (3.2.6)
49
57
  diff-lcs (1.5.0)
50
58
  docile (1.4.0)
59
+ drb (2.1.1)
60
+ ruby2_keywords
51
61
  e2mmap (0.1.0)
52
62
  fakeweb (1.3.0)
53
63
  faraday (1.5.1)
@@ -110,6 +120,7 @@ GEM
110
120
  multi_json (1.15.0)
111
121
  multi_xml (0.6.0)
112
122
  multipart-post (2.1.1)
123
+ mutex_m (0.1.2)
113
124
  nokogiri (1.15.2)
114
125
  mini_portile2 (~> 2.8.2)
115
126
  racc (~> 1.4)
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'selector_classes/common'
4
+ require_relative 'selector_classes/project'
5
+ require_relative 'selector_evaluator'
6
+
7
+ module Checkoff
8
+ # Evaluates project selectors against a project
9
+ class ProjectSelectorEvaluator < SelectorEvaluator
10
+ # @param project [Asana::Resources::Project]
11
+ # @param projects [Checkoff::Projects]
12
+ def initialize(project:,
13
+ projects: Checkoff::Projects.new)
14
+ @item = project
15
+ @projects = projects
16
+ super()
17
+ end
18
+
19
+ private
20
+
21
+ COMMON_FUNCTION_EVALUATORS = (Checkoff::SelectorClasses::Common.constants.map do |const|
22
+ Checkoff::SelectorClasses::Common.const_get(const)
23
+ end - [Checkoff::SelectorClasses::Common::FunctionEvaluator]).freeze
24
+
25
+ PROJECT_FUNCTION_EVALUATORS = (Checkoff::SelectorClasses::Project.constants.map do |const|
26
+ Checkoff::SelectorClasses::Project.const_get(const)
27
+ end - [Checkoff::SelectorClasses::Project::FunctionEvaluator]).freeze
28
+
29
+ FUNCTION_EVALUTORS = (COMMON_FUNCTION_EVALUATORS + PROJECT_FUNCTION_EVALUATORS).freeze
30
+
31
+ # @return [Array<Class<ProjectSelectorClasses::FunctionEvaluator>>]
32
+ def function_evaluators
33
+ FUNCTION_EVALUTORS
34
+ end
35
+
36
+ # @return [Hash]
37
+ def initializer_kwargs
38
+ { projects: projects }
39
+ end
40
+
41
+ # @return [Asana::Resources::Project]
42
+ attr_reader :item
43
+ # @return [Checkoff::Projects]
44
+ attr_reader :projects
45
+ end
46
+ end
@@ -70,6 +70,14 @@ module Checkoff
70
70
  end
71
71
  end
72
72
 
73
+ # Handle 'portfolios.ids' search url param
74
+ class PortfoliosIds < SimpleParam
75
+ # @return [Array<String>]
76
+ def convert
77
+ { 'portfolios.any' => single_value.split('~').join(',') }.to_a.flatten
78
+ end
79
+ end
80
+
73
81
  # Handle 'any_projects.ids' search url param
74
82
  class AnyProjectsIds < SimpleParam
75
83
  # @return [Array<String>]
@@ -188,6 +196,7 @@ module Checkoff
188
196
 
189
197
  # @type [Hash<String, Class<SimpleParam::SimpleParam>>] the mapping from param name to class
190
198
  ARGS = {
199
+ 'portfolios.ids' => SimpleParam::PortfoliosIds,
191
200
  'any_projects.ids' => SimpleParam::AnyProjectsIds,
192
201
  'not_projects.ids' => SimpleParam::NotProjectsIds,
193
202
  'completion' => SimpleParam::Completion,
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../function_evaluator'
4
+
5
+ module Checkoff
6
+ module SelectorClasses
7
+ module Common
8
+ # Base class to evaluate a project selector function given fully evaluated arguments
9
+ class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
10
+ # @param selector [Array<(Symbol, Array)>,String]
11
+ def initialize(selector:, **_kwargs)
12
+ @selector = selector
13
+ super()
14
+ end
15
+
16
+ private
17
+
18
+ # @param project [Asana::Resources::Project]
19
+ # @param field_name [Symbol]
20
+ #
21
+ # @sg-ignore
22
+ # @return [Date, nil]
23
+ def pull_date_field_by_name(project, field_name)
24
+ if field_name == :modified
25
+ return Time.parse(project.modified_at).to_date unless project.modified_at.nil?
26
+
27
+ return nil
28
+ end
29
+
30
+ if field_name == :due
31
+ return Time.parse(project.due_at).to_date unless project.due_at.nil?
32
+
33
+ return Date.parse(project.due_on) unless project.due_on.nil?
34
+
35
+ return nil
36
+ end
37
+
38
+ raise "Teach me how to handle field #{field_name}"
39
+ end
40
+
41
+ # @param project [Asana::Resources::Project]
42
+ # @param field_name [Symbol]
43
+ #
44
+ # @sg-ignore
45
+ # @return [Date, Time, nil]
46
+ def pull_date_or_time_field_by_name(project, field_name)
47
+ if field_name == :due
48
+ return Time.parse(project.due_at) unless project.due_at.nil?
49
+
50
+ return Date.parse(project.due_on) unless project.due_on.nil?
51
+
52
+ return nil
53
+ end
54
+
55
+ if field_name == :start
56
+ return Time.parse(project.start_at) unless project.start_at.nil?
57
+
58
+ return Date.parse(project.start_on) unless project.start_on.nil?
59
+
60
+ return nil
61
+ end
62
+
63
+ raise "Teach me how to handle field #{field_name}"
64
+ end
65
+
66
+ # @sg-ignore
67
+ # @param project [Asana::Resources::Project]
68
+ # @param custom_field_gid [String]
69
+ # @return [Hash]
70
+ def pull_custom_field_or_raise(project, custom_field_gid)
71
+ # @type [Array<Hash>]
72
+ custom_fields = project.custom_fields
73
+ if custom_fields.nil?
74
+ raise "Could not find custom_fields under project (was 'custom_fields' included in 'extra_fields'?)"
75
+ end
76
+
77
+ # @sg-ignore
78
+ # @type [Hash, nil]
79
+ matched_custom_field = custom_fields.find { |data| data.fetch('gid') == custom_field_gid }
80
+ if matched_custom_field.nil?
81
+ raise "Could not find custom field with gid #{custom_field_gid} " \
82
+ "in project #{project.gid} with custom fields #{custom_fields}"
83
+ end
84
+
85
+ matched_custom_field
86
+ end
87
+
88
+ # @return [Array<(Symbol, Array)>]
89
+ attr_reader :selector
90
+
91
+ # @sg-ignore
92
+ # @param custom_field [Hash]
93
+ # @return [Array<String>]
94
+ def pull_enum_values(custom_field)
95
+ resource_subtype = custom_field.fetch('resource_subtype')
96
+ case resource_subtype
97
+ when 'enum'
98
+ [custom_field.fetch('enum_value')]
99
+ when 'multi_enum'
100
+ custom_field.fetch('multi_enum_values')
101
+ else
102
+ raise "Teach me how to handle resource_subtype #{resource_subtype}"
103
+ end
104
+ end
105
+
106
+ # @param custom_field [Hash]
107
+ # @param enum_value [Object, nil]
108
+ # @return [Array<String>]
109
+ def find_gids(custom_field, enum_value)
110
+ if enum_value.nil?
111
+ []
112
+ else
113
+ raise "Unexpected enabled value on custom field: #{custom_field}" if enum_value.fetch('enabled') == false
114
+
115
+ [enum_value.fetch('gid')]
116
+ end
117
+ end
118
+
119
+ # @param project [Asana::Resources::Project]
120
+ # @param custom_field_gid [String]
121
+ # @return [Array<String>]
122
+ def pull_custom_field_values_gids(project, custom_field_gid)
123
+ custom_field = pull_custom_field_or_raise(project, custom_field_gid)
124
+ pull_enum_values(custom_field).flat_map do |enum_value|
125
+ find_gids(custom_field, enum_value)
126
+ end
127
+ end
128
+
129
+ # @sg-ignore
130
+ # @param project [Asana::Resources::Project]
131
+ # @param custom_field_name [String]
132
+ # @return [Hash, nil]
133
+ def pull_custom_field_by_name(project, custom_field_name)
134
+ custom_fields = project.custom_fields
135
+ if custom_fields.nil?
136
+ raise "custom fields not found on project - did you add 'custom_fields' in your extra_fields argument?"
137
+ end
138
+
139
+ # @sg-ignore
140
+ # @type [Hash, nil]
141
+ custom_fields.find { |field| field.fetch('name') == custom_field_name }
142
+ end
143
+
144
+ # @param project [Asana::Resources::Project]
145
+ # @param custom_field_name [String]
146
+ # @return [Hash]
147
+ def pull_custom_field_by_name_or_raise(project, custom_field_name)
148
+ custom_field = pull_custom_field_by_name(project, custom_field_name)
149
+ if custom_field.nil?
150
+ raise "Could not find custom field with name #{custom_field_name} " \
151
+ "in project #{project.gid} with custom fields #{project.custom_fields}"
152
+ end
153
+ custom_field
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common/function_evaluator'
4
+
5
+ module Checkoff
6
+ module SelectorClasses
7
+ module Common
8
+ # :and function
9
+ class AndFunctionEvaluator < FunctionEvaluator
10
+ FUNCTION_NAME = :and
11
+
12
+ def matches?
13
+ fn?(selector, FUNCTION_NAME)
14
+ end
15
+
16
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
17
+ # @param lhs [Object]
18
+ # @param rhs [Object]
19
+ # @return [Object]
20
+ def evaluate(_resource, lhs, rhs)
21
+ lhs && rhs
22
+ end
23
+ end
24
+
25
+ # :or function
26
+ #
27
+ # Does not yet shortcut, but may in future - be careful with side
28
+ # effects!
29
+ class OrFunctionEvaluator < FunctionEvaluator
30
+ FUNCTION_NAME = :or
31
+
32
+ def matches?
33
+ fn?(selector, FUNCTION_NAME)
34
+ end
35
+
36
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
37
+ # @param lhs [Object]
38
+ # @param rhs [Object]
39
+ # @return [Object]
40
+ def evaluate(_resource, lhs, rhs)
41
+ lhs || rhs
42
+ end
43
+ end
44
+
45
+ # :not function
46
+ class NotFunctionEvaluator < FunctionEvaluator
47
+ FUNCTION_NAME = :not
48
+
49
+ def matches?
50
+ fn?(selector, FUNCTION_NAME)
51
+ end
52
+
53
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
54
+ # @param subvalue [Object]
55
+ # @return [Boolean]
56
+ def evaluate(_resource, subvalue)
57
+ !subvalue
58
+ end
59
+ end
60
+
61
+ # :nil? function
62
+ class NilPFunctionEvaluator < FunctionEvaluator
63
+ def matches?
64
+ fn?(selector, :nil?)
65
+ end
66
+
67
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
68
+ # @param subvalue [Object]
69
+ # @return [Boolean]
70
+ def evaluate(_resource, subvalue)
71
+ subvalue.nil?
72
+ end
73
+ end
74
+
75
+ # :equals? function
76
+ class EqualsPFunctionEvaluator < FunctionEvaluator
77
+ FUNCTION_NAME = :equals?
78
+
79
+ def matches?
80
+ fn?(selector, FUNCTION_NAME)
81
+ end
82
+
83
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
84
+ # @param lhs [Object]
85
+ # @param rhs [Object]
86
+ # @return [Boolean]
87
+ def evaluate(_resource, lhs, rhs)
88
+ lhs == rhs
89
+ end
90
+ end
91
+
92
+ # :custom_field_value function
93
+ class CustomFieldValueFunctionEvaluator < FunctionEvaluator
94
+ FUNCTION_NAME = :custom_field_value
95
+
96
+ def matches?
97
+ fn?(selector, FUNCTION_NAME)
98
+ end
99
+
100
+ # @param _index [Integer]
101
+ def evaluate_arg?(_index)
102
+ false
103
+ end
104
+
105
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
106
+ # @param custom_field_name [String]
107
+ # @return [String, nil]
108
+ def evaluate(resource, custom_field_name)
109
+ custom_field = pull_custom_field_by_name(resource, custom_field_name)
110
+ return nil if custom_field.nil?
111
+
112
+ custom_field['display_value']
113
+ end
114
+ end
115
+
116
+ # :custom_field_gid_value function
117
+ class CustomFieldGidValueFunctionEvaluator < FunctionEvaluator
118
+ def matches?
119
+ fn?(selector, :custom_field_gid_value)
120
+ end
121
+
122
+ def evaluate_arg?(_index)
123
+ false
124
+ end
125
+
126
+ # @sg-ignore
127
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
128
+ # @param custom_field_gid [String]
129
+ # @return [String, nil]
130
+ def evaluate(resource, custom_field_gid)
131
+ custom_field = pull_custom_field_or_raise(resource, custom_field_gid)
132
+ custom_field['display_value']
133
+ end
134
+ end
135
+
136
+ # :custom_field_gid_value_contains_any_gid function
137
+ class CustomFieldGidValueContainsAnyGidFunctionEvaluator < FunctionEvaluator
138
+ FUNCTION_NAME = :custom_field_gid_value_contains_any_gid
139
+
140
+ def matches?
141
+ fn?(selector, FUNCTION_NAME)
142
+ end
143
+
144
+ def evaluate_arg?(_index)
145
+ false
146
+ end
147
+
148
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
149
+ # @param custom_field_gid [String]
150
+ # @param custom_field_values_gids [Array<String>]
151
+ # @return [Boolean]
152
+ def evaluate(resource, custom_field_gid, custom_field_values_gids)
153
+ actual_custom_field_values_gids = pull_custom_field_values_gids(resource, custom_field_gid)
154
+
155
+ actual_custom_field_values_gids.any? do |custom_field_value|
156
+ custom_field_values_gids.include?(custom_field_value)
157
+ end
158
+ end
159
+ end
160
+
161
+ # :custom_field_value_contains_any_value
162
+ class CustomFieldValueContainsAnyValueFunctionEvaluator < FunctionEvaluator
163
+ FUNCTION_NAME = :custom_field_value_contains_any_value
164
+
165
+ def matches?
166
+ fn?(selector, FUNCTION_NAME)
167
+ end
168
+
169
+ def evaluate_arg?(_index)
170
+ false
171
+ end
172
+
173
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
174
+ # @param custom_field_name [String]
175
+ # @param custom_field_values [Array<String>]
176
+ # @return [Boolean]
177
+ def evaluate(resource, custom_field_name, custom_field_values)
178
+ custom_field = pull_custom_field_by_name(resource, custom_field_name)
179
+
180
+ return false if custom_field.nil?
181
+
182
+ custom_field_values.include?(custom_field.fetch('display_value'))
183
+ end
184
+ end
185
+
186
+ # :custom_field_gid_value_contains_all_gids function
187
+ class CustomFieldGidValueContainsAllGidsFunctionEvaluator < FunctionEvaluator
188
+ FUNCTION_NAME = :custom_field_gid_value_contains_all_gids
189
+
190
+ def matches?
191
+ fn?(selector, FUNCTION_NAME)
192
+ end
193
+
194
+ def evaluate_arg?(_index)
195
+ false
196
+ end
197
+
198
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
199
+ # @param custom_field_gid [String]
200
+ # @param custom_field_values_gids [Array<String>]
201
+ # @return [Boolean]
202
+ def evaluate(resource, custom_field_gid, custom_field_values_gids)
203
+ actual_custom_field_values_gids = pull_custom_field_values_gids(resource, custom_field_gid)
204
+
205
+ custom_field_values_gids.all? do |custom_field_value|
206
+ actual_custom_field_values_gids.include?(custom_field_value)
207
+ end
208
+ end
209
+ end
210
+
211
+ # String literals
212
+ class StringLiteralEvaluator < FunctionEvaluator
213
+ def matches?
214
+ selector.is_a?(String)
215
+ end
216
+
217
+ # @sg-ignore
218
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
219
+ # @return [String]
220
+ def evaluate(_resource)
221
+ selector
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkoff
4
+ module SelectorClasses
5
+ # Base class to evaluate types of selector functions
6
+ class FunctionEvaluator
7
+ # @sg-ignore
8
+ # @param _index [Integer]
9
+ def evaluate_arg?(_index)
10
+ true
11
+ end
12
+
13
+ # @sg-ignore
14
+ # @return [Boolean]
15
+ def matches?
16
+ raise 'Override me!'
17
+ end
18
+
19
+ # @param _task [Asana::Resources::Task]
20
+ # @param _args [Array<Object>]
21
+ # @return [Object]
22
+ # @sg-ignore
23
+ def evaluate(_task, *_args)
24
+ raise 'Implement 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
+ end
35
+ end
36
+ end