watson-ruby 1.5.1 → 1.6.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
  SHA1:
3
- metadata.gz: 2cb1804dead00fc6ed4ccfc616c4551473fa2376
4
- data.tar.gz: 0a56d91f93d36ae4ed03cfec5a3cd0e27779400f
3
+ metadata.gz: 91ffcd1dd3152a9e63f14d03f55f7c7ea50e7e95
4
+ data.tar.gz: f799be3066226f1f7ccae68dc1a2502325817a6c
5
5
  SHA512:
6
- metadata.gz: 1ccdce1f3dd140fc82a608c69a0a0ab12b2d7e9750efb6f7582230f8f6567f52e9720965224131e328342b6975a1e3f36846525d5c9c45f8f8ba2ac487e595cd
7
- data.tar.gz: 5291bd22fa3a29657864492e90e24404fa0a968e783866092ec5406dd073cceef991a5b956b83c226b23d55ad7cc526683076062f7b1b9bafe085f41186e6c76
6
+ metadata.gz: 8c8be18c25738d0c92401834d3adb7259356c98f4c6422e67a06ce4a15638865b1aada5e8be03c38110957d986c3e6ba31d3acb617892fe6dcb44d00c818781e
7
+ data.tar.gz: 0270accff9246e278d2e89ea71a5823a21e6771dc14e4d523d96b9bd6e6d2d5b645816b73303c649a46282f4c2f098924af2a4a215da1f6ae8c3cde353a49212
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- watson-ruby (1.5.1)
4
+ watson-ruby (1.6.0)
5
5
  json
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- # watson-ruby [![Build Status](https://travis-ci.org/nhmood/watson-ruby.png?branch=master)](https://travis-ci.org/nhmood/watson-ruby)
1
+ # watson-ruby [![Gem Version](https://badge.fury.io/rb/watson-ruby.png)](http://badge.fury.io/rb/watson-ruby) [![Build Status](https://travis-ci.org/nhmood/watson-ruby.png?branch=master)](https://travis-ci.org/nhmood/watson-ruby)
2
+
2
3
  ### an inline issue manager
