checkoff 0.19.2 → 0.20.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/.circleci/config.yml +6 -11
- data/.overcommit.yml +21 -0
- data/.rubocop.yml +1 -1
- data/Gemfile.lock +48 -23
- data/Makefile +23 -1
- data/checkoff.gemspec +9 -3
- data/coverage/.last_run.json +2 -2
- data/fix.sh +21 -2
- data/lib/checkoff/internal/search_url/custom_field_param_converter.rb +73 -0
- data/lib/checkoff/internal/search_url/custom_field_variant.rb +111 -0
- data/lib/checkoff/internal/search_url/parser.rb +42 -0
- data/lib/checkoff/internal/search_url/simple_param_converter.rb +82 -0
- data/lib/checkoff/internal/search_url.rb +3 -0
- data/lib/checkoff/subtasks.rb +1 -1
- data/lib/checkoff/task_searches.rb +2 -2
- data/lib/checkoff/version.rb +1 -1
- metadata +57 -10
- data/lib/checkoff/internal/search_url_parser.rb +0 -228
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca8acc9a67554659cd34a8a9a60f539e834088bc896ef6e3bb7761e75183ac8a
|
|
4
|
+
data.tar.gz: ff10088e95968a73ccb5590b316a4b6e3f9ff49572ab87271f710bc9a02f25b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a80da7607358f742d06fec48be66df5b53d41832ecbc16bdaae3a023c9a929d3cfff0a983ccbbdb381d1079187589c0e3c367ade5b8f7b92fe36fcd5025bfc65
|
|
7
|
+
data.tar.gz: dc9126b9a3366626c81f1eef2f32c517f9b978e5ec07052cc2194bf718e2e9c778da2fe2b09b6a1d07213437b7bf3265d636dcd071bb7ca01d822819883d1087
|
data/.circleci/config.yml
CHANGED
|
@@ -37,9 +37,9 @@ commands:
|
|
|
37
37
|
sed -E -e 's/checkoff \([[:digit:]]+.[[:digit:]]+.[[:digit:]]+\)/checkoff (0.1.0)/g' \
|
|
38
38
|
Gemfile.lock > Gemfile.lock.deversioned
|
|
39
39
|
- restore_cache:
|
|
40
|
-
key: gems-
|
|
40
|
+
key: gems-v2-{{ checksum "Gemfile.lock.deversioned" }}
|
|
41
41
|
- restore_cache:
|
|
42
|
-
key: wheels-
|
|
42
|
+
key: wheels-v2-{{ checksum "requirements_dev.txt" }}
|
|
43
43
|
- run:
|
|
44
44
|
name: Initialize packages
|
|
45
45
|
command: |
|
|
@@ -54,11 +54,11 @@ commands:
|
|
|
54
54
|
exit 1
|
|
55
55
|
fi
|
|
56
56
|
- save_cache:
|
|
57
|
-
key: gems-
|
|
57
|
+
key: gems-v2-{{ checksum "Gemfile.lock.deversioned" }}
|
|
58
58
|
paths:
|
|
59
59
|
- "vendor/bundle"
|
|
60
60
|
- save_cache:
|
|
61
|
-
key: wheels-
|
|
61
|
+
key: wheels-v2-{{ checksum "requirements_dev.txt" }}
|
|
62
62
|
paths:
|
|
63
63
|
- "/home/circleci/.cache/pip/wheels"
|
|
64
64
|
- run:
|
|
@@ -104,13 +104,8 @@ jobs:
|
|
|
104
104
|
steps:
|
|
105
105
|
- set_up_environment
|
|
106
106
|
- run_with_languages:
|
|
107
|
-
label:
|
|
108
|
-
command:
|
|
109
|
-
bundle exec rake citest
|
|
110
|
-
# https://github.com/bluelabsio/records-mover/blob/master/Makefile#L25
|
|
111
|
-
git status --porcelain coverage/.last_run.json
|
|
112
|
-
git diff coverage/.last_run.json
|
|
113
|
-
test -z "$(git status --porcelain coverage/.last_run.json)"
|
|
107
|
+
label: test
|
|
108
|
+
command: make citest cicoverage
|
|
114
109
|
# This seemed to shave 5ish% of the build time off when added
|
|
115
110
|
resource_class: large
|
|
116
111
|
|
data/.overcommit.yml
CHANGED
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
# Uncomment the following lines to make the configuration take effect.
|
|
18
18
|
|
|
19
19
|
PreCommit:
|
|
20
|
+
# Extend default config at https://github.com/sds/overcommit/blob/master/config/default.yml
|
|
21
|
+
BrokenSymlinks:
|
|
22
|
+
enabled: true
|
|
23
|
+
description: 'Check for broken symlinks'
|
|
24
|
+
quiet: true
|
|
25
|
+
exclude:
|
|
26
|
+
# This is a symlink to sibling checkout of vincelifedaily, used
|
|
27
|
+
# only in local development
|
|
28
|
+
- config/env.1p
|
|
20
29
|
RuboCop:
|
|
21
30
|
enabled: true
|
|
22
31
|
on_warn: fail # Treat all warnings as failures
|
|
@@ -29,6 +38,10 @@ PreCommit:
|
|
|
29
38
|
- '**/Gemfile'
|
|
30
39
|
- '**/Rakefile'
|
|
31
40
|
- 'bin/*'
|
|
41
|
+
- 'script/*'
|
|
42
|
+
exclude:
|
|
43
|
+
- db/migrate/*.rb
|
|
44
|
+
- db/schema.rb
|
|
32
45
|
PythonFlake8:
|
|
33
46
|
enabled: true
|
|
34
47
|
on_warn: fail
|
|
@@ -37,6 +50,7 @@ PreCommit:
|
|
|
37
50
|
on_warn: fail
|
|
38
51
|
include:
|
|
39
52
|
- '.envrc'
|
|
53
|
+
- '**/*.sh'
|
|
40
54
|
YamlLint:
|
|
41
55
|
enabled: true
|
|
42
56
|
on_warn: fail
|
|
@@ -53,3 +67,10 @@ PrePush:
|
|
|
53
67
|
include:
|
|
54
68
|
- 'test/unit/**/test*.rb'
|
|
55
69
|
enabled: true
|
|
70
|
+
|
|
71
|
+
#PostCheckout:
|
|
72
|
+
# ALL: # Special hook name that customizes all hooks of this type
|
|
73
|
+
# quiet: true # Change all post-checkout hooks to only display output on failure
|
|
74
|
+
#
|
|
75
|
+
# IndexTags:
|
|
76
|
+
# enabled: true # Generate a tags file with `ctags` each time HEAD changes
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -12,7 +12,7 @@ GIT
|
|
|
12
12
|
PATH
|
|
13
13
|
remote: .
|
|
14
14
|
specs:
|
|
15
|
-
checkoff (0.
|
|
15
|
+
checkoff (0.20.0)
|
|
16
16
|
activesupport
|
|
17
17
|
asana (> 0.10.0)
|
|
18
18
|
cache_method
|
|
@@ -22,13 +22,14 @@ PATH
|
|
|
22
22
|
GEM
|
|
23
23
|
remote: https://rubygems.org/
|
|
24
24
|
specs:
|
|
25
|
-
activesupport (
|
|
25
|
+
activesupport (7.0.4)
|
|
26
26
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
27
27
|
i18n (>= 1.6, < 2)
|
|
28
28
|
minitest (>= 5.1)
|
|
29
29
|
tzinfo (~> 2.0)
|
|
30
|
-
|
|
30
|
+
ansi (1.5.0)
|
|
31
31
|
ast (2.4.2)
|
|
32
|
+
builder (3.2.4)
|
|
32
33
|
bump (0.10.0)
|
|
33
34
|
cache (0.4.1)
|
|
34
35
|
cache_method (0.2.7)
|
|
@@ -39,7 +40,9 @@ GEM
|
|
|
39
40
|
coderay (1.1.3)
|
|
40
41
|
concurrent-ruby (1.1.10)
|
|
41
42
|
dalli (3.2.2)
|
|
43
|
+
diff-lcs (1.5.0)
|
|
42
44
|
docile (1.4.0)
|
|
45
|
+
fakeweb (1.3.0)
|
|
43
46
|
faraday (1.5.1)
|
|
44
47
|
faraday-em_http (~> 1.0)
|
|
45
48
|
faraday-em_synchrony (~> 1.0)
|
|
@@ -68,12 +71,13 @@ GEM
|
|
|
68
71
|
imagen (0.1.8)
|
|
69
72
|
parser (>= 2.5, != 2.5.1.1)
|
|
70
73
|
iniparse (1.5.0)
|
|
74
|
+
json (2.6.2)
|
|
71
75
|
jwt (2.2.3)
|
|
72
76
|
kramdown (2.4.0)
|
|
73
77
|
rexml
|
|
74
78
|
kramdown-parser-gfm (1.1.0)
|
|
75
79
|
kramdown (~> 2.0)
|
|
76
|
-
mdl (0.
|
|
80
|
+
mdl (0.12.0)
|
|
77
81
|
kramdown (~> 2.3)
|
|
78
82
|
kramdown-parser-gfm (~> 1.1)
|
|
79
83
|
mixlib-cli (~> 2.1, >= 2.1.1)
|
|
@@ -82,12 +86,17 @@ GEM
|
|
|
82
86
|
method_source (1.0.0)
|
|
83
87
|
minitest (5.16.2)
|
|
84
88
|
minitest-profile (0.0.2)
|
|
89
|
+
minitest-reporters (1.5.0)
|
|
90
|
+
ansi
|
|
91
|
+
builder
|
|
92
|
+
minitest (>= 5.0)
|
|
93
|
+
ruby-progressbar
|
|
85
94
|
mixlib-cli (2.1.8)
|
|
86
95
|
mixlib-config (3.0.27)
|
|
87
96
|
tomlrb
|
|
88
97
|
mixlib-shellout (3.2.7)
|
|
89
98
|
chef-utils
|
|
90
|
-
mocha (
|
|
99
|
+
mocha (2.0.0.alpha.1)
|
|
91
100
|
multi_json (1.15.0)
|
|
92
101
|
multi_xml (0.6.0)
|
|
93
102
|
multipart-post (2.1.1)
|
|
@@ -101,35 +110,49 @@ GEM
|
|
|
101
110
|
childprocess (>= 0.6.3, < 5)
|
|
102
111
|
iniparse (~> 1.4)
|
|
103
112
|
rexml (~> 3.2)
|
|
104
|
-
parallel (1.
|
|
105
|
-
parser (3.
|
|
113
|
+
parallel (1.22.1)
|
|
114
|
+
parser (3.1.2.1)
|
|
106
115
|
ast (~> 2.4.1)
|
|
107
116
|
pry (0.14.1)
|
|
108
117
|
coderay (~> 1.1)
|
|
109
118
|
method_source (~> 1.0)
|
|
110
119
|
rack (2.2.4)
|
|
111
|
-
rainbow (3.
|
|
120
|
+
rainbow (3.1.1)
|
|
112
121
|
rake (13.0.3)
|
|
113
|
-
regexp_parser (2.
|
|
122
|
+
regexp_parser (2.6.0)
|
|
114
123
|
rexml (3.2.5)
|
|
115
|
-
|
|
124
|
+
rspec (3.11.0)
|
|
125
|
+
rspec-core (~> 3.11.0)
|
|
126
|
+
rspec-expectations (~> 3.11.0)
|
|
127
|
+
rspec-mocks (~> 3.11.0)
|
|
128
|
+
rspec-core (3.11.0)
|
|
129
|
+
rspec-support (~> 3.11.0)
|
|
130
|
+
rspec-expectations (3.11.1)
|
|
131
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
132
|
+
rspec-support (~> 3.11.0)
|
|
133
|
+
rspec-mocks (3.11.1)
|
|
134
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
135
|
+
rspec-support (~> 3.11.0)
|
|
136
|
+
rspec-support (3.11.1)
|
|
137
|
+
rubocop (1.36.0)
|
|
138
|
+
json (~> 2.3)
|
|
116
139
|
parallel (~> 1.10)
|
|
117
|
-
parser (>= 3.
|
|
140
|
+
parser (>= 3.1.2.1)
|
|
118
141
|
rainbow (>= 2.2.2, < 4.0)
|
|
119
142
|
regexp_parser (>= 1.8, < 3.0)
|
|
120
|
-
rexml
|
|
121
|
-
rubocop-ast (>= 1.
|
|
143
|
+
rexml (>= 3.2.5, < 4.0)
|
|
144
|
+
rubocop-ast (>= 1.20.1, < 2.0)
|
|
122
145
|
ruby-progressbar (~> 1.7)
|
|
123
146
|
unicode-display_width (>= 1.4.0, < 3.0)
|
|
124
|
-
rubocop-ast (1.
|
|
125
|
-
parser (>= 3.
|
|
147
|
+
rubocop-ast (1.22.0)
|
|
148
|
+
parser (>= 3.1.1.0)
|
|
126
149
|
rubocop-minitest (0.12.1)
|
|
127
150
|
rubocop (>= 0.90, < 2.0)
|
|
128
151
|
rubocop-rake (0.5.1)
|
|
129
152
|
rubocop
|
|
130
153
|
ruby-progressbar (1.11.0)
|
|
131
154
|
ruby2_keywords (0.0.5)
|
|
132
|
-
rugged (1.
|
|
155
|
+
rugged (1.5.0.1)
|
|
133
156
|
simplecov (0.21.2)
|
|
134
157
|
docile (~> 1.1)
|
|
135
158
|
simplecov-html (~> 0.11)
|
|
@@ -140,12 +163,11 @@ GEM
|
|
|
140
163
|
tomlrb (2.0.3)
|
|
141
164
|
tzinfo (2.0.5)
|
|
142
165
|
concurrent-ruby (~> 1.0)
|
|
143
|
-
undercover (0.4.
|
|
166
|
+
undercover (0.4.5)
|
|
144
167
|
imagen (>= 0.1.8)
|
|
145
168
|
rainbow (>= 2.1, < 4.0)
|
|
146
|
-
rugged (>= 0.27, < 1.
|
|
147
|
-
unicode-display_width (2.
|
|
148
|
-
zeitwerk (2.6.0)
|
|
169
|
+
rugged (>= 0.27, < 1.6)
|
|
170
|
+
unicode-display_width (2.3.0)
|
|
149
171
|
|
|
150
172
|
PLATFORMS
|
|
151
173
|
ruby
|
|
@@ -158,13 +180,16 @@ DEPENDENCIES
|
|
|
158
180
|
bump
|
|
159
181
|
bundler
|
|
160
182
|
checkoff!
|
|
183
|
+
fakeweb
|
|
161
184
|
mdl
|
|
162
185
|
minitest-profile
|
|
163
|
-
|
|
186
|
+
minitest-reporters
|
|
187
|
+
mocha (~> 2.0.0.alpha.1)
|
|
164
188
|
overcommit (>= 0.58.0)
|
|
165
189
|
pry
|
|
166
190
|
rake (~> 13.0)
|
|
167
|
-
|
|
191
|
+
rspec (>= 3.4)
|
|
192
|
+
rubocop (~> 1.36)
|
|
168
193
|
rubocop-minitest
|
|
169
194
|
rubocop-rake
|
|
170
195
|
simplecov (>= 0.18.0)
|
|
@@ -172,4 +197,4 @@ DEPENDENCIES
|
|
|
172
197
|
undercover
|
|
173
198
|
|
|
174
199
|
BUNDLED WITH
|
|
175
|
-
2.
|
|
200
|
+
2.3.23
|
data/Makefile
CHANGED
|
@@ -15,7 +15,7 @@ export PRINT_HELP_PYSCRIPT
|
|
|
15
15
|
help:
|
|
16
16
|
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
|
|
17
17
|
|
|
18
|
-
default:
|
|
18
|
+
default: clean-coverage test coverage quality ## run default typechecking, tests and quality
|
|
19
19
|
|
|
20
20
|
requirements_dev.txt.installed: requirements_dev.txt
|
|
21
21
|
pip install -q --disable-pip-version-check -r requirements_dev.txt
|
|
@@ -40,6 +40,8 @@ clear_metrics: ## remove or reset result artifacts created by tests and quality
|
|
|
40
40
|
clean: clear_metrics ## remove all built artifacts
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
citest: test ## Run unit tests from CircleCI
|
|
44
|
+
|
|
43
45
|
typecheck: ## validate types in code and configuration
|
|
44
46
|
|
|
45
47
|
overcommit: ## run precommit quality checks
|
|
@@ -56,6 +58,26 @@ localtest: ## run default local actions
|
|
|
56
58
|
repl: ## Load up checkoff in pry
|
|
57
59
|
@bundle exec rake repl
|
|
58
60
|
|
|
61
|
+
clean-coverage:
|
|
62
|
+
@bundle exec rake clear_metrics
|
|
63
|
+
|
|
64
|
+
coverage: test report-coverage ## check code coverage
|
|
65
|
+
@bundle exec rake undercover
|
|
66
|
+
|
|
67
|
+
report-coverage: test ## Report summary of coverage to stdout, and generate HTML, XML coverage report
|
|
68
|
+
|
|
69
|
+
report-coverage-to-codecov: report-coverage ## use codecov.io for PR-scoped code coverage reports
|
|
70
|
+
@curl -Os https://uploader.codecov.io/latest/linux/codecov
|
|
71
|
+
@chmod +x codecov
|
|
72
|
+
@./codecov --file coverage/lcov/checkoff.lcov --nonZero
|
|
73
|
+
|
|
74
|
+
# https://github.com/bluelabsio/records-mover/blob/master/Makefile#L25
|
|
75
|
+
cicoverage: report-coverage-to-codecov ## check code coverage, then report to codecov
|
|
76
|
+
@echo "Looking for un-checked-in unit test coverage metrics..."
|
|
77
|
+
@git status --porcelain coverage/.last_run.json
|
|
78
|
+
@git diff coverage/.last_run.json
|
|
79
|
+
@test -z "$(git status --porcelain coverage/.last_run.json)"
|
|
80
|
+
|
|
59
81
|
update_from_cookiecutter: ## Bring in changes from template project used to create this repo
|
|
60
82
|
bundle exec overcommit --uninstall
|
|
61
83
|
IN_COOKIECUTTER_PROJECT_UPGRADER=1 cookiecutter_project_upgrader || true
|
data/checkoff.gemspec
CHANGED
|
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
spec.summary = 'Command-line and gem client for Asana (unofficial)'
|
|
14
14
|
spec.homepage = 'https://github.com/apiology/checkoff'
|
|
15
15
|
spec.license = 'MIT license'
|
|
16
|
-
spec.required_ruby_version = '>= 2.
|
|
16
|
+
spec.required_ruby_version = '>= 2.7'
|
|
17
17
|
|
|
18
18
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
19
19
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
@@ -32,20 +32,26 @@ Gem::Specification.new do |spec|
|
|
|
32
32
|
|
|
33
33
|
spec.add_development_dependency 'bump'
|
|
34
34
|
spec.add_development_dependency 'bundler'
|
|
35
|
+
spec.add_development_dependency 'fakeweb'
|
|
35
36
|
spec.add_development_dependency 'mdl'
|
|
36
37
|
spec.add_development_dependency 'minitest-profile'
|
|
37
|
-
spec.add_development_dependency '
|
|
38
|
+
spec.add_development_dependency 'minitest-reporters'
|
|
39
|
+
spec.add_development_dependency 'mocha', ['~> 2.0.0.alpha.1']
|
|
38
40
|
# 0.58.0 and 0.57.0 don't seem super compatible with signatures, and
|
|
39
41
|
# magit doesn't seem to want to use the bundled version at the moment,
|
|
40
42
|
# so let's favor the more recent version...
|
|
41
43
|
spec.add_development_dependency 'overcommit', ['>=0.58.0']
|
|
42
44
|
spec.add_development_dependency 'pry'
|
|
43
45
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
44
|
-
spec.add_development_dependency '
|
|
46
|
+
spec.add_development_dependency 'rspec', '>=3.4'
|
|
47
|
+
spec.add_development_dependency 'rubocop', ['~> 1.36']
|
|
45
48
|
spec.add_development_dependency 'rubocop-minitest'
|
|
46
49
|
spec.add_development_dependency 'rubocop-rake'
|
|
47
50
|
# ensure version with branch coverage
|
|
48
51
|
spec.add_development_dependency 'simplecov', ['>=0.18.0']
|
|
49
52
|
spec.add_development_dependency 'simplecov-lcov'
|
|
50
53
|
spec.add_development_dependency 'undercover'
|
|
54
|
+
spec.metadata = {
|
|
55
|
+
'rubygems_mfa_required' => 'true',
|
|
56
|
+
}
|
|
51
57
|
end
|
data/coverage/.last_run.json
CHANGED
data/fix.sh
CHANGED
|
@@ -69,7 +69,18 @@ ensure_rbenv() {
|
|
|
69
69
|
|
|
70
70
|
latest_ruby_version() {
|
|
71
71
|
major_minor=${1}
|
|
72
|
-
|
|
72
|
+
|
|
73
|
+
# Double check that this command doesn't error out under -e
|
|
74
|
+
rbenv install --list >/dev/null 2>&1
|
|
75
|
+
|
|
76
|
+
# not sure why, but 'rbenv install --list' below exits with error code
|
|
77
|
+
# 1...after providing the same output the previous line gave when it
|
|
78
|
+
# exited with error code 0.
|
|
79
|
+
#
|
|
80
|
+
# https://github.com/rbenv/rbenv/issues/1441
|
|
81
|
+
set +e
|
|
82
|
+
rbenv install --list 2>/dev/null | cat | grep "^${major_minor}."
|
|
83
|
+
set -e
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
ensure_dev_library() {
|
|
@@ -91,12 +102,20 @@ ensure_ruby_build_requirements() {
|
|
|
91
102
|
ensure_dev_library openssl/ssl.h openssl libssl-dev
|
|
92
103
|
}
|
|
93
104
|
|
|
105
|
+
ensure_latest_ruby_build_definitions() {
|
|
106
|
+
ensure_rbenv
|
|
107
|
+
|
|
108
|
+
git -C "$(rbenv root)"/plugins/ruby-build pull
|
|
109
|
+
}
|
|
110
|
+
|
|
94
111
|
# You can find out which feature versions are still supported / have
|
|
95
112
|
# been release here: https://www.ruby-lang.org/en/downloads/
|
|
96
113
|
ensure_ruby_versions() {
|
|
114
|
+
ensure_latest_ruby_build_definitions
|
|
115
|
+
|
|
97
116
|
# You can find out which feature versions are still supported / have
|
|
98
117
|
# been release here: https://www.ruby-lang.org/en/downloads/
|
|
99
|
-
ruby_versions="$(latest_ruby_version 2.
|
|
118
|
+
ruby_versions="$(latest_ruby_version 2.7)"
|
|
100
119
|
|
|
101
120
|
echo "Latest Ruby versions: ${ruby_versions}"
|
|
102
121
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'custom_field_variant'
|
|
4
|
+
|
|
5
|
+
module Checkoff
|
|
6
|
+
module Internal
|
|
7
|
+
module SearchUrl
|
|
8
|
+
# Convert custom field parameters from an Asana search URL into
|
|
9
|
+
# API search arguments and Checkoff task selectors
|
|
10
|
+
class CustomFieldParamConverter
|
|
11
|
+
def initialize(custom_field_params:)
|
|
12
|
+
@custom_field_params = custom_field_params
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def convert
|
|
16
|
+
args = {}
|
|
17
|
+
task_selector = []
|
|
18
|
+
by_custom_field.each do |gid, single_custom_field_params|
|
|
19
|
+
new_args, new_task_selector = convert_single_custom_field_params(gid,
|
|
20
|
+
single_custom_field_params)
|
|
21
|
+
args, task_selector = merge_args_and_task_selectors(args, new_args,
|
|
22
|
+
task_selector, new_task_selector)
|
|
23
|
+
end
|
|
24
|
+
[args, task_selector]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def by_custom_field
|
|
30
|
+
custom_field_params.group_by do |key, _value|
|
|
31
|
+
gid_from_custom_field_key(key)
|
|
32
|
+
end.transform_values(&:to_h)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def merge_args_and_task_selectors(args, new_args, task_selector, new_task_selector)
|
|
36
|
+
args = args.merge(new_args)
|
|
37
|
+
return [args, task_selector] if new_task_selector == []
|
|
38
|
+
|
|
39
|
+
raise 'Teach me how to merge task selectors' unless task_selector == []
|
|
40
|
+
|
|
41
|
+
task_selector = new_task_selector
|
|
42
|
+
|
|
43
|
+
[args, task_selector]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
VARIANTS = {
|
|
47
|
+
'is' => CustomFieldVariant::Is,
|
|
48
|
+
'no_value' => CustomFieldVariant::NoValue,
|
|
49
|
+
'is_not' => CustomFieldVariant::IsNot,
|
|
50
|
+
'less_than' => CustomFieldVariant::LessThan,
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
def convert_single_custom_field_params(gid, single_custom_field_params)
|
|
54
|
+
variant_key = "custom_field_#{gid}.variant"
|
|
55
|
+
variant = single_custom_field_params.fetch(variant_key)
|
|
56
|
+
remaining_params = single_custom_field_params.reject { |k, _v| k == variant_key }
|
|
57
|
+
raise "Teach me how to handle #{variant_key} = #{variant}" unless variant.length == 1
|
|
58
|
+
|
|
59
|
+
variant_class = VARIANTS[variant[0]]
|
|
60
|
+
return variant_class.new(gid, remaining_params).convert unless variant_class.nil?
|
|
61
|
+
|
|
62
|
+
raise "Teach me how to handle #{variant_key} = #{variant}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def gid_from_custom_field_key(key)
|
|
66
|
+
key.split('_')[2].split('.')[0]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
attr_reader :custom_field_params
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Checkoff
|
|
4
|
+
module Internal
|
|
5
|
+
module SearchUrl
|
|
6
|
+
module CustomFieldVariant
|
|
7
|
+
# base class for handling different custom_field_#{gid}.variant params
|
|
8
|
+
class CustomFieldVariant
|
|
9
|
+
def initialize(gid, remaining_params)
|
|
10
|
+
@gid = gid
|
|
11
|
+
@remaining_params = remaining_params
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
attr_reader :gid, :remaining_params
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# custom_field_#{gid}.variant = 'less_than'
|
|
20
|
+
class LessThan < CustomFieldVariant
|
|
21
|
+
def convert
|
|
22
|
+
max_param = "custom_field_#{gid}.max"
|
|
23
|
+
case remaining_params.keys
|
|
24
|
+
when [max_param]
|
|
25
|
+
convert_single_custom_field_less_than_params_max_param(max_param)
|
|
26
|
+
else
|
|
27
|
+
raise "Teach me how to handle #{remaining_params}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def convert_single_custom_field_less_than_params_max_param(max_param)
|
|
34
|
+
max_values = remaining_params.fetch(max_param)
|
|
35
|
+
unless max_values.length == 1
|
|
36
|
+
raise "Teach me how to handle these remaining keys for #{max_param}: #{remaining_params}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
max_value = max_values[0]
|
|
40
|
+
empty_task_selector = []
|
|
41
|
+
[{ "custom_fields.#{gid}.less_than" => max_value }, empty_task_selector]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# custom_field_#{gid}.variant = 'is_not'
|
|
46
|
+
class IsNot < CustomFieldVariant
|
|
47
|
+
def convert
|
|
48
|
+
case remaining_params.keys
|
|
49
|
+
when ["custom_field_#{gid}.selected_options"]
|
|
50
|
+
convert_single_custom_field_is_not_params_selected_options
|
|
51
|
+
else
|
|
52
|
+
raise "Teach me how to handle #{remaining_params}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def convert_single_custom_field_is_not_params_selected_options
|
|
59
|
+
selected_options = remaining_params.fetch("custom_field_#{gid}.selected_options")
|
|
60
|
+
raise "Teach me how to handle #{remaining_params}" unless selected_options.length == 1
|
|
61
|
+
|
|
62
|
+
[{ "custom_fields.#{gid}.is_set" => 'true' },
|
|
63
|
+
['not',
|
|
64
|
+
['custom_field_gid_value_contains_any_gid',
|
|
65
|
+
gid,
|
|
66
|
+
selected_options.fetch(0).split('~')]]]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# custom_field_#{gid}.variant = 'no_value'
|
|
71
|
+
class NoValue < CustomFieldVariant
|
|
72
|
+
def convert
|
|
73
|
+
unless remaining_params.length.zero?
|
|
74
|
+
raise "Teach me how to handle these remaining keys for #{variant_key}: #{remaining_params}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
empty_task_selector = []
|
|
78
|
+
[{ "custom_fields.#{gid}.is_set" => 'false' }, empty_task_selector]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# custom_field_#{gid}.variant = 'is'
|
|
83
|
+
class Is < CustomFieldVariant
|
|
84
|
+
def convert
|
|
85
|
+
unless remaining_params.length == 1
|
|
86
|
+
raise "Teach me how to handle these remaining keys for #{variant_key}: #{remaining_params}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
key, values = remaining_params.to_a[0]
|
|
90
|
+
convert_custom_field_is_arg(key, values)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def convert_custom_field_is_arg(key, values)
|
|
96
|
+
empty_task_selector = []
|
|
97
|
+
|
|
98
|
+
if key.end_with? '.selected_options'
|
|
99
|
+
raise "Too many values found for #{key}: #{values}" if values.length != 1
|
|
100
|
+
|
|
101
|
+
return [{ "custom_fields.#{gid}.value" => values[0] },
|
|
102
|
+
empty_task_selector]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
raise "Teach me how to handle #{key} = #{values}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative 'simple_param_converter'
|
|
6
|
+
require_relative 'custom_field_param_converter'
|
|
7
|
+
|
|
8
|
+
module Checkoff
|
|
9
|
+
module Internal
|
|
10
|
+
module SearchUrl
|
|
11
|
+
# Parse Asana search URLs into parameters suitable to pass into
|
|
12
|
+
# the /workspaces/{workspace_gid}/tasks/search endpoint
|
|
13
|
+
class Parser
|
|
14
|
+
def initialize(_deps = {}); end
|
|
15
|
+
|
|
16
|
+
def convert_params(url)
|
|
17
|
+
url_params = CGI.parse(URI.parse(url).query)
|
|
18
|
+
custom_field_params, simple_url_params = partition_url_params(url_params)
|
|
19
|
+
custom_field_args, task_selector = convert_custom_field_params(custom_field_params)
|
|
20
|
+
simple_url_args = convert_simple_params(simple_url_params)
|
|
21
|
+
[custom_field_args.merge(simple_url_args), task_selector]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def convert_simple_params(simple_url_params)
|
|
27
|
+
SimpleParamConverter.new(simple_url_params: simple_url_params).convert
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def convert_custom_field_params(custom_field_params)
|
|
31
|
+
CustomFieldParamConverter.new(custom_field_params: custom_field_params).convert
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def partition_url_params(url_params)
|
|
35
|
+
url_params.to_a.partition do |key, _values|
|
|
36
|
+
key.start_with? 'custom_field_'
|
|
37
|
+
end.map(&:to_h)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'custom_field_param_converter'
|
|
4
|
+
|
|
5
|
+
module Checkoff
|
|
6
|
+
module Internal
|
|
7
|
+
module SearchUrl
|
|
8
|
+
module SimpleParam
|
|
9
|
+
# base class for handling different types of search url params
|
|
10
|
+
class SimpleParam
|
|
11
|
+
def initialize(values:)
|
|
12
|
+
@values = values
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :values
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Handle 'any_projects.ids' search url param
|
|
21
|
+
class AnyProjectsIds < SimpleParam
|
|
22
|
+
def convert
|
|
23
|
+
['projects.any', values.join(',')]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Handle 'completion' search url param
|
|
28
|
+
class Completion < SimpleParam
|
|
29
|
+
def convert
|
|
30
|
+
raise "Teach me how to handle #{key} = #{values}" if values.length != 1
|
|
31
|
+
|
|
32
|
+
value = values.fetch(0)
|
|
33
|
+
raise "Teach me how to handle #{key} = #{values}" if value != 'incomplete'
|
|
34
|
+
|
|
35
|
+
['completed', false]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Handle 'not_tags.ids' search url param
|
|
40
|
+
class NotTagsIds < SimpleParam
|
|
41
|
+
def convert
|
|
42
|
+
raise "Teach me how to handle #{key} = #{values}" if values.length != 1
|
|
43
|
+
|
|
44
|
+
value = values.fetch(0)
|
|
45
|
+
tag_ids = value.split('~')
|
|
46
|
+
['tags.not', tag_ids.join(',')]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Convert simple parameters - ones where the param name itself
|
|
52
|
+
# doesn't encode any parameters'
|
|
53
|
+
class SimpleParamConverter
|
|
54
|
+
def initialize(simple_url_params:)
|
|
55
|
+
@simple_url_params = simple_url_params
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def convert
|
|
59
|
+
simple_url_params.to_a.to_h do |key, values|
|
|
60
|
+
convert_arg(key, values)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
ARGS = {
|
|
67
|
+
'any_projects.ids' => SimpleParam::AnyProjectsIds,
|
|
68
|
+
'completion' => SimpleParam::Completion,
|
|
69
|
+
'not_tags.ids' => SimpleParam::NotTagsIds,
|
|
70
|
+
}.freeze
|
|
71
|
+
|
|
72
|
+
# https://developers.asana.com/docs/search-tasks-in-a-workspace
|
|
73
|
+
def convert_arg(key, values)
|
|
74
|
+
clazz = ARGS.fetch(key)
|
|
75
|
+
clazz.new(values: values).convert
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
attr_reader :simple_url_params
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/checkoff/subtasks.rb
CHANGED
|
@@ -41,7 +41,7 @@ module Checkoff
|
|
|
41
41
|
def raw_subtasks(task)
|
|
42
42
|
task_options = projects.task_options
|
|
43
43
|
task_options[:options][:fields] << 'is_rendered_as_separator'
|
|
44
|
-
task.subtasks(task_options)
|
|
44
|
+
task.subtasks(**task_options)
|
|
45
45
|
end
|
|
46
46
|
cache_method :raw_subtasks, SHORT_CACHE_TIME
|
|
47
47
|
|
|
@@ -12,7 +12,7 @@ require_relative 'task_selectors'
|
|
|
12
12
|
require 'asana/resource_includes/collection'
|
|
13
13
|
require 'asana/resource_includes/response_helper'
|
|
14
14
|
|
|
15
|
-
require 'checkoff/internal/
|
|
15
|
+
require 'checkoff/internal/search_url'
|
|
16
16
|
|
|
17
17
|
# https://developers.asana.com/docs/task-searches
|
|
18
18
|
module Checkoff
|
|
@@ -33,7 +33,7 @@ module Checkoff
|
|
|
33
33
|
projects: Checkoff::Projects.new(config: config),
|
|
34
34
|
clients: Checkoff::Clients.new(config: config),
|
|
35
35
|
client: clients.client,
|
|
36
|
-
search_url_parser: Checkoff::Internal::
|
|
36
|
+
search_url_parser: Checkoff::Internal::SearchUrl::Parser.new,
|
|
37
37
|
asana_resources_collection_class: Asana::Resources::Collection)
|
|
38
38
|
@workspaces = workspaces
|
|
39
39
|
@task_selectors = task_selectors
|
data/lib/checkoff/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: checkoff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vince Broz
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-10-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -108,6 +108,20 @@ dependencies:
|
|
|
108
108
|
- - ">="
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: fakeweb
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
111
125
|
- !ruby/object:Gem::Dependency
|
|
112
126
|
name: mdl
|
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -137,7 +151,7 @@ dependencies:
|
|
|
137
151
|
- !ruby/object:Gem::Version
|
|
138
152
|
version: '0'
|
|
139
153
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name:
|
|
154
|
+
name: minitest-reporters
|
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
|
142
156
|
requirements:
|
|
143
157
|
- - ">="
|
|
@@ -150,6 +164,20 @@ dependencies:
|
|
|
150
164
|
- - ">="
|
|
151
165
|
- !ruby/object:Gem::Version
|
|
152
166
|
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: mocha
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - "~>"
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: 2.0.0.alpha.1
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: 2.0.0.alpha.1
|
|
153
181
|
- !ruby/object:Gem::Dependency
|
|
154
182
|
name: overcommit
|
|
155
183
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -193,19 +221,33 @@ dependencies:
|
|
|
193
221
|
- !ruby/object:Gem::Version
|
|
194
222
|
version: '13.0'
|
|
195
223
|
- !ruby/object:Gem::Dependency
|
|
196
|
-
name:
|
|
224
|
+
name: rspec
|
|
197
225
|
requirement: !ruby/object:Gem::Requirement
|
|
198
226
|
requirements:
|
|
199
227
|
- - ">="
|
|
200
228
|
- !ruby/object:Gem::Version
|
|
201
|
-
version: '
|
|
229
|
+
version: '3.4'
|
|
202
230
|
type: :development
|
|
203
231
|
prerelease: false
|
|
204
232
|
version_requirements: !ruby/object:Gem::Requirement
|
|
205
233
|
requirements:
|
|
206
234
|
- - ">="
|
|
207
235
|
- !ruby/object:Gem::Version
|
|
208
|
-
version: '
|
|
236
|
+
version: '3.4'
|
|
237
|
+
- !ruby/object:Gem::Dependency
|
|
238
|
+
name: rubocop
|
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
|
240
|
+
requirements:
|
|
241
|
+
- - "~>"
|
|
242
|
+
- !ruby/object:Gem::Version
|
|
243
|
+
version: '1.36'
|
|
244
|
+
type: :development
|
|
245
|
+
prerelease: false
|
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
247
|
+
requirements:
|
|
248
|
+
- - "~>"
|
|
249
|
+
- !ruby/object:Gem::Version
|
|
250
|
+
version: '1.36'
|
|
209
251
|
- !ruby/object:Gem::Dependency
|
|
210
252
|
name: rubocop-minitest
|
|
211
253
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -322,7 +364,11 @@ files:
|
|
|
322
364
|
- lib/checkoff/custom_fields.rb
|
|
323
365
|
- lib/checkoff/internal/config_loader.rb
|
|
324
366
|
- lib/checkoff/internal/create-class.sh
|
|
325
|
-
- lib/checkoff/internal/
|
|
367
|
+
- lib/checkoff/internal/search_url.rb
|
|
368
|
+
- lib/checkoff/internal/search_url/custom_field_param_converter.rb
|
|
369
|
+
- lib/checkoff/internal/search_url/custom_field_variant.rb
|
|
370
|
+
- lib/checkoff/internal/search_url/parser.rb
|
|
371
|
+
- lib/checkoff/internal/search_url/simple_param_converter.rb
|
|
326
372
|
- lib/checkoff/internal/task_selector_evaluator.rb
|
|
327
373
|
- lib/checkoff/my_tasks.rb
|
|
328
374
|
- lib/checkoff/projects.rb
|
|
@@ -366,7 +412,8 @@ files:
|
|
|
366
412
|
homepage: https://github.com/apiology/checkoff
|
|
367
413
|
licenses:
|
|
368
414
|
- MIT license
|
|
369
|
-
metadata:
|
|
415
|
+
metadata:
|
|
416
|
+
rubygems_mfa_required: 'true'
|
|
370
417
|
post_install_message:
|
|
371
418
|
rdoc_options: []
|
|
372
419
|
require_paths:
|
|
@@ -375,14 +422,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
375
422
|
requirements:
|
|
376
423
|
- - ">="
|
|
377
424
|
- !ruby/object:Gem::Version
|
|
378
|
-
version: '2.
|
|
425
|
+
version: '2.7'
|
|
379
426
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
380
427
|
requirements:
|
|
381
428
|
- - ">="
|
|
382
429
|
- !ruby/object:Gem::Version
|
|
383
430
|
version: '0'
|
|
384
431
|
requirements: []
|
|
385
|
-
rubygems_version: 3.
|
|
432
|
+
rubygems_version: 3.2.33
|
|
386
433
|
signing_key:
|
|
387
434
|
specification_version: 4
|
|
388
435
|
summary: Command-line and gem client for Asana (unofficial)
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
# frozen_string_literal: true
|
|
4
|
-
|
|
5
|
-
module Checkoff
|
|
6
|
-
module Internal
|
|
7
|
-
# base class for handling different custom_field_#{gid}.variant params
|
|
8
|
-
class CustomFieldVariant
|
|
9
|
-
def initialize(gid, remaining_params)
|
|
10
|
-
@gid = gid
|
|
11
|
-
@remaining_params = remaining_params
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
attr_reader :gid, :remaining_params
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# custom_field_#{gid}.variant = 'less_than'
|
|
20
|
-
class LessThanCustomFieldVariant < CustomFieldVariant
|
|
21
|
-
def convert
|
|
22
|
-
max_param = "custom_field_#{gid}.max"
|
|
23
|
-
case remaining_params.keys
|
|
24
|
-
when [max_param]
|
|
25
|
-
convert_single_custom_field_less_than_params_max_param(max_param)
|
|
26
|
-
else
|
|
27
|
-
raise "Teach me how to handle #{remaining_params}"
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def convert_single_custom_field_less_than_params_max_param(max_param)
|
|
34
|
-
max_values = remaining_params.fetch(max_param)
|
|
35
|
-
unless max_values.length == 1
|
|
36
|
-
raise "Teach me how to handle these remaining keys for #{max_param}: #{remaining_params}"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
max_value = max_values[0]
|
|
40
|
-
empty_task_selector = []
|
|
41
|
-
[{ "custom_fields.#{gid}.less_than" => max_value }, empty_task_selector]
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# custom_field_#{gid}.variant = 'is_not'
|
|
46
|
-
class IsNotCustomFieldVariant < CustomFieldVariant
|
|
47
|
-
def convert
|
|
48
|
-
case remaining_params.keys
|
|
49
|
-
when ["custom_field_#{gid}.selected_options"]
|
|
50
|
-
convert_single_custom_field_is_not_params_selected_options
|
|
51
|
-
else
|
|
52
|
-
raise "Teach me how to handle #{remaining_params}"
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
def convert_single_custom_field_is_not_params_selected_options
|
|
59
|
-
selected_options = remaining_params.fetch("custom_field_#{gid}.selected_options")
|
|
60
|
-
raise "Teach me how to handle #{remaining_params}" unless selected_options.length == 1
|
|
61
|
-
|
|
62
|
-
[{ "custom_fields.#{gid}.is_set" => 'true' },
|
|
63
|
-
['not',
|
|
64
|
-
['custom_field_gid_value_contains_any_gid',
|
|
65
|
-
gid,
|
|
66
|
-
selected_options.fetch(0).split('~')]]]
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# custom_field_#{gid}.variant = 'no_value'
|
|
71
|
-
class NoValueCustomFieldVariant < CustomFieldVariant
|
|
72
|
-
def convert
|
|
73
|
-
unless remaining_params.length.zero?
|
|
74
|
-
raise "Teach me how to handle these remaining keys for #{variant_key}: #{remaining_params}"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
empty_task_selector = []
|
|
78
|
-
[{ "custom_fields.#{gid}.is_set" => 'false' }, empty_task_selector]
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# custom_field_#{gid}.variant = 'is'
|
|
83
|
-
class IsCustomFieldVariant < CustomFieldVariant
|
|
84
|
-
def convert
|
|
85
|
-
unless remaining_params.length == 1
|
|
86
|
-
raise "Teach me how to handle these remaining keys for #{variant_key}: #{remaining_params}"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
key, values = remaining_params.to_a[0]
|
|
90
|
-
convert_custom_field_is_arg(key, values)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
private
|
|
94
|
-
|
|
95
|
-
def convert_custom_field_is_arg(key, values)
|
|
96
|
-
empty_task_selector = []
|
|
97
|
-
|
|
98
|
-
if key.end_with? '.selected_options'
|
|
99
|
-
raise "Too many values found for #{key}: #{values}" if values.length != 1
|
|
100
|
-
|
|
101
|
-
return [{ "custom_fields.#{gid}.value" => values[0] },
|
|
102
|
-
empty_task_selector]
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
raise "Teach me how to handle #{key} = #{values}"
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Convert custom field parameters from an Asana search URL into
|
|
110
|
-
# API search arguments and Checkoff task selectors
|
|
111
|
-
class CustomFieldParamConverter
|
|
112
|
-
def initialize(custom_field_params:)
|
|
113
|
-
@custom_field_params = custom_field_params
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def convert
|
|
117
|
-
args = {}
|
|
118
|
-
task_selector = []
|
|
119
|
-
by_custom_field.each do |gid, single_custom_field_params|
|
|
120
|
-
new_args, new_task_selector = convert_single_custom_field_params(gid,
|
|
121
|
-
single_custom_field_params)
|
|
122
|
-
args, task_selector = merge_args_and_task_selectors(args, new_args,
|
|
123
|
-
task_selector, new_task_selector)
|
|
124
|
-
end
|
|
125
|
-
[args, task_selector]
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
private
|
|
129
|
-
|
|
130
|
-
def by_custom_field
|
|
131
|
-
custom_field_params.group_by do |key, _value|
|
|
132
|
-
gid_from_custom_field_key(key)
|
|
133
|
-
end.transform_values(&:to_h)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def merge_args_and_task_selectors(args, new_args, task_selector, new_task_selector)
|
|
137
|
-
args = args.merge(new_args)
|
|
138
|
-
return [args, task_selector] if new_task_selector == []
|
|
139
|
-
|
|
140
|
-
raise 'Teach me how to merge task selectors' unless task_selector == []
|
|
141
|
-
|
|
142
|
-
task_selector = new_task_selector
|
|
143
|
-
|
|
144
|
-
[args, task_selector]
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
VARIANTS = {
|
|
148
|
-
'is' => IsCustomFieldVariant,
|
|
149
|
-
'no_value' => NoValueCustomFieldVariant,
|
|
150
|
-
'is_not' => IsNotCustomFieldVariant,
|
|
151
|
-
'less_than' => LessThanCustomFieldVariant,
|
|
152
|
-
}.freeze
|
|
153
|
-
|
|
154
|
-
def convert_single_custom_field_params(gid, single_custom_field_params)
|
|
155
|
-
variant_key = "custom_field_#{gid}.variant"
|
|
156
|
-
variant = single_custom_field_params.fetch(variant_key)
|
|
157
|
-
remaining_params = single_custom_field_params.reject { |k, _v| k == variant_key }
|
|
158
|
-
raise "Teach me how to handle #{variant_key} = #{variant}" unless variant.length == 1
|
|
159
|
-
|
|
160
|
-
variant_class = VARIANTS[variant[0]]
|
|
161
|
-
return variant_class.new(gid, remaining_params).convert unless variant_class.nil?
|
|
162
|
-
|
|
163
|
-
raise "Teach me how to handle #{variant_key} = #{variant}"
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def gid_from_custom_field_key(key)
|
|
167
|
-
key.split('_')[2].split('.')[0]
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
attr_reader :custom_field_params
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# Parse Asana search URLs into parameters suitable to pass into
|
|
174
|
-
# the /workspaces/{workspace_gid}/tasks/search endpoint
|
|
175
|
-
class SearchUrlParser
|
|
176
|
-
def initialize(_deps = {}); end
|
|
177
|
-
|
|
178
|
-
def convert_params(url)
|
|
179
|
-
url_params = CGI.parse(URI.parse(url).query)
|
|
180
|
-
custom_field_params, regular_url_params = partition_url_params(url_params)
|
|
181
|
-
custom_field_args, task_selector = convert_custom_field_params(custom_field_params)
|
|
182
|
-
regular_url_args = convert_regular_params(regular_url_params)
|
|
183
|
-
[custom_field_args.merge(regular_url_args), task_selector]
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
private
|
|
187
|
-
|
|
188
|
-
def convert_regular_params(regular_url_params)
|
|
189
|
-
regular_url_params.to_a.map do |key, values|
|
|
190
|
-
convert_arg(key, values)
|
|
191
|
-
end.to_h
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def convert_custom_field_params(custom_field_params)
|
|
195
|
-
CustomFieldParamConverter.new(custom_field_params: custom_field_params).convert
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def partition_url_params(url_params)
|
|
199
|
-
url_params.to_a.partition do |key, _values|
|
|
200
|
-
key.start_with? 'custom_field_'
|
|
201
|
-
end.map(&:to_h)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
# https://developers.asana.com/docs/search-tasks-in-a-workspace
|
|
205
|
-
def convert_arg(key, values)
|
|
206
|
-
case key
|
|
207
|
-
when 'any_projects.ids'
|
|
208
|
-
['projects.any', values.join(',')]
|
|
209
|
-
when 'completion'
|
|
210
|
-
raise "Teach me how to handle #{key} = #{values}" if values.length != 1
|
|
211
|
-
|
|
212
|
-
value = values.fetch(0)
|
|
213
|
-
raise "Teach me how to handle #{key} = #{values}" if value != 'incomplete'
|
|
214
|
-
|
|
215
|
-
['completed', false]
|
|
216
|
-
when 'not_tags.ids'
|
|
217
|
-
raise "Teach me how to handle #{key} = #{values}" if values.length != 1
|
|
218
|
-
|
|
219
|
-
value = values.fetch(0)
|
|
220
|
-
tag_ids = value.split('~')
|
|
221
|
-
['tags.not', tag_ids.join(',')]
|
|
222
|
-
else
|
|
223
|
-
raise "Teach me how to handle #{key} = #{values}"
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
end
|