checkoff 0.256.0 → 0.257.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 +12 -11
- data/lib/checkoff/cli.rb +1 -4
- data/lib/checkoff/clients.rb +2 -2
- data/lib/checkoff/custom_fields.rb +9 -5
- data/lib/checkoff/internal/asana_event_enrichment.rb +9 -11
- data/lib/checkoff/internal/config_loader.rb +2 -2
- data/lib/checkoff/internal/logging.rb +2 -2
- data/lib/checkoff/internal/search_url/parser.rb +7 -5
- data/lib/checkoff/internal/search_url/results_merger.rb +1 -2
- data/lib/checkoff/internal/selector_classes/common.rb +2 -1
- data/lib/checkoff/internal/selector_classes/function_evaluator.rb +2 -2
- data/lib/checkoff/internal/selector_classes/task.rb +2 -1
- data/lib/checkoff/internal/task_timing.rb +6 -5
- data/lib/checkoff/internal/thread_local.rb +2 -1
- data/lib/checkoff/my_tasks.rb +3 -1
- data/lib/checkoff/portfolios.rb +1 -1
- data/lib/checkoff/projects.rb +4 -3
- data/lib/checkoff/sections.rb +4 -2
- data/lib/checkoff/subtasks.rb +2 -1
- data/lib/checkoff/tags.rb +3 -2
- data/lib/checkoff/task_searches.rb +6 -3
- data/lib/checkoff/tasks.rb +8 -7
- data/lib/checkoff/timelines.rb +7 -3
- data/lib/checkoff/timing.rb +4 -8
- data/lib/checkoff/version.rb +1 -1
- data/lib/checkoff/workspaces.rb +5 -2
- data/rbi/checkoff.rbi +101 -57
- data/sig/checkoff.rbs +92 -55
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1896715fc41d51e76c6c1381eed1a4b3cd72ee304a75b36d1a26bfe42d0ee158
|
|
4
|
+
data.tar.gz: 07f5cc66320360ebc668d21adc062585fb6927b029872b99f0416f30b7986387
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 161d6849b466f214b62ee2c04d2188bd672323560ff37fd99709166efb920aea03aec8f5255835b2b938ee6c0036c847e40d57a509ccaeded3f03b5bda98d603
|
|
7
|
+
data.tar.gz: 16acaccaea9884fbfe513ac8dfe6f9bb30f72342deba8d88a335a3f391d8fb18fd61d8034731346b5e6015ef9c0fb125e3153599dfe2eeb76dfd4ed6ff0ccfe3
|
data/lib/checkoff/attachments.rb
CHANGED
|
@@ -54,7 +54,7 @@ module Checkoff
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# @param url [String]
|
|
57
|
-
# @param resource [Asana::Resources::
|
|
57
|
+
# @param resource [Asana::Resources::Task]
|
|
58
58
|
# @param attachment_name [String,nil]
|
|
59
59
|
# @param just_the_url [Boolean]
|
|
60
60
|
# @param verify_mode [Integer] - e.g., OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER
|
|
@@ -79,15 +79,15 @@ module Checkoff
|
|
|
79
79
|
# extension as the URL using Net::HTTP, raising an exception if
|
|
80
80
|
# not succesful
|
|
81
81
|
#
|
|
82
|
-
# @param uri [URI]
|
|
82
|
+
# @param uri [URI::Generic]
|
|
83
83
|
# @param verify_mode [Integer] - e.g., OpenSSL::SSL::VERIFY_NONE,OpenSSL::SSL::VERIFY_PEER
|
|
84
84
|
#
|
|
85
85
|
# @return [Object]
|
|
86
86
|
def download_uri(uri, verify_mode: OpenSSL::SSL::VERIFY_PEER, &block)
|
|
87
87
|
out = nil
|
|
88
|
-
# @sg-ignore
|
|
89
88
|
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', verify_mode:) do |http|
|
|
90
|
-
# @sg-ignore
|
|
89
|
+
# @sg-ignore Unresolved constant Net::HTTP::Get / Unresolved call to request —
|
|
90
|
+
# stdlib RBS gap on Net::HTTP block param types
|
|
91
91
|
http.request(Net::HTTP::Get.new(uri)) do |response|
|
|
92
92
|
raise("Unexpected response code: #{response.code}") unless response.code == '200'
|
|
93
93
|
|
|
@@ -120,7 +120,7 @@ module Checkoff
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
# @param url [String]
|
|
123
|
-
# @param resource [Asana::Resources::
|
|
123
|
+
# @param resource [Asana::Resources::Task]
|
|
124
124
|
# @param attachment_name [String,nil]
|
|
125
125
|
# @param verify_mode [Integer] - e.g., OpenSSL::SSL::VERIFY_NONE,OpenSSL::SSL::VERIFY_PEER
|
|
126
126
|
#
|
|
@@ -130,25 +130,25 @@ module Checkoff
|
|
|
130
130
|
uri = URI(url)
|
|
131
131
|
attachment_name ||= File.basename(uri.path)
|
|
132
132
|
download_uri(uri, verify_mode:) do |tempfile|
|
|
133
|
-
# @sg-ignore
|
|
133
|
+
# @sg-ignore attachment_name reassignment above isn't narrowed from the declared
|
|
134
|
+
# [String, nil] param type — needs a fresh typed local, deferred to a follow-up code PR
|
|
134
135
|
content_type ||= content_type_from_filename(attachment_name)
|
|
135
|
-
# @sg-ignore
|
|
136
|
+
# @sg-ignore URI::Generic#path can be nil
|
|
136
137
|
content_type ||= content_type_from_filename(uri.path)
|
|
137
138
|
|
|
138
|
-
# @sg-ignore
|
|
139
|
+
# @sg-ignore same attachment_name/content_type narrowing gap as above
|
|
139
140
|
resource.attach(filename: attachment_name, mime: content_type,
|
|
140
141
|
io: tempfile)
|
|
141
142
|
end
|
|
142
143
|
end
|
|
143
144
|
|
|
144
145
|
# @param url [String]
|
|
145
|
-
# @param resource [Asana::Resources::
|
|
146
|
+
# @param resource [Asana::Resources::Task]
|
|
146
147
|
# @param attachment_name [String,nil]
|
|
147
148
|
#
|
|
148
149
|
# @return [Asana::Resources::Attachment]
|
|
149
150
|
def create_attachment_from_url_alone!(url, resource, attachment_name:)
|
|
150
151
|
with_params = {
|
|
151
|
-
# @sg-ignore
|
|
152
152
|
'parent' => resource.gid,
|
|
153
153
|
'url' => url,
|
|
154
154
|
'resource_subtype' => 'external',
|
|
@@ -196,7 +196,8 @@ module Checkoff
|
|
|
196
196
|
tasks = Checkoff::Tasks.new
|
|
197
197
|
attachments = Checkoff::Attachments.new
|
|
198
198
|
task = tasks.task_by_gid(gid)
|
|
199
|
-
# @sg-ignore
|
|
199
|
+
# @sg-ignore task_by_gid can return nil; needs a raise-if-nil guard, deferred to a
|
|
200
|
+
# follow-up code PR
|
|
200
201
|
attachment = attachments.create_attachment_from_url!(url, task)
|
|
201
202
|
puts "Results: #{attachment.inspect}"
|
|
202
203
|
end
|
data/lib/checkoff/cli.rb
CHANGED
|
@@ -29,7 +29,6 @@ module Checkoff
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
@from_workspace_name = from_workspace_arg
|
|
32
|
-
# @sg-ignore
|
|
33
32
|
@from_project_name = project_arg_to_name(from_project_arg)
|
|
34
33
|
@from_section_name = from_section_arg
|
|
35
34
|
end
|
|
@@ -41,7 +40,6 @@ module Checkoff
|
|
|
41
40
|
if to_project_arg == :source_project
|
|
42
41
|
from_project_name
|
|
43
42
|
else
|
|
44
|
-
# @sg-ignore
|
|
45
43
|
project_arg_to_name(to_project_arg)
|
|
46
44
|
end
|
|
47
45
|
end
|
|
@@ -146,7 +144,7 @@ module Checkoff
|
|
|
146
144
|
:to_workspace_name, :to_project_name, :to_section_name,
|
|
147
145
|
:projects, :sections
|
|
148
146
|
|
|
149
|
-
# @param project_arg [String]
|
|
147
|
+
# @param project_arg [Symbol, String]
|
|
150
148
|
# @return [Symbol, String]
|
|
151
149
|
def project_arg_to_name(project_arg)
|
|
152
150
|
arg = project_arg.to_s
|
|
@@ -218,7 +216,6 @@ module Checkoff
|
|
|
218
216
|
# @return [String]
|
|
219
217
|
def run_on_project(workspace, project)
|
|
220
218
|
tasks_by_section =
|
|
221
|
-
# @sg-ignore
|
|
222
219
|
sections.tasks_by_section(workspace, project)
|
|
223
220
|
tasks_by_section.update(tasks_by_section) do |_key, tasks|
|
|
224
221
|
tasks_to_hash(tasks)
|
data/lib/checkoff/clients.rb
CHANGED
|
@@ -30,9 +30,9 @@ module Checkoff
|
|
|
30
30
|
# @return [Asana::Client]
|
|
31
31
|
def client
|
|
32
32
|
@client ||= @asana_client_class.new do |c|
|
|
33
|
-
# @sg-ignore
|
|
33
|
+
# @sg-ignore Unresolved call to authentication — gem config DSL block, not covered by annotations
|
|
34
34
|
c.authentication :access_token, @config.fetch(:personal_access_token)
|
|
35
|
-
# @sg-ignore
|
|
35
|
+
# @sg-ignore Unresolved call to default_headers — gem config DSL block, not covered by annotations
|
|
36
36
|
c.default_headers 'asana-enable' =>
|
|
37
37
|
'new_project_templates,new_user_task_lists,new_memberships,new_goal_memberships'
|
|
38
38
|
end
|
|
@@ -41,7 +41,7 @@ module Checkoff
|
|
|
41
41
|
# @param custom_field_name [String]
|
|
42
42
|
#
|
|
43
43
|
# @return [Asana::Resources::CustomField]
|
|
44
|
-
# @sg-ignore
|
|
44
|
+
# @sg-ignore nil check above is not flow-sensitive
|
|
45
45
|
def custom_field_or_raise(workspace_name, custom_field_name)
|
|
46
46
|
cf = custom_field(workspace_name, custom_field_name)
|
|
47
47
|
raise "Could not find custom_field #{custom_field_name} under workspace #{workspace_name}." if cf.nil?
|
|
@@ -78,8 +78,10 @@ module Checkoff
|
|
|
78
78
|
|
|
79
79
|
# @param resource [Asana::Resources::Project,Asana::Resources::Task]
|
|
80
80
|
# @param custom_field_name [String]
|
|
81
|
-
# @sg-ignore
|
|
82
81
|
# @return [Array<String>]
|
|
82
|
+
# @sg-ignore Declared return type Array<String> does not match inferred type Array,Array<Array> —
|
|
83
|
+
# fixing cleanly needs binding enum_value.fetch('name') to a typed local; leaving for a
|
|
84
|
+
# follow-up code PR to keep this one annotation-only.
|
|
83
85
|
def resource_custom_field_values_names_by_name(resource, custom_field_name)
|
|
84
86
|
custom_field = resource_custom_field_by_name(resource, custom_field_name)
|
|
85
87
|
return [] if custom_field.nil?
|
|
@@ -108,9 +110,9 @@ module Checkoff
|
|
|
108
110
|
end
|
|
109
111
|
|
|
110
112
|
# @param resource [Asana::Resources::Task,Asana::Resources::Project]
|
|
111
|
-
# @sg-ignore
|
|
112
113
|
# @param custom_field_name [String]
|
|
113
114
|
# @return [Hash]
|
|
115
|
+
# @sg-ignore nil check above is not flow-sensitive
|
|
114
116
|
def resource_custom_field_by_name_or_raise(resource, custom_field_name)
|
|
115
117
|
custom_field = resource_custom_field_by_name(resource, custom_field_name)
|
|
116
118
|
if custom_field.nil?
|
|
@@ -120,10 +122,10 @@ module Checkoff
|
|
|
120
122
|
custom_field
|
|
121
123
|
end
|
|
122
124
|
|
|
123
|
-
# @sg-ignore
|
|
124
125
|
# @param resource [Asana::Resources::Project,Asana::Resources::Task]
|
|
125
126
|
# @param custom_field_gid [String]
|
|
126
127
|
# @return [Hash]
|
|
128
|
+
# @sg-ignore nil check above is not flow-sensitive
|
|
127
129
|
def resource_custom_field_by_gid_or_raise(resource, custom_field_gid)
|
|
128
130
|
# @type [Array<Hash>]
|
|
129
131
|
custom_fields = resource.custom_fields
|
|
@@ -146,7 +148,9 @@ module Checkoff
|
|
|
146
148
|
# @param custom_field [Hash{String => Hash,Array<Hash>}]
|
|
147
149
|
#
|
|
148
150
|
# @return [Array<Hash>]
|
|
149
|
-
# @sg-ignore
|
|
151
|
+
# @sg-ignore Declared return type Array<Hash> does not match inferred type Array,Hash,Array<Hash> —
|
|
152
|
+
# fixing cleanly needs restructuring the case/when to bind each branch to a typed local; leaving
|
|
153
|
+
# for a follow-up code PR to keep this one annotation-only.
|
|
150
154
|
def resource_custom_field_enum_values(custom_field)
|
|
151
155
|
resource_subtype = custom_field.fetch('resource_subtype')
|
|
152
156
|
case resource_subtype
|
|
@@ -74,9 +74,11 @@ module Checkoff
|
|
|
74
74
|
end
|
|
75
75
|
resource = webhook_subscription&.fetch('resource', nil)
|
|
76
76
|
name, resource_type = enrich_gid(resource) if resource
|
|
77
|
-
# @sg-ignore
|
|
77
|
+
# @sg-ignore webhook_subscription is declared [Hash, nil] but this line assumes non-nil
|
|
78
|
+
# without the &. used elsewhere in this method — looks like a real nil-safety gap, not
|
|
79
|
+
# just a typing gap; flagging for the follow-up code PR rather than silently widening
|
|
78
80
|
webhook_subscription['checkoff:enriched:name'] = name if name
|
|
79
|
-
# @sg-ignore
|
|
81
|
+
# @sg-ignore same nil-safety gap as above
|
|
80
82
|
webhook_subscription['checkoff:enriched:resource_type'] = resource_type if resource_type
|
|
81
83
|
end
|
|
82
84
|
|
|
@@ -96,13 +98,11 @@ module Checkoff
|
|
|
96
98
|
|
|
97
99
|
# @param filter [Hash{String => String, Array<String>}]
|
|
98
100
|
#
|
|
99
|
-
# @
|
|
100
|
-
# @return [String, nil]
|
|
101
|
+
# @return [void]
|
|
101
102
|
def enrich_filter_parent_gid!(filter)
|
|
102
|
-
parent_gid = filter['checkoff:parent.gid']
|
|
103
|
+
parent_gid = T.cast(filter['checkoff:parent.gid'], T.nilable(String))
|
|
103
104
|
return unless parent_gid
|
|
104
105
|
|
|
105
|
-
# @sg-ignore
|
|
106
106
|
name, resource_type = enrich_gid(parent_gid)
|
|
107
107
|
filter['checkoff:enriched:parent.name'] = name if name
|
|
108
108
|
filter['checkoff:enriched:parent.resource_type'] = resource_type if resource_type
|
|
@@ -112,11 +112,10 @@ module Checkoff
|
|
|
112
112
|
#
|
|
113
113
|
# @return [void]
|
|
114
114
|
def enrich_filter_resource!(filter)
|
|
115
|
-
resource_gid = filter['checkoff:resource.gid']
|
|
115
|
+
resource_gid = T.cast(filter['checkoff:resource.gid'], T.nilable(String))
|
|
116
116
|
|
|
117
117
|
return unless resource_gid
|
|
118
118
|
|
|
119
|
-
# @sg-ignore
|
|
120
119
|
task = tasks.task_by_gid(resource_gid)
|
|
121
120
|
task_name = task&.name
|
|
122
121
|
filter['checkoff:enriched:resource.name'] = task_name if task_name
|
|
@@ -134,12 +133,11 @@ module Checkoff
|
|
|
134
133
|
filter['checkoff:enriched:fetched.section.name'] = name if name
|
|
135
134
|
end
|
|
136
135
|
|
|
137
|
-
# @param asana_event [Hash
|
|
136
|
+
# @param asana_event [Hash]
|
|
138
137
|
#
|
|
139
138
|
# @return [void]
|
|
140
139
|
def enrich_event_parent!(asana_event)
|
|
141
140
|
# @type [Hash{String => String }]
|
|
142
|
-
# @sg-ignore
|
|
143
141
|
parent = asana_event['parent']
|
|
144
142
|
|
|
145
143
|
return unless parent
|
|
@@ -154,7 +152,7 @@ module Checkoff
|
|
|
154
152
|
nil
|
|
155
153
|
end
|
|
156
154
|
|
|
157
|
-
# @param asana_event [Hash
|
|
155
|
+
# @param asana_event [Hash]
|
|
158
156
|
#
|
|
159
157
|
# @return [void]
|
|
160
158
|
def enrich_event_resource!(asana_event)
|
|
@@ -20,12 +20,12 @@ module Checkoff
|
|
|
20
20
|
|
|
21
21
|
# @param key [Symbol]
|
|
22
22
|
# @return [Object]
|
|
23
|
-
# @sg-ignore
|
|
23
|
+
# @sg-ignore return type could not be inferred — cascades from the ENV.fetch RBS gap below
|
|
24
24
|
def [](key)
|
|
25
25
|
config_value = @config[key]
|
|
26
26
|
return config_value unless config_value.nil?
|
|
27
27
|
|
|
28
|
-
# @sg-ignore
|
|
28
|
+
# @sg-ignore Unresolved call to fetch on RBS::Unnamed::ENVClass, Class<ENV>
|
|
29
29
|
ENV.fetch(envvar_name(key), nil)
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -57,10 +57,10 @@ module Logging
|
|
|
57
57
|
private
|
|
58
58
|
|
|
59
59
|
# @return [Symbol]
|
|
60
|
-
# @sg-ignore
|
|
60
|
+
# @sg-ignore return type could not be inferred — cascades from the ENV.fetch RBS gap below
|
|
61
61
|
def log_level
|
|
62
|
-
# @sg-ignore
|
|
63
62
|
# rubocop:disable Style/RedundantFetchBlock
|
|
63
|
+
# @sg-ignore Unresolved call to fetch on RBS::Unnamed::ENVClass, Class<ENV>
|
|
64
64
|
ENV.fetch('LOG_LEVEL') { 'INFO' }.downcase.to_sym
|
|
65
65
|
# rubocop:enable Style/RedundantFetchBlock
|
|
66
66
|
end
|
|
@@ -24,9 +24,10 @@ module Checkoff
|
|
|
24
24
|
# @param url [String]
|
|
25
25
|
# @return [Array(Hash{String => String}, Array)]
|
|
26
26
|
def convert_params(url)
|
|
27
|
-
# @sg-ignore
|
|
27
|
+
# @sg-ignore URI#query can be nil for a URL with no query string; CGI.parse expects
|
|
28
|
+
# String — looks like a real gap (unhandled no-query-string case), not just a typing
|
|
29
|
+
# gap, flagging for the follow-up code PR rather than silently asserting non-nil
|
|
28
30
|
url_params = CGI.parse(URI.parse(url).query)
|
|
29
|
-
# @sg-ignore
|
|
30
31
|
custom_field_params, date_url_params, simple_url_params = partition_url_params(url_params)
|
|
31
32
|
custom_field_args, custom_field_task_selector = convert_custom_field_params(custom_field_params)
|
|
32
33
|
date_url_args, date_task_selector = convert_date_params(date_url_params)
|
|
@@ -56,14 +57,15 @@ module Checkoff
|
|
|
56
57
|
CustomFieldParamConverter.new(custom_field_params:).convert
|
|
57
58
|
end
|
|
58
59
|
|
|
59
|
-
# @param url_params [Hash{String => String}]
|
|
60
|
+
# @param url_params [Hash{String => Array<String>}]
|
|
60
61
|
# @return [Array(Hash{String => Array<String>}, Hash{String => Array<String>}, Hash{String => Array<String>})]
|
|
61
62
|
def partition_url_params(url_params)
|
|
62
63
|
groups = T.let(url_params.to_a.group_by do |key, _values|
|
|
63
|
-
# @sg-ignore
|
|
64
|
+
# @sg-ignore Unresolved call to start_with? — T.let wrapping this
|
|
65
|
+
# chained group_by block seems to block block-param inference
|
|
64
66
|
if key.start_with? 'custom_field_'
|
|
65
67
|
:custom_field
|
|
66
|
-
# @sg-ignore
|
|
68
|
+
# @sg-ignore Unresolved call to include? — same T.let/block-param gap
|
|
67
69
|
elsif key.include? '_date'
|
|
68
70
|
:date
|
|
69
71
|
else
|
|
@@ -17,8 +17,7 @@ module Checkoff
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# @param task_selectors [Array<Symbol, Array>]
|
|
20
|
-
# @return [Array(Symbol, Array, Array)]
|
|
21
|
-
# @sg-ignore
|
|
20
|
+
# @return [Symbol, Array, Array(Symbol, Array, Array)]
|
|
22
21
|
def self.merge_task_selectors(*task_selectors)
|
|
23
22
|
return [] if task_selectors.empty?
|
|
24
23
|
|
|
@@ -238,9 +238,10 @@ module Checkoff
|
|
|
238
238
|
end
|
|
239
239
|
|
|
240
240
|
# @param resource [Asana::Resources::Task, Asana::Resources::Project]
|
|
241
|
-
# @sg-ignore
|
|
242
241
|
# @param prefix [String]
|
|
243
242
|
# @return [boolish]
|
|
243
|
+
# @sg-ignore Checkoff::SelectorClasses::Common::NameStartsWithPFunctionEvaluator#evaluate
|
|
244
|
+
# return type could not be inferred
|
|
244
245
|
def evaluate(resource, prefix)
|
|
245
246
|
resource.name&.start_with?(prefix)
|
|
246
247
|
end
|
|
@@ -15,15 +15,15 @@ module Checkoff
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
# @return [Boolean]
|
|
18
|
-
# @sg-ignore
|
|
18
|
+
# @sg-ignore return type could not be inferred — abstract method body is only `raise`
|
|
19
19
|
def matches?
|
|
20
20
|
raise 'Override me!'
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# @param _task [Asana::Resources::Task]
|
|
24
24
|
# @param _args [Array<Object>]
|
|
25
|
-
# @sg-ignore
|
|
26
25
|
# @return [Object]
|
|
26
|
+
# @sg-ignore return type could not be inferred — abstract method body is only `raise`
|
|
27
27
|
def evaluate(_task, *_args)
|
|
28
28
|
raise 'Implement me!'
|
|
29
29
|
end
|
|
@@ -362,7 +362,8 @@ module Checkoff
|
|
|
362
362
|
# @param task [Asana::Resources::Task]
|
|
363
363
|
#
|
|
364
364
|
# @return [Boolean]
|
|
365
|
-
# @sg-ignore
|
|
365
|
+
# @sg-ignore Checkoff::SelectorClasses::Task::MilestonePFunctionEvaluator#evaluate
|
|
366
|
+
# return type could not be inferred
|
|
366
367
|
def evaluate(task)
|
|
367
368
|
raise 'Please add resource_subtype to extra_fields' if task.resource_subtype.nil?
|
|
368
369
|
|
|
@@ -19,14 +19,16 @@ module Checkoff
|
|
|
19
19
|
|
|
20
20
|
# @param task [Asana::Resources::Task]
|
|
21
21
|
# @return [Time, nil]
|
|
22
|
-
# @sg-ignore
|
|
22
|
+
# @sg-ignore return type could not be inferred — #to_time called via &. on a Date|Time
|
|
23
|
+
# union receiver
|
|
23
24
|
def start_time(task)
|
|
24
25
|
date_or_time_field_by_name(task, :start)&.to_time
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# @param task [Asana::Resources::Task]
|
|
28
|
-
# @sg-ignore
|
|
29
29
|
# @return [Time, nil]
|
|
30
|
+
# @sg-ignore return type could not be inferred — #to_time called via &. on a Date|Time
|
|
31
|
+
# union receiver
|
|
30
32
|
def due_time(task)
|
|
31
33
|
date_or_time_field_by_name(task, :due)&.to_time
|
|
32
34
|
end
|
|
@@ -35,8 +37,7 @@ module Checkoff
|
|
|
35
37
|
#
|
|
36
38
|
# @return [Date, Time, nil]
|
|
37
39
|
def start_date_or_time(task)
|
|
38
|
-
|
|
39
|
-
return @time_class.parse(task.start_at).localtime unless task.start_at.nil?
|
|
40
|
+
return @time_class.parse(T.must(task.start_at)).localtime unless task.start_at.nil?
|
|
40
41
|
|
|
41
42
|
return @date_class.parse(task.start_on) unless task.start_on.nil?
|
|
42
43
|
|
|
@@ -58,7 +59,7 @@ module Checkoff
|
|
|
58
59
|
#
|
|
59
60
|
# @return [Time, nil]
|
|
60
61
|
def modified_time(task)
|
|
61
|
-
@time_class.parse(task.modified_at).localtime unless task.modified_at.nil?
|
|
62
|
+
@time_class.parse(T.must(task.modified_at)).localtime unless task.modified_at.nil?
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
# @param task [Asana::Resources::Task]
|
|
@@ -10,7 +10,8 @@ module Checkoff
|
|
|
10
10
|
# @param value [Object,Boolean]
|
|
11
11
|
# @yieldreturn [generic<T>]
|
|
12
12
|
# @return [generic<T>]
|
|
13
|
-
# @sg-ignore
|
|
13
|
+
# @sg-ignore return type could not be inferred — Solargraph's generic/yield-return
|
|
14
|
+
# inference gap for #with_thread_local_variable
|
|
14
15
|
def with_thread_local_variable(name, value, &block)
|
|
15
16
|
old_value = Thread.current[name]
|
|
16
17
|
Thread.current[name] = value
|
data/lib/checkoff/my_tasks.rb
CHANGED
|
@@ -67,7 +67,9 @@ 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
|
-
# @sg-ignore
|
|
70
|
+
# @sg-ignore assignee_section can be nil (task has no My Tasks section) but is
|
|
71
|
+
# dereferenced without a nil guard — looks like a real gap, flagging for the
|
|
72
|
+
# follow-up code PR
|
|
71
73
|
current_section = section_key(assignee_section.name)
|
|
72
74
|
by_section[current_section] ||= []
|
|
73
75
|
by_section[current_section] << task
|
data/lib/checkoff/portfolios.rb
CHANGED
|
@@ -47,7 +47,7 @@ module Checkoff
|
|
|
47
47
|
# @param portfolio_name [String]
|
|
48
48
|
#
|
|
49
49
|
# @return [Asana::Resources::Portfolio]
|
|
50
|
-
# @sg-ignore
|
|
50
|
+
# @sg-ignore nil check above is not flow-sensitive
|
|
51
51
|
def portfolio_or_raise(workspace_name, portfolio_name)
|
|
52
52
|
portfolio_obj = portfolio(workspace_name, portfolio_name)
|
|
53
53
|
raise "Could not find portfolio #{portfolio_name} under workspace #{workspace_name}." if portfolio_obj.nil?
|
data/lib/checkoff/projects.rb
CHANGED
|
@@ -108,8 +108,9 @@ module Checkoff
|
|
|
108
108
|
else
|
|
109
109
|
# @type [Enumerable<Asana::Resources::Project>]
|
|
110
110
|
ps = projects_by_workspace_name(workspace_name, extra_fields:)
|
|
111
|
-
# @type
|
|
112
|
-
# @sg-ignore
|
|
111
|
+
# @type [Asana::Resources::Project,nil]
|
|
112
|
+
# @sg-ignore Variable type could not be inferred / Unresolved call to _1 — Solargraph
|
|
113
|
+
# doesn't fully support numbered block parameters
|
|
113
114
|
project = ps.find { _1.name == project_name }
|
|
114
115
|
project_by_gid(project.gid, extra_fields:) unless project.nil?
|
|
115
116
|
end
|
|
@@ -120,8 +121,8 @@ module Checkoff
|
|
|
120
121
|
# @param project_name [String,Symbol] - :my_tasks or a project name
|
|
121
122
|
# @param [Array<String>] extra_fields
|
|
122
123
|
#
|
|
123
|
-
# @sg-ignore
|
|
124
124
|
# @return [Asana::Resources::Project]
|
|
125
|
+
# @sg-ignore nil check above is not flow-sensitive
|
|
125
126
|
def project_or_raise(workspace_name, project_name, extra_fields: [])
|
|
126
127
|
p = project(workspace_name, project_name, extra_fields:)
|
|
127
128
|
raise "Could not find project #{project_name.inspect} under workspace #{workspace_name}." if p.nil?
|
data/lib/checkoff/sections.rb
CHANGED
|
@@ -80,7 +80,7 @@ module Checkoff
|
|
|
80
80
|
|
|
81
81
|
# Given a workspace name and project name, then provide a Hash of
|
|
82
82
|
# tasks with section name -> task list of the uncompleted tasks
|
|
83
|
-
# @param workspace_name [String]
|
|
83
|
+
# @param workspace_name [String, Symbol]
|
|
84
84
|
# @param project_name [String, Symbol]
|
|
85
85
|
# @param only_uncompleted [Boolean]
|
|
86
86
|
# @param extra_fields [Array<String>]
|
|
@@ -295,7 +295,9 @@ module Checkoff
|
|
|
295
295
|
# @type [String, nil]
|
|
296
296
|
current_section = section_key(section_name)
|
|
297
297
|
|
|
298
|
-
# @sg-ignore
|
|
298
|
+
# @sg-ignore Hash#fetch arg0 expected String,NilClass received String,nil — Solargraph
|
|
299
|
+
# treats the nil literal in a Hash{K,nil=>V} declaration and an inferred `nil` union
|
|
300
|
+
# member as distinct types; no comment-only fix found
|
|
299
301
|
by_section.fetch(current_section) << task
|
|
300
302
|
end
|
|
301
303
|
|
data/lib/checkoff/subtasks.rb
CHANGED
|
@@ -89,7 +89,8 @@ module Checkoff
|
|
|
89
89
|
# as memberships with a separate API within a task.
|
|
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
|
|
93
94
|
def subtask_section?(subtask)
|
|
94
95
|
subtask.is_rendered_as_separator
|
|
95
96
|
end
|
data/lib/checkoff/tags.rb
CHANGED
|
@@ -79,7 +79,7 @@ module Checkoff
|
|
|
79
79
|
# @param tag_name [String]
|
|
80
80
|
#
|
|
81
81
|
# @return [Asana::Resources::Tag]
|
|
82
|
-
# @sg-ignore
|
|
82
|
+
# @sg-ignore nil check above is not flow-sensitive
|
|
83
83
|
def tag_or_raise(workspace_name, tag_name)
|
|
84
84
|
t = tag(workspace_name, tag_name)
|
|
85
85
|
|
|
@@ -114,7 +114,8 @@ module Checkoff
|
|
|
114
114
|
# @return [Hash{Symbol => Object}]
|
|
115
115
|
def build_params(options)
|
|
116
116
|
{ limit: options[:per_page], completed_since: options[:completed_since] }.reject do |_, v|
|
|
117
|
-
# @sg-ignore
|
|
117
|
+
# @sg-ignore Unresolved call to nil? on Object — Solargraph doesn't treat a Hash's
|
|
118
|
+
# declared `Object` value type as full Object here
|
|
118
119
|
v.nil? || Array(v).empty?
|
|
119
120
|
end
|
|
120
121
|
end
|
|
@@ -73,7 +73,9 @@ module Checkoff
|
|
|
73
73
|
workspace = workspaces.workspace_or_raise(workspace_name)
|
|
74
74
|
api_params, task_selector = @search_url_parser.convert_params(url)
|
|
75
75
|
debug { "Task search params: api_params=#{api_params}, task_selector=#{task_selector}" }
|
|
76
|
-
# @sg-ignore
|
|
76
|
+
# @sg-ignore task_selector expected Symbol,Array<Symbol,Integer,Array> received
|
|
77
|
+
# Hash{String=>String} — Solargraph mis-assigns tuple-destructured types from
|
|
78
|
+
# `api_params, task_selector = @search_url_parser.convert_params(url)` above
|
|
77
79
|
raw_task_search(api_params, workspace_gid: workspace.gid, task_selector:,
|
|
78
80
|
extra_fields:)
|
|
79
81
|
end
|
|
@@ -186,8 +188,9 @@ module Checkoff
|
|
|
186
188
|
end
|
|
187
189
|
|
|
188
190
|
# @param [Array<String>] extra_fields
|
|
189
|
-
# @
|
|
190
|
-
# @return
|
|
191
|
+
# @return [Hash{Symbol => Array<String>}]
|
|
192
|
+
# @sg-ignore return type could not be inferred — bracket access on a Hash{Symbol=>undefined}
|
|
193
|
+
# local doesn't narrow even with a T.cast on the return expression
|
|
191
194
|
def calculate_api_options(extra_fields)
|
|
192
195
|
# @type [Hash{Symbol => undefined}]
|
|
193
196
|
all_options = projects.task_options(extra_fields: ['custom_fields'] + extra_fields)
|
data/lib/checkoff/tasks.rb
CHANGED
|
@@ -122,8 +122,7 @@ module Checkoff
|
|
|
122
122
|
only_uncompleted: true,
|
|
123
123
|
extra_fields: [])
|
|
124
124
|
thread_local = Checkoff::Internal::ThreadLocal.new
|
|
125
|
-
# @type [String]
|
|
126
|
-
# @sg-ignore
|
|
125
|
+
# @type [String, nil]
|
|
127
126
|
task_gid = thread_local.with_thread_local_variable(:suppress_asana_webhook_watch_creation,
|
|
128
127
|
true) do
|
|
129
128
|
gid_for_task(workspace_name, project_name, section_name, task_name)
|
|
@@ -179,9 +178,10 @@ module Checkoff
|
|
|
179
178
|
# @param name [String]
|
|
180
179
|
# @param workspace_gid [String]
|
|
181
180
|
# @param assignee_gid [String]
|
|
182
|
-
# @sg-ignore
|
|
183
181
|
#
|
|
184
182
|
# @return [Asana::Resources::Task]
|
|
183
|
+
# @sg-ignore @asana_task.create return type could not be inferred — calling a class method
|
|
184
|
+
# through a Class<T>-typed ivar is a known Solargraph dispatch limitation
|
|
185
185
|
def add_task(name,
|
|
186
186
|
workspace_gid: @workspaces.default_workspace_gid,
|
|
187
187
|
assignee_gid: default_assignee_gid)
|
|
@@ -201,10 +201,10 @@ module Checkoff
|
|
|
201
201
|
|
|
202
202
|
# True if any of the task's dependencies are marked incomplete
|
|
203
203
|
#
|
|
204
|
-
# @sg-ignore
|
|
205
204
|
# Include 'dependencies.gid' in extra_fields of task passed in.
|
|
206
205
|
#
|
|
207
206
|
# @param task [Asana::Resources::Task]
|
|
207
|
+
# @return [Boolean]
|
|
208
208
|
def incomplete_dependencies?(task)
|
|
209
209
|
# Avoid a redundant fetch. Unfortunately, Ruby SDK allows
|
|
210
210
|
# dependencies to be fetched along with other attributes--but
|
|
@@ -214,14 +214,13 @@ module Checkoff
|
|
|
214
214
|
#
|
|
215
215
|
# https://github.com/Asana/ruby-asana/issues/125
|
|
216
216
|
|
|
217
|
-
# @type [
|
|
217
|
+
# @type [Array<Hash>]
|
|
218
218
|
dependencies = task.instance_variable_get(:@dependencies) || []
|
|
219
219
|
|
|
220
220
|
dependencies.any? do |parent_task_info|
|
|
221
221
|
# the real bummer though is that asana doesn't let you fetch
|
|
222
222
|
# the completion status of dependencies, so we need to do this
|
|
223
223
|
# regardless:
|
|
224
|
-
# @sg-ignore
|
|
225
224
|
parent_task_gid = parent_task_info.fetch('gid')
|
|
226
225
|
|
|
227
226
|
parent_task = task_by_gid(parent_task_gid, only_uncompleted: false)
|
|
@@ -387,7 +386,9 @@ module Checkoff
|
|
|
387
386
|
end
|
|
388
387
|
|
|
389
388
|
# @return [String]
|
|
390
|
-
# @sg-ignore
|
|
389
|
+
# @sg-ignore @config.fetch return type could not be inferred — @config is intentionally
|
|
390
|
+
# dual-typed [Hash, EnvFallbackConfigLoader] throughout this codebase; Solargraph can't
|
|
391
|
+
# unify #fetch across that union
|
|
391
392
|
def default_assignee_gid
|
|
392
393
|
@config.fetch(:default_assignee_gid)
|
|
393
394
|
end
|
data/lib/checkoff/timelines.rb
CHANGED
|
@@ -57,7 +57,9 @@ module Checkoff
|
|
|
57
57
|
section_data = membership_data.fetch('section')
|
|
58
58
|
section_gid = section_data.fetch('gid')
|
|
59
59
|
section = @sections.section_by_gid(section_gid)
|
|
60
|
-
# @sg-ignore
|
|
60
|
+
# @sg-ignore section_by_gid can return nil on a gid lookup miss, and
|
|
61
|
+
# task_data_dependent_on_previous_section_last_milestone? dereferences it immediately
|
|
62
|
+
# without a nil guard — looks like a real gap, flagging for the follow-up code PR
|
|
61
63
|
task_data_dependent_on_previous_section_last_milestone?(task_data, section)
|
|
62
64
|
end
|
|
63
65
|
end
|
|
@@ -95,7 +97,8 @@ module Checkoff
|
|
|
95
97
|
|
|
96
98
|
all_dependent_task_gids ||= @tasks.all_dependent_tasks(task).map(&:gid)
|
|
97
99
|
|
|
98
|
-
# @sg-ignore
|
|
100
|
+
# @sg-ignore all_dependent_task_gids is nil-initialized then set via ||= above, but
|
|
101
|
+
# Solargraph doesn't narrow it back to non-nil after the reassignment
|
|
99
102
|
all_dependent_task_gids.include? last_milestone.gid
|
|
100
103
|
end
|
|
101
104
|
end
|
|
@@ -131,7 +134,8 @@ module Checkoff
|
|
|
131
134
|
dependent_task.resource_subtype == 'milestone'
|
|
132
135
|
end
|
|
133
136
|
|
|
134
|
-
# @sg-ignore
|
|
137
|
+
# @sg-ignore all_dependent_milestones is nil-initialized then set via ||= above, but
|
|
138
|
+
# Solargraph doesn't narrow it back to non-nil after the reassignment
|
|
135
139
|
all_dependent_milestones.any? do |milestone|
|
|
136
140
|
milestone.memberships.any? do |milestone_membership_data|
|
|
137
141
|
milestone_membership_data.fetch('project').fetch('gid') == project_gid
|