3
4
  [watson](http://goosecode.com/watson) ([mirror](http://nhmood.github.io/watson-ruby)) is a tool for creating and tracking bug reports, issues, and internal notes in code.
4
5
  It is avaliable in two flavors, [watson-ruby](http://github.com/nhmood/watson-ruby) and [watson-perl](http://github.com/nhmood/watson-perl)
@@ -27,6 +28,7 @@ bundle exec rake
27
28
 
28
29
 
29
30
  ## Recent Updates
31
+ - watson now supports [Asana](http://asana.com)
30
32
  - watson now supports [GitLab](http://gitlab.com)
31
33
  - [vim-unite-watson.vim](https://github.com/alpaca-tc/vim-unite-watson.vim) integrates watson right into vim!
32
34
  - watson now supports multiple export modes
@@ -63,6 +65,10 @@ submit a pull request (comment parsing happens in **lib/watson/paser.rb**)
63
65
  - **Python**
64
66
  - **Coffeescript**
65
67
  - **Clojure**
68
+ - **VimL**
69
+ - **Markdown**
70
+ - **HTML**
71
+ - **Emacslisp**
66
72
 
67
73
 
68
74
  ## Command line arguments
@@ -80,7 +86,7 @@ If no RC file exists, default RC file will be created
80
86
  -i, --ignore list of files, directories, or types to ignore
81
87
  -p, --parse-depth depth to recursively parse directories
82
88
  -r, --remote list / create tokens for remote posting
83
- [github, bitbucket, gitlab]
89
+ [github, bitbucket, gitlab, asana]
84
90
  -s, --show filter results (files listed) based on issue status
85
91
  [all, clean, dirty]
86
92
  -t, --tags list of tags to search for
@@ -111,6 +117,13 @@ It should be followed by a space separated list of directories that should be pa
111
117
  If watson is run without this parameter, the current directory is parsed.
112
118
 
113
119
 
120
+ ### --debug [CLASS]
121
+ This parameter enables debug prints for the specified class.
122
+ It should be followed by a space separated list of classes that should print debug messages.
123
+ The list of classes are found in `lib/watson`.
124
+ If passed without arguments, debug prints in **ALL** classes of watson will be enabled.
125
+
126
+
114
127
  ### -f, --files [FILES]
115
128
  This parameter specifies which files watson should parse through.
116
129
  It should be followed by a space separated list of files that should be parsed.
@@ -149,10 +162,10 @@ If individual directories are passed with the -d (--dirs) flag, each will be par
149
162
  If watson is run without this parameter, the parsing depth is unlimited and will search through all subdirectories found.
150
163
 
151
164
 
152
- ### -r, --remote [GITHUB, BITBUCKET, GITLAB]
165
+ ### -r, --remote [GITHUB, BITBUCKET, GITLAB, ASANA]
153
166
  This parameter is used to both list currently established remotes as well as setup new ones.
154
167
  If passed without any options, the currently established remotes will be listed.
155
- If passed with a github, bitbucket, gitlab argument, watson will proceed to ask some questions to set up the corresponding remote.
168
+ If passed with a github, bitbucket, gitlab, asana argument, watson will proceed to ask some questions to set up the corresponding remote.
156
169
 
157
170
 
158
171
  ### -s, --show [ALL, CLEAN, DIRTY]
@@ -180,9 +193,13 @@ This parameter displays the current version of watson you are running.
180
193
  ## .watsonrc
181
194
  watson supports an RC file that allows for reusing commong settings without repeating command line arguments every time.
182
195
 
183
- The .watsonrc is placed in every directory that watson is run from as opposed to a unified file (in ~/.watsonrc for example). The thinking behind this is that each project may have a different set of folders to ignore, directories to search through, and tags to look for.
196
+ The .watsonrc is placed in every directory that watson is run from as opposed to a unified file (in ~/.watsonrc for example). The thought process behind this is that each project may have a different set of folders to ignore, directories to search through, and tags to look for.
184
197
  For example, a C/C++ project might want to look in src/ and ignore obj/ whereas a Ruby project might want to look in lib/ and ignore assets/.
185
198
 
199
+ A base `.watsonrc` is created in the users `$HOME` directory and used as the template for creating all subsequent config files.
200
+ Any changes made in the `$HOME/.watsonrc` will carry on towards new configs, but will **not** update previously created configs.
201
+ The `$HOME/.watsonrc` is directly copied, not merged, as to avoid confusion as to the source of config parameters.
202
+
186
203
  The .watsonrc file is fairly straightforward...
187
204
  **[dirs]** - This is a newline separated list of directories to look in while parsing.
188
205
 
@@ -191,15 +208,27 @@ The .watsonrc file is fairly straightforward...
191
208
  **[ignore]** - This is a newline separated list of files / folders to ignore while parsing.
192
209
  This supports wildcard type selecting by providing .filename (no * required)
193
210
 
194
- **[context_depth]** - This value determines how many lines of context should be grabbed for each issue when posting to a remote.
211
+ **[context_depth]** - This value determines how many lines of context should be grabbed for each issue when posting to a remote.
195
212
 
196
- **[(github/bitbucket/gitlab)_api]** - If a remote is established, the API key for the corresponding remote is stored here.
213
+ **[type]** - This field allows for custom filetype/comment associations without having to edit the gem itself.
214
+ The format of filetype entries should be
215
+ ```
216
+ ".type" => ["comment1", "comment2"]
217
+ ".nhmood" => ["@@", "***"]
218
+ ".cc" => ["//"]
219
+ ```
220
+
221
+ **[(github/bitbucket/gitlab/asana)_api]** - If a remote is established, the API key for the corresponding remote is stored here.
197
222
  Currently, OAuth has yet to be implemented for Bitbucket so the Bitbucket username is stored here.
198
223
 
199
224
  **[(github/bitbucket/gitlab)_repo]** - The repo name / path is stored here.
200
225
 
201
226
  **[(github/gitlab)_endpoint]** - The endpoint in case of GitHub Enterprise of GitLab configuration.
202
227
 
228
+ **[asana_project]** - The Asana project in case of Asana integration is stored here.
229
+
230
+ **[asana_workspace]** - The Asana workspace in case of Asana integration is stored here.
231
+
203
232
  The remote related .watsonrc options shouldn't need to be edited manually, as they are automatically populated when the -r, --remote setup is called.
204
233
 
205
234
  ## unite.vim
@@ -222,7 +251,7 @@ Special thanks to [@eugenekolo](http://twitter.com/eugenekolo) [[email](eugenek@
222
251
  Special thanks to [@crowell](http://github.com/crowell) for testing out watson-ruby!
223
252
 
224
253
  ## FAQ
225
- - ** Will inline issues get deleted if I close them on GitHub/Bitbucket/GitLab?**
254
+ - **Will inline issues get deleted if I close them on GitHub/Bitbucket/GitLab?**
226
255
  No, watson won't touch your source code, it will only inform you that issues have been closed remotely.
227
256
 
228
257
  - **Why Ruby?**
data/lib/watson.rb CHANGED
@@ -8,6 +8,7 @@ require_relative 'watson/remote'
8
8
  require_relative 'watson/github'
9
9
  require_relative 'watson/bitbucket'
10
10
  require_relative 'watson/gitlab'
11
+ require_relative 'watson/asana'
11
12
  require_relative 'watson/version'
12
13
 
13
14
  module Watson
@@ -28,13 +29,14 @@ module Watson
28
29
  # [todo] - Replace Identify line in each method with method_added call
29
30
  # http://ruby-doc.org/core-2.0.0/Module.html#method-i-method_added
30
31
 
31
- # Separate ON and OFF so we can force state and still let
32
- # individual classes have some control over their prints
33
32
 
34
- # Global flag to turn ON debugging across all files
35
- GLOBAL_DEBUG_ON = false
36
- # Gllobal flag to turn OFF debugging across all files
37
- GLOBAL_DEBUG_OFF = false
33
+ # Module container for debug mode (which classes to debug print)
34
+ # [review] - This doesn't seem like the right place to put this
35
+ class << self
36
+ attr_accessor :debug_mode
37
+ @@debug_mode = Array.new()
38
+ end
39
+
38
40
 
39
41
  # [review] - Not sure if module_function is proper way to scope
40
42
  # I want to be able to call debug_print without having to use the scope
@@ -46,18 +48,24 @@ module Watson
46
48
  ###########################################################
47
49
  # Global debug print that prints based on local file DEBUG flag as well as GLOBAL debug flag
48
50
  def debug_print(msg)
49
- # [todo] - If input msg is a Hash, use pp to dump it
50
51
 
51
- # Print only if DEBUG flag of calling class is true OR
52
- # GLOBAL_DEBUG_ON of Watson module (defined above) is true
53
- # AND GLOBAL_DEBUG_OFF of Watson module (Defined above) is false
52
+ # If nothing set from CLI, debug_mode will be nil
53
+ return if Watson.debug_mode.nil?
54
+
55
+ # If empty, just --debug passed, print ALL, else selective print
56
+ _enabled = false
57
+ if !Watson.debug_mode.empty?
58
+ _debug = (self.is_a? Class) ? self.name.downcase : self.class.name.downcase
59
+ Watson.debug_mode.each do |dbg|
60
+ _enabled = true if _debug.include?(dbg)
61
+ end
62
+ else
63
+ _enabled = true
64
+ end
54
65
 
55
- # Sometimes we call debug_print from a static method (class << self)
56
- # and other times from a class method, and ::DEBUG is accessed differently
57
- # from a class vs object, so lets take care of that
58
- _DEBUG = (self.is_a? Class) ? self::DEBUG : self.class::DEBUG
66
+ return if !_enabled
67
+ (msg.is_a? Hash) ? (pp msg) : (print "=> #{msg}")
59
68
 
60
- print "=> #{msg}" if ( (_DEBUG == true || GLOBAL_DEBUG_ON == true) && (GLOBAL_DEBUG_OFF == false))
61
69
  end
62
70
 
63
71
 
@@ -0,0 +1,619 @@
1
+ module Watson
2
+
3
+ class Remote
4
+
5
+ class Asana
6
+
7
+ @end_point = "https://app.asana.com/api/1.0"
8
+ @formatter = nil
9
+
10
+ # Debug printing for this class
11
+ DEBUG = true
12
+
13
+ class << self
14
+
15
+ # [todo] - post issue to diff. project in same workspace depending on config?
16
+
17
+ # Include for debug_print
18
+ include Watson
19
+
20
+ #############################################################################
21
+ # Setup remote access to Asana
22
+ # Get API Key, Workspace, Project
23
+ def setup(config)
24
+
25
+ @formatter = Printer.new(config).build_formatter
26
+
27
+ # Identify method entry
28
+ debug_print "#{ self.class } : #{ __method__ }\n"
29
+
30
+ @formatter.print_status "+", GREEN
31
+ print BOLD + "Setting up Asana...\n" + RESET
32
+
33
+ config_exists = config.asana_api.empty? && config.asana_workspace.empty? && config.asana_project.empty?
34
+ unless config_exists
35
+ @formatter.print_status "!", RED
36
+ print BOLD + "Previous Asana API is in RC, are you sure you want to overwrite?\n" + RESET
37
+ print " (Y)es/(N)o: "
38
+
39
+ # Get user input
40
+ _overwrite = $stdin.gets.chomp
41
+ if ["no", "n"].include?(_overwrite.downcase)
42
+ print "\n\n"
43
+ @formatter.print_status "x", RED
44
+ print BOLD + "Not overwriting current Asana config\n" + RESET
45
+ return false
46
+ end
47
+ end
48
+
49
+ @formatter.print_status "!", YELLOW
50
+ print BOLD + "Asana API key required to make/update issues\n" + RESET
51
+ print " See help or README for more details on Asana access\n\n"
52
+
53
+ print "\n"
54
+
55
+ print BOLD + "API Key: " + RESET
56
+ _api_key = $stdin.gets.chomp
57
+ if _api_key.empty?
58
+ @formatter.print_status "x", RED
59
+ print BOLD + "Input blank. Please enter your api key!\n\n" + RESET
60
+ return false
61
+ end
62
+
63
+ print BOLD + "Workspace: " + RESET
64
+ _workspace = $stdin.gets.chomp
65
+ if _workspace.empty?
66
+ @formatter.print_status "x", RED
67
+ print BOLD + "Input blank. Please enter your workspace!\n\n" + RESET
68
+ return false
69
+ end
70
+
71
+ print BOLD + "Project: " + RESET
72
+ _project = $stdin.gets.chomp
73
+ if _project.empty?
74
+ @formatter.print_status "x", RED
75
+ print BOLD + "Input blank. Please enter your project!\n\n" + RESET
76
+ return false
77
+ end
78
+
79
+ workspace_dict = get_workspaces(_api_key)
80
+
81
+ unless workspace_dict
82
+ @formatter.print_status "x", RED
83
+ print BOLD + "Asana setup failed\n" + RESET
84
+ return false
85
+ end
86
+
87
+ unless workspace_dict.include?(_workspace)
88
+ print "\n"
89
+ @formatter.print_status "x", RED
90
+ print BOLD + "Workspace doesn't exist\n" + RESET
91
+ print " Check that the workspace name is correct\n"
92
+ print " Possible workspaces: '#{ workspace_dict.keys.join('\', \'') }'\n\n"
93
+ return false
94
+ end
95
+
96
+ workspace_id = workspace_dict[_workspace]
97
+ project_dict = get_projects(_api_key, workspace_id)
98
+
99
+ unless project_dict
100
+ @formatter.print_status "x", RED
101
+ print BOLD + "Asana API Request failed\n" + RESET
102
+ return false
103
+ end
104
+
105
+ unless project_dict.include?(_project)
106
+ print "\n"
107
+ @formatter.print_status "x", RED
108
+ print BOLD + "Project doesn't exist within workspace '#{ _workspace }'\n" + RESET
109
+ print " Check that the project name is correct\n"
110
+ print " Possible projects: '#{ project_dict.keys.join('\', \'') }'\n\n"
111
+ return false
112
+ end
113
+
114
+ config.asana_api = _api_key
115
+ debug_print "Asana API Key updated to: #{ config.asana_api }\n"
116
+ config.asana_workspace = _workspace
117
+ debug_print "Asana Workspace updated to: #{ config.asana_workspace }\n"
118
+ config.asana_project = _project
119
+ debug_print "Asana Project updated to: #{ config.asana_project }\n"
120
+
121
+ # All setup has been completed, need to update RC
122
+ # Call config updater/writer from @config to write config
123
+ debug_print "Updating config with new Asana info\n"
124
+ config.update_conf("asana_api", "asana_workspace", "asana_project")
125
+
126
+ # Give user some info
127
+ print "\n"
128
+ @formatter.print_status "o", GREEN
129
+ print BOLD + "Asana successfully setup\n" + RESET
130
+ print " Issues will now automatically be retrieved from Asana by default\n"
131
+ print " Use -u, --update to post issues to Asana\n"
132
+ print " See help or README for more details on GitHub/Bitbucket/Asana access\n\n"
133
+
134
+ true
135
+
136
+ end
137
+
138
+ ###########################################################
139
+ # Post given issue to Asana project
140
+ def post_issue(issue, config)
141
+
142
+ # Identify method entry
143
+ debug_print "#{ self.class } : #{ __method__ }\n"
144
+
145
+ # Set up formatter for printing errors
146
+ # config.output_format should be set based on less status by now
147
+ @formatter = Printer.new(config).build_formatter
148
+
149
+ # Only attempt to get issues if API is specified
150
+ if config.asana_api.empty?
151
+ debug_print "No asana API found, this shouldn't be called...\n"
152
+ return false
153
+ end
154
+
155
+ return false if config.asana_issues.key?(issue[:md5])
156
+ debug_print "#{issue[:md5]} not found in remote issues, posting\n"
157
+
158
+ _api_key = config.asana_api
159
+ _workspace = config.asana_workspace
160
+ _project = config.asana_project
161
+
162
+ workspace_id = get_workspace_id(_api_key, _workspace)
163
+
164
+ unless workspace_id
165
+ @formatter.print_status "x", RED
166
+ print BOLD + "Unable to get workspace info from Asana API\n" + RESET
167
+ return false
168
+ end
169
+
170
+ tags = init_tags(config, _api_key, workspace_id)
171
+
172
+ unless tags
173
+ @formatter.print_status "x", RED
174
+ print BOLD + "Unable to initialise tags\n" + RESET
175
+ return false
176
+ end
177
+
178
+ project_id = get_project_identifier(_api_key, _project, workspace_id)
179
+
180
+ unless project_id
181
+ @formatter.print_status "x", RED
182
+ print BOLD + "Unable to get project info from Asana API\n" + RESET
183
+ return false
184
+ end
185
+
186
+ tasks_url = "#{ @end_point }/workspaces/#{ workspace_id }/tasks"
187
+
188
+ # Create the body text for the issue here, too long to fit nicely into opts hash
189
+ _body =
190
+ "__filename__ : #{ issue[:path] }\n" +
191
+ "__line #__ : #{ issue[:line_number] }\n" +
192
+ "__tag__ : #{ issue[:tag] }\n" +
193
+ "__md5__ : #{ issue[:md5] }\n\n" +
194
+ "#{ issue[:context].join }\n"
195
+
196
+ opts = {
197
+ :url => tasks_url,
198
+ :ssl => true,
199
+ :method => "POST",
200
+ :basic_auth => [_api_key, ""],
201
+ :data => [{"name" => issue[:title],
202
+ "notes" => _body,
203
+ "projects" => project_id}],
204
+ :verbose => false
205
+ }
206
+
207
+ _json, _resp = Watson::Remote.http_call(opts)
208
+
209
+ unless _resp.code == "201"
210
+ @formatter.print_status "x", RED
211
+ print BOLD + "Unable to access Asana API, key may be invalid\n" + RESET
212
+ print " Consider running --remote (-r) option to regenerate key\n\n"
213
+ print " Status: #{ _resp.code } - #{ _resp.message }\n"
214
+ return false
215
+ end
216
+
217
+ _data = _json["data"]
218
+ new_task_id = _data["id"]
219
+
220
+ debug_print "creating file tag"
221
+ file_tag = create_or_get_tag(_api_key,workspace_id,issue[:path])
222
+ debug_print "tagging with file tag <#{ file_tag }>"
223
+
224
+ if file_tag
225
+ file_tag_id = file_tag['id']
226
+ unless tag_task(_api_key, new_task_id, file_tag_id)
227
+ @formatter.print_status "!", RED
228
+ print BOLD + "Unable to tag with '#{ issue[:path] }'\n" + RESET
229
+ end
230
+ else
231
+ @formatter.print_status "!", RED
232
+ print BOLD + "Unable create tag '#{ issue[:path] }'\n" + RESET
233
+ end
234
+
235
+ debug_print "tagging with issue tag <#{ issue[:tag] }>"
236
+ issue_tag_id = tags[issue[:tag]]['id']
237
+ unless tag_task(_api_key, new_task_id, issue_tag_id)
238
+ @formatter.print_status "!", RED
239
+ print BOLD + "Unable to tag with '#{ issue[:tag] }'\n" + RESET
240
+ end
241
+
242
+ debug_print "tagging with watson tag"
243
+ watson_tag_id = tags['watson']['id']
244
+ unless tag_task(_api_key, new_task_id, watson_tag_id)
245
+ @formatter.print_status "!", RED
246
+ print BOLD + "Unable to tag with 'watson'\n" + RESET
247
+ end
248
+
249
+ # Parse response and append issue hash so we are up to date
250
+ config.asana_issues[issue[:md5]] = {
251
+ :title => _data["name"],
252
+ :id => _data["id"],
253
+ :state => _data["completed"]
254
+ }
255
+
256
+ true
257
+
258
+ end
259
+
260
+ ###########################################################
261
+ # Get all remote Asana issues and store into Config container class
262
+ def get_issues(config)
263
+
264
+ # Identify method entry
265
+ debug_print "#{ self.class } : #{ __method__ }\n"
266
+
267
+ # Set up formatter for printing errors
268
+ # config.output_format should be set based on less status by now
269
+ @formatter = Printer.new(config).build_formatter
270
+
271
+ # Only attempt to get issues if API is specified
272
+ if config.asana_api.empty?
273
+ debug_print "No asana API found, this shouldn't be called...\n"
274
+ return false
275
+ end
276
+
277
+ _api_key = config.asana_api
278
+ _workspace = config.asana_workspace
279
+ _project = config.asana_project
280
+
281
+ task_records = get_tasks(_api_key, _project, _workspace)
282
+
283
+ unless task_records
284
+ config.asana_valid = false
285
+ return false
286
+ end
287
+
288
+ task_records.each do |issue|
289
+ # Skip this issue if it doesn't have watson md5 tag
290
+ _md5 = issue["notes"].match(/.*__md5__ : (\w+)\s.*/)
291
+ next if (_md5).nil?
292
+
293
+ # If it does, use md5 as hash key and populate values with our info
294
+ config.asana_issues[_md5[1]] = {
295
+ :title => issue["name"],
296
+ :id => issue["id"],
297
+ :state => issue["completed"] # TODO: How to check status?
298
+ }
299
+ end
300
+
301
+ config.asana_valid = true
302
+
303
+ end
304
+
305
+ private
306
+
307
+ ###########################################################
308
+ # Return all projects under the given api_key/workspace
309
+ def get_projects(_api_key, workspace_id)
310
+
311
+ debug_print "#{ self.class } : #{ __method__ }\n"
312
+
313
+ projects_url = "#{ @end_point }/workspaces/#{ workspace_id}/projects"
314
+
315
+ opts = {
316
+ :url => projects_url,
317
+ :ssl => true,
318
+ :method => "GET",
319
+ :basic_auth => [_api_key, ""],
320
+ :verbose => false
321
+ }
322
+
323
+ _json, _resp = Watson::Remote.http_call(opts)
324
+
325
+ if _resp.code != "200"
326
+ print "\n"
327
+ @formatter.print_status "x", RED
328
+ print BOLD + "Unable to access project list\n" + RESET
329
+ print " Status: #{ _resp.code } - #{ _resp.message }\n\n"
330
+ return false
331
+ end
332
+
333
+ project_list = _json["data"]
334
+ project_dict = {}
335
+ project_list.each { |x| project_dict[x["name"]] = x["id"] }
336
+ project_dict
337
+ end
338
+
339
+ ###########################################################
340
+ # Return all workspaces under the given API key
341
+ def get_workspaces(_api_key)
342
+
343
+ debug_print "#{ self.class } : #{ __method__ }\n"
344
+
345
+ workspaces_url = "#{ @end_point }/workspaces"
346
+
347
+ opts = {
348
+ :url => workspaces_url,
349
+ :ssl => true,
350
+ :method => "GET",
351
+ :basic_auth => [_api_key, ""],
352
+ :verbose => false
353
+ }
354
+
355
+ _json, _resp = Watson::Remote.http_call(opts)
356
+
357
+ if _resp.code != "200"
358
+ print "\n"
359
+ @formatter.print_status "x", RED
360
+ print BOLD + "Unable to access workspace list with given credentials\n" + RESET
361
+ print " Check that API key is correct\n"
362
+ print " Status: #{ _resp.code } - #{ _resp.message }\n\n"
363
+ return false
364
+ end
365
+
366
+ workspace_list = _json["data"]
367
+ workspace_dict = {}
368
+ workspace_list.each { |x| workspace_dict[x["name"]] = x["id"] }
369
+ workspace_dict
370
+ end
371
+
372
+ ###########################################################
373
+ # Return all tasks in given project/workspace
374
+ def get_tasks(_api_key, _project, _workspace)
375
+
376
+ debug_print "#{ self.class } : #{ __method__ }\n"
377
+
378
+ workspace_id = get_workspace_id(_api_key, _workspace)
379
+
380
+ unless workspace_id
381
+ @formatter.print_status "x", RED
382
+ print BOLD + "Unable to get workspace info from Asana API\n" + RESET
383
+ return false end
384
+
385
+ project_id = get_project_identifier(_api_key, _project, workspace_id)
386
+
387
+ unless project_id
388
+ @formatter.print_status "x", RED
389
+ print BOLD + "Unable to get project info from Asana API\n" + RESET
390
+ return false
391
+ end
392
+
393
+ tasks_url = "#{ @end_point }/projects/#{ project_id }/tasks?opt_fields=name,notes,completed&include_archived=true"
394
+
395
+ opts = {
396
+ :url => tasks_url,
397
+ :ssl => true,
398
+ :method => "GET",
399
+ :basic_auth => [_api_key, ""],
400
+ :verbose => false
401
+ }
402
+
403
+ _json, _resp = Watson::Remote.http_call(opts)
404
+
405
+ # Check response to validate repo access
406
+ if _resp.code != "200"
407
+ @formatter.print_status "x", RED
408
+ print BOLD + "Unable to access Asana API, key may be invalid\n" + RESET
409
+ print " Consider running --remote (-r) option to regenerate key\n\n"
410
+ print " Status: #{ _resp.code } - #{ _resp.message }\n"
411
+
412
+ debug_print "Asana invalid, setting config var\n"
413
+ #return false
414
+ end
415
+
416
+ _json["data"]
417
+
418
+ end
419
+
420
+ ###########################################################
421
+ # Return full record for a particular task
422
+ def get_task_record(_api_key, task_id)
423
+
424
+ debug_print "#{ self.class } : #{ __method__ }\n"
425
+
426
+ task_url = "#{ @end_point }/tasks/#{ task_id }"
427
+
428
+ opts = {
429
+ :url => task_url,
430
+ :ssl => true,
431
+ :method => "GET",
432
+ :basic_auth => [_api_key, ""],
433
+ :verbose => false
434
+ }
435
+
436
+ _json, _resp = Watson::Remote.http_call(opts)
437
+
438
+ if _resp.code != "200"
439
+ @formatter.print_status "x", RED
440
+ print BOLD + "Unable to get task, API key may be invalid\n" + RESET
441
+ print " Consider running --remote (-r) option to regenerate key\n\n"
442
+ print " Status: #{ _resp.code } - #{ _resp.message }\n"
443
+ return false
444
+ end
445
+
446
+ _json["data"]
447
+ end
448
+
449
+ ###########################################################
450
+ # Get project id given project and work space name
451
+ def get_project_identifier(_api_key, _project, workspace_id)
452
+ debug_print "#{ self.class } : #{ __method__ }\n"
453
+ projects_dict = get_projects(_api_key, workspace_id)
454
+ unless projects_dict
455
+ return false
456
+ end
457
+ projects_dict[_project]
458
+ end
459
+
460
+ ###########################################################
461
+ # Get workspace id given workspace name
462
+ def get_workspace_id(_api_key, _workspace)
463
+ debug_print "#{ self.class } : #{ __method__ }\n"
464
+ workspace_dict = get_workspaces(_api_key)
465
+ unless workspace_dict
466
+ return false
467
+ end
468
+ workspace_dict[_workspace]
469
+ end
470
+
471
+ ###########################################################
472
+ # Tags task with task_id with the tag specified by tag_id
473
+ def tag_task(_api_key, task_id, tag_id)
474
+ debug_print "#{ self.class } : #{ __method__ }\n"
475
+
476
+ tags_url = "#{ @end_point }/tasks/#{ task_id }/addTag"
477
+
478
+ opts = {
479
+ :url => tags_url,
480
+ :ssl => true,
481
+ :method => "POST",
482
+ :basic_auth => [_api_key, ""],
483
+ :data => [{"tag" => tag_id}],
484
+ :verbose => false
485
+ }
486
+
487
+ _json, _resp = Watson::Remote.http_call(opts)
488
+
489
+ unless _resp.code == "200"
490
+ @formatter.print_status "x", RED
491
+ print BOLD + "Unable to access Asana API, key may be invalid\n" + RESET
492
+ print " Consider running --remote (-r) option to regenerate key\n\n"
493
+ print " Status: #{ _resp.code } - #{ _resp.message }\n"
494
+ return false
495
+ end
496
+
497
+ _json['data']
498
+
499
+ end
500
+
501
+ ###########################################################
502
+ # Returns hash, tag name => tag
503
+ def get_tags(_api_key, _workspace_id)
504
+ debug_print "#{ self.class } : #{ __method__ }\n"
505
+
506
+ tags_url = "#{ @end_point }/workspaces/#{ _workspace_id }/tags"
507
+
508
+ opts = {
509
+ :url => tags_url,
510
+ :ssl => true,
511
+ :method => "GET",
512
+ :basic_auth => [_api_key, ""],
513
+ :verbose => false
514
+ }
515
+
516
+ _json, _resp = Watson::Remote.http_call(opts)
517
+
518
+ unless _resp.code == "200"
519
+ @formatter.print_status "x", RED
520
+ print BOLD + "Unable to access Asana API, key may be invalid\n" + RESET
521
+ print " Consider running --remote (-r) option to regenerate key\n\n"
522
+ print " Status: #{ _resp.code } - #{ _resp.message }\n"
523
+ return false
524
+ end
525
+
526
+ # [review] - fancy ruby way of generating this hash?
527
+
528
+ _tags_dict = {}
529
+ _json['data'].each { |x| _tags_dict[x['name']] = x }
530
+ _tags_dict
531
+
532
+ end
533
+
534
+ ###########################################################
535
+ # Make sure base tags exist (tag_list + the watson tag), add them if not
536
+ def init_tags(config, _api_key, _workspace_id)
537
+ debug_print "#{ self.class } : #{ __method__ }\n"
538
+ tags_to_check = config.tag_list.dup << 'watson'
539
+ create_and_get_tags(_api_key, _workspace_id, tags_to_check)
540
+ end
541
+
542
+ ###########################################################
543
+ # Adds 'tags_to_add' and returns full list of tags.
544
+ # The reason that this is combined into one method is that
545
+ # Asana do not actually 'create' the tag properly until
546
+ # it has been used to tag a task
547
+ def create_and_get_tags(_api_key, _workspace_id, tags_to_add)
548
+ debug_print "#{ self.class } : #{ __method__ }\n"
549
+
550
+ _tags = get_tags(_api_key, _workspace_id)
551
+
552
+ unless _tags
553
+ return false
554
+ end
555
+
556
+ debug_print "Checking that tags #{ tags_to_add } exist\n"
557
+ tags_to_create = tags_to_add - _tags.keys
558
+ if tags_to_create
559
+ debug_print "Need to create tags #{ tags_to_create }\n"
560
+ end
561
+ tags_to_create.each do |tag_name|
562
+ _tag = create_tag(_api_key, _workspace_id, tag_name)
563
+ unless _tag
564
+ return false
565
+ end
566
+ _tags[_tag['name']] = _tag
567
+ end
568
+
569
+ _tags
570
+ end
571
+
572
+ ###########################################################
573
+ # Creates a tag in the given workspace
574
+ def create_tag(_api_key, _workspace_id, tag)
575
+ debug_print "#{ self.class } : #{ __method__ }\n"
576
+
577
+ opts = {
578
+ :url => "#{ @end_point }/workspaces/#{ _workspace_id }/tags",
579
+ :ssl => true,
580
+ :method => "POST",
581
+ :basic_auth => [_api_key, ""],
582
+ :data => [{'name'=>tag}],
583
+ :verbose => false
584
+ }
585
+
586
+ _json, _resp = Watson::Remote.http_call(opts)
587
+
588
+ unless _resp.code == "201"
589
+ @formatter.print_status "x", RED
590
+ print BOLD + "Unable to access Asana API, key may be invalid\n" + RESET
591
+ print " Consider running --remote (-r) option to regenerate key\n\n"
592
+ print " Status: #{ _resp.code } - #{ _resp.message }\n"
593
+ return false
594
+ end
595
+
596
+ _data = _json['data']
597
+
598
+ debug_print "Created tag '#{ tag }' with id #{ _data['id'] } \n"
599
+
600
+ _data
601
+
602
+ end
603
+
604
+ ###########################################################
605
+ # Creates a tag in the given workspace or else if already
606
+ # exists, returns that tag
607
+ def create_or_get_tag(_api_key, _workspace_id, tag_name)
608
+ debug_print "#{ self.class } : #{ __method__ }\n"
609
+ _tags = create_and_get_tags(_api_key, _workspace_id, [tag_name])
610
+ _tags ? _tags[tag_name] : false
611
+ end
612
+
613
+ end
614
+
615
+ end
616
+
617
+ end
618
+
619
+ end