jiraSOAP 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -46,11 +46,7 @@ Once that ugliness is over with, you can run a quick demo (making appropriate su
46
46
 
47
47
  Get the [Gist](http://gist.github.com/612186).
48
48
 
49
-
50
- Notes About Using This Gem
51
- --------------------------
52
-
53
- 1. URESOLVED issues have a Resolution with a value of `nil`.
49
+ Read the documentation [here](http://rdoc.info/github/Marketcircle/jiraSOAP/master/frames). The meat of the service is in the `RemoteAPI` module.
54
50
 
55
51
 
56
52
  TODO
@@ -58,6 +54,7 @@ TODO
58
54
 
59
55
  - Performance optimizations; there are a number of places that can be optimized
60
56
  + Using GCD/Threads for parsing arrays of results; a significant speed up for large types and large arrays (ie. creating issues from JQL searches)
57
+ - Refactor for a smaller code base
61
58
  - Fix type hacks;. dates should be `NSDate`s and URLs should be `NSURL`s, right now they are all strings
62
59
  - Public test suite
63
60
  + Needs a mock server
@@ -9,25 +9,29 @@ module JIRA
9
9
  # will end up messing up any other instances currently being used.
10
10
  #
11
11
  # It is best to treat this class as a singleton. There should only be one.
12
+ # However, this is not enforced, in case you want to be able to login as
13
+ # multiple users to the same endpoint.
12
14
  #
13
15
  # HTTPS is not supported in this version.
16
+ #
17
+ # @todo consider adding a finalizer that will try to logout
14
18
  class JIRAService < Handsoap::Service
15
19
  include RemoteAPI
16
20
 
17
21
  attr_reader :auth_token, :user
18
22
 
19
23
  # Factory method to initialize and login.
20
- #@param [String] url URL for the JIRA server
21
- #@param [String] user JIRA user name to login with
22
- #@param [String] password
23
- def self.instance_at_url(url, user, password)
24
+ # @param [String] url URL for the JIRA server
25
+ # @param [String] user JIRA user name to login with
26
+ # @param [String] password
27
+ def self.instance_with_endpoint(url, user, password)
24
28
  jira = JIRAService.new url
25
29
  jira.login user, password
26
30
  jira
27
31
  end
28
32
 
29
33
  # Slightly hacky in order to set the endpoint at the initialization.
30
- #@param endpoint_url URL for the JIRA server
34
+ # @param endpoint_url URL for the JIRA server
31
35
  def initialize(endpoint_url)
32
36
  super
33
37
 
@@ -39,11 +43,11 @@ class JIRAService < Handsoap::Service
39
43
  self.class.endpoint endpoint_data
40
44
  end
41
45
 
42
- #PONDER: a finalizer that will try to logout
43
-
44
46
  # Something to help users out until the rest of the API is implemented.
45
47
  def method_missing(method, *args)
46
- message = 'Check the documentation; the method may not be implemented yet.'
48
+ message = 'Check the documentation; the method may not be implemented or '
49
+ message << 'has changed in recent revisions. The client side API has not '
50
+ message << 'been stabilized yet.'
47
51
  raise NoMethodError, message, caller
48
52
  end
49
53
 
@@ -1,24 +1,24 @@
1
- #Some simple extensions to Handsoap.
1
+ # Some simple extensions to Handsoap.
2
2
  module Handsoap
3
- #Some simple extensions to XmlMason. Currently, this only includes methods to
4
- #make it easier to build SOAP messages that contain arrays.
3
+ # Some simple extensions to XmlMason. Currently, this only includes methods to
4
+ # make it easier to build SOAP messages that contain arrays.
5
5
  module XmlMason
6
- #Represents a node in an XML document
6
+ # Represents a node in an XML document used for SOAP message creation.
7
7
  class Node
8
- #@todo Make this method recursive
9
- #@param [String] node_name
10
- #@param [Array] array
11
- #@param [Hash] options
8
+ # @todo Make this method recursive
9
+ # @param [String] node_name
10
+ # @param [Array] array
11
+ # @param [Hash] options
12
12
  def add_simple_array(node_name, array = [], options = {})
13
13
  prefix, name = parse_ns(node_name)
14
14
  node = append_child Element.new(self, prefix, name, nil, options)
15
15
  array.each { |element| node.add node_name, element }
16
16
  end
17
17
 
18
- #@todo Make this method recursive
19
- #@param [String] node_name
20
- #@param [Array] array
21
- #@param [Hash] options
18
+ # @todo Make this method recursive
19
+ # @param [String] node_name
20
+ # @param [Array] array
21
+ # @param [Hash] options
22
22
  def add_complex_array(node_name, array = [], options = {})
23
23
  prefix, name = parse_ns(node_name)
24
24
  node = append_child Element.new(self, prefix, name, nil, options)
@@ -1,4 +1,15 @@
1
- Handsoap.http_driver = :net_http #curb does not build for MacRuby
1
+ framework 'Foundation'
2
+
3
+ # work around curb not building for MacRuby (ticket #941)
4
+ Handsoap.http_driver = :net_http
5
+
6
+ class URL
7
+ def initialize(url_string)
8
+ @url = NSURL.URLWithString url_string
9
+ end
10
+
11
+ alias_method absoluteString to_s
12
+ end
2
13
 
3
14
  module JIRA
4
15
  #overrides for MacRuby
@@ -3,29 +3,29 @@
3
3
  #@todo exception handling
4
4
  #@todo code refactoring and de-duplication
5
5
  module RemoteAPI
6
- #XPath constant to get a node containing a response array.
7
- #This could be used for all responses, but is only used in cases where we
8
- #cannot use a more blunt XPath expression.
6
+ # XPath constant to get a node containing a response array.
7
+ # This could be used for all responses, but is only used in cases where we
8
+ # cannot use a more blunt XPath expression.
9
9
  RESPONSE_XPATH = '/node()[1]/node()[1]/node()[1]/node()[2]'
10
10
 
11
- #The first method to call. No other method will work unless you are logged in.
12
- #@param [String] user JIRA user name to login with
13
- #@param [String] password
14
- #@return [boolean] true if successful, otherwise false
11
+ # The first method to call; other methods will fail until you are logged in.
12
+ # @param [String] user JIRA user name to login with
13
+ # @param [String] password
14
+ # @return [true] true if successful, otherwise an exception is thrown
15
15
  def login(user, password)
16
16
  response = invoke('soap:login') { |msg|
17
17
  msg.add 'soap:in0', user
18
18
  msg.add 'soap:in1', password
19
19
  }
20
- #cache now that we know it is safe to do so
20
+ # cache now that we know it is safe to do so
21
21
  @user = user
22
22
  @auth_token = response.document.xpath('//loginReturn').first.to_s
23
23
  true
24
24
  end
25
25
 
26
- #You only need to call this to make an exlicit logout; normally, a session
27
- #willautomatically expire after a set time (configured on the server).
28
- #@return [boolean] true if successful, otherwise false
26
+ # You only need to call this to make an explicit logout; normally, a session
27
+ # will automatically expire after a set time (configured on the server).
28
+ # @return [true] true if successful, otherwise false
29
29
  def logout
30
30
  response = invoke('soap:logout') { |msg|
31
31
  msg.add 'soap:in0', @auth_token
@@ -33,8 +33,7 @@ module RemoteAPI
33
33
  response.document.xpath('//logoutReturn').first.to_s == 'true'
34
34
  end
35
35
 
36
- #Get the global listing for types of priorities.
37
- #@return [[JIRA::Priority]]
36
+ # @return [[JIRA::Priority]]
38
37
  def get_priorities
39
38
  response = invoke('soap:getPriorities') { |msg|
40
39
  msg.add 'soap:in0', @auth_token
@@ -45,8 +44,7 @@ module RemoteAPI
45
44
  }
46
45
  end
47
46
 
48
- #Get the global listing for types of resolutions.
49
- #@return [[JIRA::Resolution]]
47
+ # @return [[JIRA::Resolution]]
50
48
  def get_resolutions
51
49
  response = invoke('soap:getResolutions') { |msg|
52
50
  msg.add 'soap:in0', @auth_token
@@ -57,8 +55,7 @@ module RemoteAPI
57
55
  }
58
56
  end
59
57
 
60
- #Get the global listing for types of custom fields.
61
- #@return [[JIRA::Field]]
58
+ # @return [[JIRA::Field]]
62
59
  def get_custom_fields
63
60
  response = invoke('soap:getCustomFields') { |msg|
64
61
  msg.add 'soap:in0', @auth_token
@@ -69,8 +66,7 @@ module RemoteAPI
69
66
  }
70
67
  end
71
68
 
72
- #Get the global listing for types of issues.
73
- #@return [[JIRA::IssueType]]
69
+ # @return [[JIRA::IssueType]]
74
70
  def get_issue_types
75
71
  response = invoke('soap:getIssueTypes') { |msg|
76
72
  msg.add 'soap:in0', @auth_token
@@ -81,8 +77,7 @@ module RemoteAPI
81
77
  }
82
78
  end
83
79
 
84
- #Get the global listing of status type.
85
- #@return [[JIRA::Status]]
80
+ # @return [[JIRA::Status]]
86
81
  def get_statuses
87
82
  response = invoke('soap:getStatuses') { |msg|
88
83
  msg.add 'soap:in0', @auth_token
@@ -93,8 +88,7 @@ module RemoteAPI
93
88
  }
94
89
  end
95
90
 
96
- #Get the global listing for notification schemes.
97
- #@return [[JIRA::Scheme]]
91
+ # @return [[JIRA::Scheme]]
98
92
  def get_notification_schemes
99
93
  response = invoke('soap:getNotificationSchemes') { |msg|
100
94
  msg.add 'soap:in0', @auth_token
@@ -105,9 +99,8 @@ module RemoteAPI
105
99
  }
106
100
  end
107
101
 
108
- #Get all the versions associated with a project.
109
- #@param [String] project_key
110
- #@return [[JIRA::Version]]
102
+ # @param [String] project_key
103
+ # @return [[JIRA::Version]]
111
104
  def get_versions_for_project(project_key)
112
105
  response = invoke('soap:getVersions') { |msg|
113
106
  msg.add 'soap:in0', @auth_token
@@ -119,9 +112,8 @@ module RemoteAPI
119
112
  }
120
113
  end
121
114
 
122
- #Get the information for a project with a given key.
123
- #@param [String] project_key
124
- #@return [JIRA::Project]
115
+ # @param [String] project_key
116
+ # @return [JIRA::Project]
125
117
  def get_project_with_key(project_key)
126
118
  response = invoke('soap:getProjectByKey') { |msg|
127
119
  msg.add 'soap:in0', @auth_token
@@ -131,9 +123,8 @@ module RemoteAPI
131
123
  JIRA::Project.project_with_xml_fragment frag
132
124
  end
133
125
 
134
- #This will only give you basic information about a user.
135
- #@param [String] user_name
136
- #@return [JIRA::User]
126
+ # @param [String] user_name
127
+ # @return [JIRA::User]
137
128
  def get_user_with_name(user_name)
138
129
  response = invoke('soap:getUser') { |msg|
139
130
  msg.add 'soap:in0', @auth_token
@@ -142,10 +133,10 @@ module RemoteAPI
142
133
  JIRA::User.user_with_xml_fragment response.document.xpath '//getUserReturn'
143
134
  end
144
135
 
145
- #Gives you the default avatar image for a project; if you want all
146
- #the avatars for a project, use {#get_project_avatars_for_key}.
147
- #@param [String] project_key
148
- #@return [JIRA::Avatar]
136
+ # Gets you the default avatar image for a project; if you want all
137
+ # the avatars for a project, use {#get_project_avatars_for_key}.
138
+ # @param [String] project_key
139
+ # @return [JIRA::Avatar]
149
140
  def get_project_avatar_for_key(project_key)
150
141
  response = invoke('soap:getProjectAvatar') { |msg|
151
142
  msg.add 'soap:in0', @auth_token
@@ -154,10 +145,10 @@ module RemoteAPI
154
145
  JIRA::Avatar.avatar_with_xml_fragment response.document.xpath '//getProjectAvatarReturn'
155
146
  end
156
147
 
157
- #Gives ALL avatars for a given project use this method; if you
158
- #just want the default avatar, use {#get_project_avatar_for_key}.
159
- #@param [String] project_key
160
- #@return [[JIRA::Avatar]]
148
+ # Gets ALL avatars for a given project use this method; if you
149
+ # just want the default avatar, use {#get_project_avatar_for_key}.
150
+ # @param [String] project_key
151
+ # @return [[JIRA::Avatar]]
161
152
  def get_project_avatars_for_key(project_key)
162
153
  response = invoke('soap:getProjectAvatars') { |msg|
163
154
  msg.add 'soap:in0', @auth_token
@@ -169,12 +160,12 @@ module RemoteAPI
169
160
  }
170
161
  end
171
162
 
172
- #This method is the equivalent of making an advanced search from the
173
- #web interface.
174
- #@param [String] jql_query JQL query as a string
175
- #@param [Fixnum] max_results limit on number of returned results;
163
+ # This method is the equivalent of making an advanced search from the
164
+ # web interface.
165
+ # @param [String] jql_query JQL query as a string
166
+ # @param [Fixnum] max_results limit on number of returned results;
176
167
  # the value may be overridden by the server if max_results is too large
177
- #@return [[JIRA::Issue]]
168
+ # @return [[JIRA::Issue]]
178
169
  def get_issues_from_jql_search(jql_query, max_results = 500)
179
170
  response = invoke('soap:getIssuesFromJqlSearch') { |msg|
180
171
  msg.add 'soap:in0', @auth_token
@@ -187,29 +178,29 @@ module RemoteAPI
187
178
  }
188
179
  end
189
180
 
190
- #This method can update most, but not all, issue fields.
181
+ # This method can update most, but not all, issue fields.
191
182
  #
192
- #Fields known to not update via this method:
183
+ # Fields known to not update via this method:
193
184
  # - status - use {#progress_workflow_action}
194
185
  # - attachments - use {#add_base64_encoded_attachment_to_issue}
195
186
  #
196
- #Though JIRA::FieldValue objects have an id field, they do not expect
197
- #to be given id values. You must use the name of the field you wish to update.
198
- #@example Usage With A Normal Field
187
+ # Though JIRA::FieldValue objects have an id field, they do not expect to be
188
+ # given id values. You must use the name of the field you wish to update.
189
+ # @example Usage With A Normal Field
199
190
  # summary = JIRA::FieldValue.new
200
191
  # summary.id = 'summary'
201
192
  # summary.values = ['My new summary']
202
- #@example Usage With A Custom Field
193
+ # @example Usage With A Custom Field
203
194
  # custom_field = JIRA::FieldValue.new
204
195
  # custom_field.id = 'customfield_10060'
205
196
  # custom_field.values = ['123456']
206
- #@example Setting a field to be blank/nil
197
+ # @example Setting a field to be blank/nil
207
198
  # description = JIRA::FieldValue.field_value_with_nil_values 'description'
208
- #@example Calling the method to update an issue
199
+ # @example Calling the method to update an issue
209
200
  # jira_service_instance.update_issue 'PROJECT-1', description, custom_field
210
- #@param [String] issue_key
211
- #@param [JIRA::FieldValue] *field_values
212
- #@return [JIRA::Issue]
201
+ # @param [String] issue_key
202
+ # @param [JIRA::FieldValue] *field_values
203
+ # @return [JIRA::Issue]
213
204
  def update_issue(issue_key, *field_values)
214
205
  response = invoke('soap:updateIssue') { |msg|
215
206
  msg.add 'soap:in0', @auth_token
@@ -222,16 +213,16 @@ module RemoteAPI
222
213
  JIRA::Issue.issue_with_xml_fragment frag
223
214
  end
224
215
 
225
- #Some fields will be ignored when an issue is created:
216
+ # Some fields will be ignored when an issue is created.
226
217
  # - reporter - you cannot override this value at creation
227
- # - due date - I think this is a bug in jiraSOAP or JIRA
218
+ # - resolution
228
219
  # - attachments
229
220
  # - votes
230
221
  # - status
231
- # - resolution
222
+ # - due date - I think this is a bug in jiraSOAP or JIRA
232
223
  # - environment - I think this is a bug in jiraSOAP or JIRA
233
- #@param [JIRA::Issue] issue
234
- #@return [JIRA::Issue]
224
+ # @param [JIRA::Issue] issue
225
+ # @return [JIRA::Issue]
235
226
  def create_issue_with_issue(issue)
236
227
  response = invoke('soap:createIssue') { |msg|
237
228
  msg.add 'soap:in0', @auth_token
@@ -242,39 +233,349 @@ module RemoteAPI
242
233
  frag = response.document.xpath '//createIssueReturn'
243
234
  JIRA::Issue.issue_with_xml_fragment frag
244
235
  end
236
+
237
+ # @todo test this method
238
+ # @param [String] issue_key
239
+ # @return [JIRA::Issue]
240
+ def get_issue_with_key(issue_key)
241
+ response = invoke('soap:getIssue') { |msg|
242
+ msg.add 'soap:in0', @auth_token
243
+ msg.add 'soap:in1', issue_key
244
+ }
245
+ frag = response.document.xpath '//getIssueReturn'
246
+ JIRA:Issue.issue_with_xml_fragment frag
247
+ end
248
+
249
+ # @todo test this method
250
+ # @param [String] issue_id
251
+ # @return [JIRA::Issue]
252
+ def get_issue_with_id(issue_id)
253
+ response = invoke('soap:getIssueById') { |msg|
254
+ msg.add 'soap:in0', @auth_token
255
+ msg.add 'soap:in1', issue_id
256
+ }
257
+ frag = response.document.xpath '//getIssueByIdReturn'
258
+ JIRA::Issue.issue_with_xml_fragment frag
259
+ end
260
+
261
+ # @todo test this method
262
+ # @param [String] issue_key
263
+ # @return [[JIRA::Attachment]]
264
+ def get_attachments_for_issue_with_key(issue_key)
265
+ response = invoke('soap:getAttachmentsFromIssue') { |msg|
266
+ msg.add 'soap:in0', @auth_token
267
+ msg.add 'soap:in1', issue_key
268
+ }
269
+ response.document.xpath("#{RESPONSE_XPATH}/getAttachmentsFromIssueReturn").map {
270
+ |frag|
271
+ JIRA::AttachmentMetadata.attachment_with_xml_fragment frag
272
+ }
273
+ end
274
+
275
+ # @todo test this method
276
+ # @param [String] project_key
277
+ # @param [JIRA::Version] version
278
+ # @return [JIRA::Version]
279
+ def add_version_to_project(project_key, version)
280
+ response = invoke('soap:addVersion') { |msg|
281
+ msg.add 'soap:in0', @auth_token
282
+ msg.add 'soap:in1', project_key
283
+ msg.add 'soap:in2' do |submsg| version.soapify_for submsg end
284
+ }
285
+ frag = response.document.xpath '//addVersionReturn'
286
+ JIRA::Version.version_with_xml_fragment frag
287
+ end
288
+
289
+ # @todo test this method
290
+ # @param [String] project_key
291
+ # @param [String] version_name
292
+ # @param [boolean] state
293
+ # @return [boolean] true if successful, otherwise an exception is thrown
294
+ def set_archive_state_for_version_for_project(project_key, version_name, state)
295
+ response = invoke('soap:archiveVersion') { |msg|
296
+ msg.add 'soap:in0', @auth_token
297
+ msg.add 'soap:in1', project_key
298
+ msg.add 'soap:in2', version_name
299
+ msg.add 'soap:in3', state
300
+ }
301
+ true
302
+ end
303
+
304
+ # @todo test this method
305
+ # @param [JIRA::Project] project
306
+ # @return [JIRA::Project]
307
+ def create_project_with_project(project)
308
+ response = invoke('soap:createProjectFromObject') { |msg|
309
+ msg.add 'soap:in0', @auth_token
310
+ msg.add 'soap:in1' do |submsg| project.soapify_for submsg end
311
+ }
312
+ frag = response.document.xpath '//createProjectFromObjectReturn'
313
+ JIRA::Project.project_with_xml_fragment frag
314
+ end
315
+
316
+ # @todo test this method
317
+ # @param [JIRA::Project] project
318
+ # @return [JIRA::Project]
319
+ def update_project_with_project(project)
320
+ response = invoke('soap:updateProject') { |msg|
321
+ msg.add 'soap:in0', @auth_token
322
+ msg.add 'soap:in1' do |submsg| project.soapify_for submsg end
323
+ }
324
+ frag = response.document.xpath '//updateProjectReturn'
325
+ JIRA::Project.project_with_xml_fragment frag
326
+ end
327
+
328
+ # @todo test this method
329
+ # @param [String] username
330
+ # @param [String] password
331
+ # @param [String] full_name
332
+ # @param [String] email
333
+ # @return [JIRA::User]
334
+ def create_user(username, password, full_name, email)
335
+ response = invoke('soap:createUser') { |msg|
336
+ msg.add 'soap:in0', @auth_token
337
+ msg.add 'soap:in1', username
338
+ msg.add 'soap:in2', password
339
+ msg.add 'soap:in3', full_name
340
+ msg.add 'soap:in4', email
341
+ }
342
+ frag = response.document.xpath '//createUserReturn'
343
+ JIRA::User.user_with_xml_fragment frag
344
+ end
345
+
346
+ # @todo test this method
347
+ # @param [String] username
348
+ # @return [boolean] true if successful, throws an exception otherwise
349
+ def delete_user_with_name(username)
350
+ response = invoke('soap:deleteUser') { |msg|
351
+ msg.add 'soap:in0', @auth_token
352
+ msg.add 'soap:in1', username
353
+ }
354
+ true
355
+ end
356
+
357
+ # @todo test this method
358
+ # @param [String] id
359
+ # @return [JIRA::Project]
360
+ def get_project_with_id(id)
361
+ response = invoke('soap:getProjectById') { |msg|
362
+ msg.add 'soap:in0', @auth_token
363
+ msg.add 'soap:in1', id
364
+ }
365
+ frag = response.document.xpath '//getProjectByIdReturn'
366
+ JIRA::Project.project_with_xml_fragment frag
367
+ end
368
+
369
+ # @todo test this method
370
+ # @param [String] issue_key
371
+ # @param [JIRA::Comment] comment
372
+ # @return [boolean] true if successful, throws an exception otherwise
373
+ def add_comment_to_issue(issue_key, comment)
374
+ response = invoke('soap:addComment') { |msg|
375
+ msg.add 'soap:in0', @auth_token
376
+ msg.add 'soap:in1', issue_key
377
+ msg.add 'soap:in2' do |submsg| comment.soapify_for submsg end
378
+ }
379
+ true
380
+ end
381
+
382
+ # @todo test this method
383
+ # @param [String] id
384
+ # @return [JIRA::Comment]
385
+ def get_comment_with_id(id)
386
+ response = invoke('soap:getComment') { |msg|
387
+ msg.add 'soap:in0', @auth_token
388
+ msg.add 'soap:in1', id
389
+ }
390
+ frag = response.document.xpath '//getCommentReturn'
391
+ JIRA::Comment.comment_with_xml_fragment frag
392
+ end
393
+
394
+ # @todo test this method
395
+ # @param [String] issue_key
396
+ # @return [[JIRA::Comment]]
397
+ def get_comments_for_issue(issue_key)
398
+ response = invoke('soap:getComments') { |msg|
399
+ msg.add 'soap:in0', @auth_token
400
+ msg.add 'soap:in1', issue_key
401
+ }
402
+ response.document.xpath("#{RESPONSE_XPATH}/getCommentsReturn").map {
403
+ |frag|
404
+ JIRA::Comment.comment_with_xml_fragment frag
405
+ }
406
+ end
407
+
408
+ # @todo test this method
409
+ # @param [String] project_name
410
+ # @return [[JIRA::IssueType]]
411
+ def get_issue_types_for_project_with_id(project_id)
412
+ response = invoke('soap:getIssueTypesForProject') { |msg|
413
+ msg.add 'soap:in0', @auth_token
414
+ msg.add 'soap:in1', project_id
415
+ }
416
+ response.document.xpath("#{RESPONSE_XPATH}/getIssueTypesForProjectReturn").map {
417
+ |frag|
418
+ JIRA::IssueType.issue_type_with_xml_fragment frag
419
+ }
420
+ end
421
+
422
+ # @todo test this method
423
+ # @param [String] query
424
+ # @param [Fixnum] max_results
425
+ # @param [Fixnum] offset
426
+ # @return [[JIRA::Issue]]
427
+ def get_issues_from_text_search(query, max_results = 500, offset = 0)
428
+ response = invoke('soap:getIssuesFromTextSearch') { |msg|
429
+ msg.add 'soap:in0', @auth_token
430
+ msg.add 'soap:in1', query
431
+ msg.add 'soap:in2', offset
432
+ msg.add 'soap:in3', max_results
433
+ }
434
+ response.document.xpath("#{RESPONSE_XPATH}/getIssuesFromTextSearchWithLimitReturn").map {
435
+ |frag|
436
+ JIRA::Issue.issue_with_xml_fragment frag
437
+ }
438
+ end
439
+
440
+ # @todo test this method
441
+ # @param [JIRA::Comment] comment
442
+ # @return [JIRA::Comment]
443
+ def update_comment(comment)
444
+ response = invoke('soap:editComment') { |msg|
445
+ msg.add 'soap:in0', @auth_token
446
+ msg.add 'soap:in1' do |submsg| comment.soapify_for submsg end
447
+ }
448
+ frag = response.document.xpath '//editCommentReturn'
449
+ JIRA::Comment.comment_with_xml_fragment frag
450
+ end
451
+
452
+ # @todo test this method
453
+ # @return [[JIRA::IssueType]]
454
+ def get_subtask_issue_types
455
+ response = invoke('soap:getSubTaskIssueTypes') { |msg|
456
+ msg.add 'soap:in0', @auth_token
457
+ }
458
+ response.document.xpath("#{RESPONSE_XPATH}/getSubtaskIssueTypesReturn").map {
459
+ |frag|
460
+ JIRA::IssueType.issue_type_with_xml_fragment frag
461
+ }
462
+ end
463
+
464
+ # @todo test this method
465
+ # @param [String] project_id
466
+ # @return [[JIRA::IssueType]]
467
+ def get_subtask_issue_types_for_project_with_id(project_id)
468
+ response = invoke('soap:getSubTaskIssueTypesForProject') { |msg|
469
+ msg.add 'soap:in0', @auth_token
470
+ msg.add 'soap:in1', project_id
471
+ }
472
+ response.document.xpath("#{RESPONSE_XPATH}/getSubtaskIssueTypesForProjectReturn").map {
473
+ |frag|
474
+ JIRA::IssueType.issue_type_with_xml_fragment frag
475
+ }
476
+ end
477
+
478
+ # @todo test this method
479
+ # @todo find out what this method does
480
+ # @return [boolean] true if successful, throws an exception otherwise
481
+ def refresh_custom_fields
482
+ response = invoke('soap:refreshCustomFields') { |msg|
483
+ msg.add 'soap:in0', @auth_token
484
+ }
485
+ true
486
+ end
487
+
488
+ # @todo test this method
489
+ # Retrieves favourite filters for the currently logged in user.
490
+ # @return [JIRA::Filter]
491
+ def get_favourite_filters
492
+ response = invoke('soap:getFavouriteFilters') { |msg|
493
+ msg.add 'soap:in0', @auth_token
494
+ }
495
+ response.document.xpath("#{RESPONSE_XPATH}/getFavouriteFiltersReturn").map {
496
+ |frag|
497
+ JIRA::Filter.filter_with_xml_fragment frag
498
+ }
499
+ end
500
+
501
+ # @todo test this method
502
+ # @param [String] id
503
+ # @param [Fixnum] max_results
504
+ # @param [Fixnum] offset
505
+ # @return [[JIRA::Issue]]
506
+ def get_issues_from_filter_with_id(id, max_results = 500, offset = 0)
507
+ response = invoke('soap:getIssuesFromFilterWithLimit') { |msg|
508
+ msg.add 'soap:in0', @auth_token
509
+ msg.add 'soap:in1', id
510
+ msg.add 'soap:in2', offset
511
+ msg.add 'soap:in3', max_results
512
+ }
513
+ response.document.xpath("#{RESPONSE_XPATH}/getIssuesFromFilterWithLimitReturn").map {
514
+ |frag|
515
+ JIRA::Issue.issue_with_xml_fragment frag
516
+ }
517
+ end
518
+
519
+ # @todo test this method
520
+ # @param [String] project_name
521
+ # @param [JIRA::Version] version
522
+ # @return [boolean] true, throws an exception otherwise
523
+ def release_version_for_project(project_name, version)
524
+ response = invoke('soap:releaseVersion') { |msg|
525
+ msg.add 'soap:in0', @auth_token
526
+ msg.add 'soap:in1', project_name
527
+ msg.add 'soap:in2', version.id
528
+ }
529
+ true
530
+ end
531
+
532
+ # @todo test this method
533
+ # @param [String] id
534
+ # @return [Fixnum]
535
+ def get_issue_count_for_filter_with_id(id)
536
+ response = invoke('soap:getIssueCountForFilter') { |msg|
537
+ msg.add 'soap:in0', @auth_token
538
+ msg.add 'soap:in1', id
539
+ }
540
+ response.document.xpath('//getIssueCountForFilterReturn').to_s.to_i
541
+ end
542
+
543
+ # @todo test this method
544
+ # @todo optimize building the message, try a single pass
545
+ # Expect this method to be slow.
546
+ # @param [String] issue_key
547
+ # @param [[String]] filenames names to put on the files
548
+ # @param [[String]] data base64 encoded data
549
+ # @return [boolean] true if successful, otherwise an exception is thrown
550
+ def add_base64_encoded_attachments_to_issue(issue_key, filenames, data)
551
+ response = invoke('soap:addBase64EncodedAttachmentsToIssue') { |msg|
552
+ msg.add 'soap:in0', @auth_token
553
+ msg.add 'soap:in1', issue_key
554
+ msg.add 'soap:in2' do |submsg|
555
+ filenames.each { |filename| submsg.add 'filenames', filename }
556
+ end
557
+ msg.add 'soap:in3' do |submsg|
558
+ data.each { |datum| submsg.add 'base64EncodedData', datum }
559
+ end
560
+ }
561
+ true
562
+ end
563
+
564
+ # @todo test this method
565
+ # @return [JIRA::ServerInfo]
566
+ def get_server_info
567
+ response = invoke('soap:getServerInfo') { |msg|
568
+ msg.add 'soap:in0', @auth_token
569
+ }
570
+ frag = response.document.xpath '//getServerInfoReturn'
571
+ JIRA::ServerInfo.server_info_with_xml_fragment frag
572
+ end
245
573
  end
246
574
 
247
- #TODO: next block of useful methods
248
- # addBase64EncodedAttachmentsToIssue
249
- # addComment
250
- # addVersion
251
- # archiveVersion
252
- # createProject
575
+ #TODO: v0.3
253
576
  # createProjectRole
254
- # createUser
255
577
  # deleteProjectAvatar
256
- # deleteUser
257
- # editComment
258
- # getAttachmentsFromIssue
259
578
  # getAvailableActions
260
- # getComment
261
- # getComments
262
- # getComponents
263
- # getFavouriteFilters
264
- # getIssue
265
- # getIssueById
266
- # getIssueCountForFilter
267
- # getIssuesFromFilterWithLimit
268
- # getIssuesFromTextSearchWithLimit
269
- # getIssueTypesForProject
270
- # getProjectById
271
- # getServerInfo
272
- # getSubTaskIssueTypes
273
- # getSubTaskIssueTypesForProject
274
579
  # progressWorkflowAction
275
- # refreshCustomFields
276
- # releaseVersion
277
580
  # setProjectAvatar (change to different existing)
278
581
  # setNewProjectAvatar (upload new and set it)
279
- # updateProject
280
- # progressWorkflowAction
@@ -1,34 +1,50 @@
1
1
  module JIRA
2
2
 
3
+ # Represents a priority level. Straightforward.
3
4
  class Priority
4
5
  attr_accessor :id, :name, :color, :icon, :description
6
+
7
+ # Factory method that takes a fragment of a SOAP response.
8
+ # @todo change @color to be some kind of hex Fixnum object
9
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
10
+ # @return [JIRA::Priority,nil]
5
11
  def self.priority_with_xml_fragment(frag)
6
12
  return if frag.nil?
7
13
  priority = Priority.new
8
14
  priority.id = frag.xpath('id').to_s
9
15
  priority.name = frag.xpath('name').to_s
10
- priority.color = frag.xpath('color').to_s #PONDER: hex
11
- priority.icon = frag.xpath('icon').to_s #FIXME: NSURL
16
+ priority.color = frag.xpath('color').to_s
17
+ priority.icon = URL.new frag.xpath('icon').to_s
12
18
  priority.description = frag.xpath('description').to_s
13
19
  priority
14
20
  end
15
21
  end
16
22
 
23
+ # Represents a resolution. Straightforward.
17
24
  class Resolution
18
25
  attr_accessor :id, :name, :icon, :description
26
+
27
+ # Factory method that takes a fragment of a SOAP response.
28
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
29
+ # @return [JIRA::Resolution,nil]
19
30
  def self.resolution_with_xml_fragment(frag)
20
31
  return if frag.nil?
21
32
  resolution = Resolution.new
22
33
  resolution.id = frag.xpath('id').to_s
23
34
  resolution.name = frag.xpath('name').to_s
24
- resolution.icon = frag.xpath('icon').to_s #FIXME: NSURL
35
+ resolution.icon = URL.new frag.xpath('icon').to_s
25
36
  resolution.description = frag.xpath('description').to_s
26
37
  resolution
27
38
  end
28
39
  end
29
40
 
41
+ # Represents a field mapping.
30
42
  class Field
31
43
  attr_accessor :id, :name
44
+
45
+ # Factory method that takes a fragment of a SOAP response.
46
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
47
+ # @return [JIRA::Field,nil]
32
48
  def self.field_with_xml_fragment(frag)
33
49
  return if frag.nil?
34
50
  field = Field.new
@@ -38,8 +54,14 @@ class Field
38
54
  end
39
55
  end
40
56
 
57
+ # Represents a custom field with values.
58
+ # @todo see if @key is always nil from the server
41
59
  class CustomField
42
60
  attr_accessor :id, :key, :values
61
+
62
+ # Factory method that takes a fragment of a SOAP response.
63
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
64
+ # @return [JIRA::CustomField,nil]
43
65
  def self.custom_field_with_xml_fragment(frag)
44
66
  return if frag.nil?
45
67
  custom_field = CustomField.new
@@ -48,65 +70,145 @@ class CustomField
48
70
  custom_field.values = frag.xpath('values/*').map { |value| value.to_s }
49
71
  custom_field
50
72
  end
73
+
74
+ # Generate a SOAP message fragment for the object.
75
+ # @param [Handsoap::XmlMason::Node] msg SOAP message to add the object to
76
+ # @param [String] label tag name used in wrapping tags
77
+ # @return [Handsoap::XmlMason::Element]
51
78
  def soapify_for(msg, label = 'customFieldValues')
52
79
  msg.add label do |submsg|
53
80
  submsg.add 'customfieldId', @id
54
- submsg.add 'key', @key #TODO: see if this is always nil
81
+ submsg.add 'key', @key
55
82
  submsg.add_simple_array 'values', @values
56
83
  end
57
84
  end
58
85
  end
59
86
 
87
+ # Represents and issue type. Straight forward.
60
88
  class IssueType
61
89
  attr_accessor :id, :name, :icon, :description
62
90
  attr_writer :subtask
91
+
92
+ # @return [boolean] true if the issue type is a subtask, otherwise false
63
93
  def subtask?; @subtask; end
94
+
95
+ # Factory method that takes a fragment of a SOAP response.
96
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
97
+ # @return [JIRA::IssueType,nil]
64
98
  def self.issue_type_with_xml_fragment(frag)
65
99
  return if frag.nil?
66
100
  issue_type = IssueType.new
67
101
  issue_type.id = frag.xpath('id').to_s
68
102
  issue_type.name = frag.xpath('name').to_s
69
- issue_type.icon = frag.xpath('icon').to_s #FIXME: NSURL
103
+ issue_type.icon = URL.new frag.xpath('icon').to_s
70
104
  issue_type.subtask = frag.xpath('subtask').to_s == 'true'
71
105
  issue_type.description = frag.xpath('description').to_s
72
106
  issue_type
73
107
  end
74
108
  end
75
109
 
110
+ # Represents a comment. Straight forward.
111
+ class Comment
112
+ attr_accessor :id, :original_author, :role_level, :group_level, :body
113
+ attr_accessor :create_date, :last_updated, :update_author
114
+
115
+ # Factory method that takes a fragment of a SOAP response.
116
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
117
+ # @return [JIRA::Comment,nil]
118
+ def self.comment_with_xml_fragment(frag)
119
+ return if frag.nil?
120
+ comment = Comment.new
121
+ comment.id = frag.xpath('id').to_s
122
+ comment.original_author = frag.xpath('author').to_s
123
+ comment.body = frag.xpath('body').to_s
124
+ comment.create_date = Time.xmlschema frag.xpath('created').to_s
125
+ comment.group_level = frag.xpath('updateAuthor').to_s
126
+ comment.role_level = frag.xpath('roleLevel').to_s
127
+ comment.update_author = frag.xpath('updateAuthor').to_s
128
+ comment.last_updated = Time.xmlschema frag.xpath('updated').to_s
129
+ comment
130
+ end
131
+
132
+ # @param [Handsoap::XmlMason::Node] msg
133
+ # @return [Handsoap::XmlMason::Node]
134
+ def soapify_for(msg)
135
+ msg.add 'id', @id
136
+ msg.add 'author', @original_author
137
+ msg.add 'body', @body
138
+ msg.add 'created', @created
139
+ msg.add 'groupLevel', @group_level
140
+ msg.add 'roleLevel', @role_level
141
+ msg.add 'updateAuthor', @update_author
142
+ msg.add 'updated', @last_updated
143
+ end
144
+ end
145
+
146
+ # Represents a status. Straightforward.
76
147
  class Status
77
148
  attr_accessor :id, :name, :icon, :description
149
+
150
+ # Factory method that takes a fragment of a SOAP response.
151
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
152
+ # @return [JIRA::Status,nil]
78
153
  def self.status_with_xml_fragment(frag)
79
154
  return if frag.nil?
80
155
  status = Status.new
81
156
  status.id = frag.xpath('id').to_s
82
157
  status.name = frag.xpath('name').to_s
83
- status.icon = frag.xpath('icon').to_s #FIXME: NSURL
158
+ status.icon = URL.new frag.xpath('icon').to_s
84
159
  status.description = frag.xpath('description').to_s
85
160
  status
86
161
  end
87
162
  end
88
163
 
164
+ # Represents a version for a project. Straightforward.
165
+ # @todo find out why we don't get a description for this object
89
166
  class Version
90
167
  attr_accessor :id, :name, :sequence, :released, :archived, :release_date
91
168
  attr_writer :released, :archived
169
+
170
+ # @return [boolean] true if the version has been released, otherwise false
92
171
  def released?; @released; end
172
+
173
+ # @return [boolean] true if the version has been archive, otherwise false
93
174
  def archived?; @archived; end
175
+
176
+ # Factory method that takes a fragment of a SOAP response.
177
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
178
+ # @return [JIRA::Status,nil]
94
179
  def self.version_with_xml_fragment(frag)
95
180
  return if frag.nil?
96
- #TODO: find out why we don't get a description for this type
97
181
  version = Version.new
98
182
  version.id = frag.xpath('id').to_s
99
183
  version.name = frag.xpath('name').to_s
100
184
  version.sequence = frag.xpath('sequence').to_s.to_i
101
185
  version.released = frag.xpath('released').to_s == 'true'
102
186
  version.archived = frag.xpath('archived').to_s == 'true'
103
- version.release_date = frag.xpath('releaseDate').to_s #FIXME: NSDate
187
+ version.release_date = Time.xmlschema frag.xpath('releaseDate')
104
188
  version
105
189
  end
190
+
191
+ # @param [Handsoap::XmlMason::Node] msg
192
+ # @return [Handsoap::XmlMason::Node]
193
+ def soapify_for(msg)
194
+ msg.add 'id', @id
195
+ msg.add 'name', @name
196
+ msg.add 'sequence', @sequence
197
+ msg.add 'released', @released
198
+ msg.add 'archived', @archived
199
+ msg.add 'releaseDate', @release_date
200
+ end
106
201
  end
107
202
 
203
+ # Represents a scheme used by the server. Not very useful for the sake of the
204
+ # API; a more useful case might be if you wanted to emulate the server's
205
+ # behaviour.
108
206
  class Scheme
109
207
  attr_accessor :id, :name, :type, :description
208
+
209
+ # Factory method that takes a fragment of a SOAP response.
210
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
211
+ # @return [JIRA::Scheme,nil]
110
212
  def self.scheme_with_xml_fragment(frag)
111
213
  return if frag.nil?
112
214
  scheme = Scheme.new
@@ -118,8 +220,13 @@ class Scheme
118
220
  end
119
221
  end
120
222
 
223
+ # Represents a component description for a project. Straightforward.
121
224
  class Component
122
225
  attr_accessor :id, :name
226
+
227
+ # Factory method that takes a fragment of a SOAP response.
228
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
229
+ # @return [JIRA::Component,nil]
123
230
  def self.component_with_xml_fragment(frag)
124
231
  return if frag.nil?
125
232
  component = Component.new
@@ -129,20 +236,25 @@ class Component
129
236
  end
130
237
  end
131
238
 
239
+ # Represents a project configuration. NOT straightforward.
240
+ # @todo find out why the server always seems to pass nil for schemes
132
241
  class Project
133
242
  attr_accessor :id, :name, :key, :url, :project_url, :lead, :description
134
243
  attr_accessor :issue_security_scheme, :notification_scheme, :permission_scheme
244
+
245
+ # Factory method that takes a fragment of a SOAP response.
246
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
247
+ # @return [JIRA::Project,nil]
135
248
  def self.project_with_xml_fragment(frag)
136
249
  return if frag.nil?
137
250
  project = Project.new
138
251
  project.id = frag.xpath('id').to_s
139
252
  project.name = frag.xpath('name').to_s
140
253
  project.key = frag.xpath('key').to_s
141
- project.url = frag.xpath('url').to_s #FIXME: NSURL
142
- project.project_url = frag.xpath('projectUrl').to_s #FIXME: NSURL
254
+ project.url = URL.new frag.xpath('url').to_s
255
+ project.project_url = URL.new frag.xpath('projectUrl').to_s
143
256
  project.lead = frag.xpath('lead').to_s
144
257
  project.description = frag.xpath('description').to_s
145
- #TODO: find out why the server always seems to pass nil
146
258
  project.issue_security_scheme =
147
259
  Scheme.scheme_with_xml_fragment frag.xpath 'issueSecurityScheme'
148
260
  project.notification_scheme =
@@ -151,12 +263,32 @@ class Project
151
263
  Scheme.scheme_with_xml_fragment frag.xpath 'permissionScheme'
152
264
  project
153
265
  end
266
+
267
+ # @todo encode the schemes
268
+ # @param [Handsoap::XmlMason::Node] msg
269
+ # @return [Handsoap::XmlMason::Node]
270
+ def soapify_for(msg)
271
+ msg.add 'id', @id
272
+ msg.add 'name', @name
273
+ msg.add 'key', @key
274
+ msg.add 'url', @url
275
+ msg.add 'projectUrl', @project_url
276
+ msg.add 'lead', @lead
277
+ msg.add 'description', @description
278
+ end
154
279
  end
155
280
 
281
+ # Contains a base64 encoded avatar image and some metadata. Straightforward.
156
282
  class Avatar
157
283
  attr_accessor :id, :owner, :type, :content_type, :base64_data
158
284
  attr_writer :system
285
+
286
+ # @return [boolean] true if avatar is the default system avatar, else false
159
287
  def system?; @system; end
288
+
289
+ # Factory method that takes a fragment of a SOAP response.
290
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
291
+ # @return [JIRA::Avatar,nil]
160
292
  def self.avatar_with_xml_fragment(frag)
161
293
  return if frag.nil?
162
294
  avatar = Avatar.new
@@ -170,14 +302,24 @@ class Avatar
170
302
  end
171
303
  end
172
304
 
173
- #easily the most convoluted structure in the API
174
- #will most likely be the greatest source of bugs
305
+ # Represents a JIRA issue; easily the most convoluted structure in the API.
306
+ # This structure and anything related directly to it will most likely be the
307
+ # greatest source of bugs.
308
+ #
309
+ # The irony of the situation is that this structure is also the most critical
310
+ # to have in working order.
311
+ #
312
+ # Issues with an UNRESOLVED status will have nil for the value of @resolution.
175
313
  class Issue
176
314
  attr_accessor :id, :key, :summary, :description, :type_id, :last_updated
177
315
  attr_accessor :votes, :status_id, :assignee_name, :reporter_name, :priority_id
178
316
  attr_accessor :project_name, :affects_versions, :create_date, :due_date
179
317
  attr_accessor :fix_versions, :resolution_id, :environment, :components
180
318
  attr_accessor :attachment_names, :custom_field_values
319
+
320
+ # Factory method that takes a fragment of a SOAP response.
321
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
322
+ # @return [JIRA::Issue,nil]
181
323
  def self.issue_with_xml_fragment(frag)
182
324
  return if frag.nil?
183
325
  issue = Issue.new
@@ -201,28 +343,42 @@ class Issue
201
343
  issue.summary = frag.xpath('summary').to_s
202
344
  issue.description = frag.xpath('description').to_s
203
345
  issue.type_id = frag.xpath('type').to_s
204
- issue.last_updated = frag.xpath('updated').to_s
346
+ issue.last_updated = Time.xmlschema frag.xpath('updated').to_s
205
347
  issue.votes = frag.xpath('votes').to_s.to_i
206
348
  issue.status_id = frag.xpath('status').to_s
207
349
  issue.assignee_name = frag.xpath('assignee').to_s
208
350
  issue.reporter_name = frag.xpath('reporter').to_s
209
351
  issue.priority_id = frag.xpath('priority').to_s
210
352
  issue.project_name = frag.xpath('project').to_s
211
- issue.create_date = frag.xpath('created').to_s #FIXME: NSDate
212
- issue.due_date = frag.xpath('duedate').to_s #FIXME: NSDate
353
+ issue.create_date = Time.xmlschema frag.xpath('created').to_s
354
+ issue.due_date = Time.xmlschema frag.xpath('duedate').to_s
213
355
  issue.resolution_id = frag.xpath('resolution').to_s
214
356
  issue.environment = frag.xpath('environment').to_s
215
357
  issue
216
358
  end
217
- #can you spot the oddities and inconsistencies? (hint: there are many)
218
- def soapify_for(msg)
219
- #we don't both including fields that are ignored
220
- #I tried to only ignore fields that will never be needed at creation
221
- #but I may have messed up. It should be easy to fix :)
222
- #we don't wrap the whole thing in 'issue' tags for #create_issue calls
223
- #this is an inconsistency in the way jiraSOAP works
224
- #but you'll probably never know unless you read the source
225
359
 
360
+ # Generate the SOAP message fragment for an issue. Can you spot the oddities
361
+ # and inconsistencies? (hint: there are many).
362
+ #
363
+ # We don't bother including fields that are ignored. I tried to only
364
+ # ignore fields that will never be needed at creation time, but I may have
365
+ # messed up.
366
+ #
367
+ # We don't wrap the whole thing in 'issue' tags for
368
+ # {#RemoteAPI#create_issue_with_issue} calls; this is an inconsistency in the
369
+ # way jiraSOAP works and may need to be worked around for other {RemoteAPI}
370
+ # methods.
371
+ #
372
+ # Servers only seem to accept issues if components/versions are just ids
373
+ # and do not contain the rest of the {JIRA::Component}/{JIRA::Version}
374
+ # structure.
375
+ #
376
+ # To get the automatic assignee we pass '-1' as the value for @assignee.
377
+ #
378
+ # Passing an environment/due date field with a value of nil causes the
379
+ # server to complain about the formatting of the message.
380
+ # @param [Handsoap::XmlMason::Node] msg message the node to add the object to
381
+ def soapify_for(msg)
226
382
  #might be going away, since it appears to have no effect at creation time
227
383
  msg.add 'reporter', @reporter_name unless @reporter.nil?
228
384
 
@@ -233,7 +389,6 @@ class Issue
233
389
  msg.add 'summary', @summary
234
390
  msg.add 'description', @description
235
391
 
236
- #server only accepts issues if components/versions are just ids
237
392
  msg.add 'components' do |submsg|
238
393
  (@components || []).each { |component|
239
394
  submsg.add 'components' do |component_msg|
@@ -255,19 +410,21 @@ class Issue
255
410
  }
256
411
  end
257
412
 
258
- #-1 is the value you send to get the automatic assignee
259
413
  msg.add 'assignee', (@assignee_name || '-1')
260
-
261
414
  msg.add_complex_array 'customFieldValues', (@custom_field_values || [])
262
415
 
263
- #passing environment/due_date when nil seems to mess up the server
264
416
  msg.add 'environment', @environment unless @environment.nil?
265
- msg.add 'duedate', @due_date unless @due_date.nil?
417
+ msg.add 'duedate', @due_date.xmlschema unless @due_date.nil?
266
418
  end
267
419
  end
268
420
 
421
+ # Contains the basic information about a user. Straightforward.
269
422
  class User
270
423
  attr_accessor :name, :full_name, :email
424
+
425
+ # Factory method that takes a fragment of a SOAP response.
426
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
427
+ # @return [JIRA::User,nil]
271
428
  def self.user_with_xml_fragment(frag)
272
429
  return if frag.nil?
273
430
  user = User.new
@@ -278,14 +435,25 @@ class User
278
435
  end
279
436
  end
280
437
 
438
+ # A structure that is a bit of a hack. It is essentially just a key-value pair
439
+ # that is used mainly by {RemoteAPI#update_issue}.
281
440
  class FieldValue
282
441
  attr_accessor :id, :values
442
+
443
+ # Factory method that gives you a nil value for the given id.
444
+ # @param [String] id name of the field for @values
445
+ # @return [JIRA::FieldValue] Will always have @values = [nil]
283
446
  def self.field_value_with_nil_values(id)
284
447
  fv = FieldValue.new
285
448
  fv.id = id
286
449
  fv.values = [nil]
287
450
  fv
288
451
  end
452
+
453
+ # Generate the SOAP message fragment for a field value.
454
+ # @param [Handsoap::XmlMason::Node] message the node to add the object to
455
+ # @param [String] label name for the tags that wrap the message
456
+ # @return [Handsoap::XmlMason::Element]
289
457
  def soapify_for(message, label = 'fieldValue')
290
458
  message.add label do |message|
291
459
  message.add 'id', @id
@@ -294,4 +462,85 @@ class FieldValue
294
462
  end
295
463
  end
296
464
 
465
+ # Only contains the meta-data for an attachment. The URI for an attachment
466
+ # appears to be of the form
467
+ # $ENDPOINT_URL/secure/attachment/$ATTACHMENT_ID/$ATTACHMENT_FILENAME
468
+ class AttachmentMetadata
469
+ attr_accessor :id, :author, :create_date, :filename, :file_size, :mime_type
470
+
471
+ # Factory method that takes a fragment of a SOAP response.
472
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
473
+ # @return [JIRA::Attachment,nil]
474
+ def self.attachment_with_xml_fragment(frag)
475
+ return if frag.nil?
476
+ attachment = Attachment.new
477
+ attachment.id = frag.xpath('id').to_s
478
+ attachment.author = frag.xpath('author').to_s
479
+ attachment.create_date = Time.xmlschema frag.xpath('created').to_s
480
+ attachment.filename = frag.xpath('filename').to_s
481
+ attachment.file_size = frag.xpath('filesize').to_s.to_i
482
+ attachment.mime_type = frag.xpath('mimetype').to_s
483
+ attachment
484
+ end
485
+ end
486
+
487
+ # Only contains basic information about the endpoint server.
488
+ class ServerInfo
489
+ attr_accessor :base_url, :build_date, :build_number, :edition
490
+ attr_accessor :server_time, :version
491
+
492
+ # Factory method that takes a fragment of a SOAP response.
493
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
494
+ # @return [JIRA::ServerInfo,nil]
495
+ def self.server_info_with_xml_fragment(frag)
496
+ return if frag.nil?
497
+ server_info = ServerInfo.new
498
+ server_info.base_url = URL.new frag.xpath('baseUrl').to_s
499
+ server_info.build_date = Time.xmlschema frag.xpath('buildDate').to_s
500
+ server_info.build_number = frag.xpath('buildNumber').to_s.to_i
501
+ server_info.edition = frag.xpath('edition').to_s
502
+ server_info.version = frag.xpath('version').to_s
503
+ server_info.server_time =
504
+ TimeInfo.time_info_with_xml_fragment frag.xpath 'serverTime'
505
+ server_info
506
+ end
507
+ end
508
+
509
+ # Simple structure for a time and time zone; used oddly.
510
+ class TimeInfo
511
+ attr_accessor :server_time, :timezone
512
+
513
+ # Factory method that takes a fragment of a SOAP response.
514
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
515
+ # @return [JIRA::TimeInfo,nil]
516
+ def self.time_info_with_xml_fragment(frag)
517
+ return if frag.nil?
518
+ time_info = TimeInfo.new
519
+ time_info.server_time = Time.parse frag.xpath('serverTime').to_s
520
+ time_info.timezone = frag.xpath('timeZoneId').to_s
521
+ time_info
522
+ end
523
+ end
524
+
525
+ # Represents a filter
526
+ # @todo find out what @project is supposed to be for
527
+ class Filter
528
+ attr_accessor :id, :name, :author, :project, :description, :xml
529
+
530
+ # Factory method that takes a fragment of a SOAP response.
531
+ # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag
532
+ # @return [JIRA::Filter,nil]
533
+ def self.filter_with_xml_fragment(frag)
534
+ return if frag.nil?
535
+ filter = Filter.new
536
+ filter.id = frag.xpath('id').to_s
537
+ filter.name = frag.xpath('name').to_s
538
+ filter.author = frag.xpath('author').to_s
539
+ filter.project = frag.xpath('project').to_s
540
+ filter.description = frag.xpath('description').to_s
541
+ filter.xml = frag.xpath('xml').to_s
542
+ filter
543
+ end
544
+ end
545
+
297
546
  end
@@ -0,0 +1,21 @@
1
+ # A bit of a hack to support using an NSURL in MacRuby while still supporting
2
+ # MRI by using the URI module.
3
+ #
4
+ # I suggest not thinking about it too much beyond this point: this is a
5
+ # URI object if you are running on CRuby, but it will be an NSURL if you
6
+ # are running on MacRuby.
7
+ class URL
8
+ attr_accessor :url
9
+
10
+ # Initializes @url with the correct object type.
11
+ # @param [String] url string to turn into some kind of URL object
12
+ # @return [URI::HTTP,NSURL] URI::HTTP on CRuby, NSURL on MacRuby
13
+ def initialize(url)
14
+ @url = URI.parse url
15
+ end
16
+
17
+ # The magic of the hack, passing everything to the level beneath.
18
+ def method_missing(method, *args)
19
+ @url.send method, *args
20
+ end
21
+ end
data/lib/jiraSOAP.rb CHANGED
@@ -1,7 +1,9 @@
1
- #TODO: set a requirement on the handsoap version
2
1
  require 'handsoap'
3
2
  require 'logger'
3
+ require 'time'
4
+ require 'uri'
4
5
 
6
+ require 'jiraSOAP/url.rb'
5
7
  require 'jiraSOAP/handsoap_extensions.rb'
6
8
  require 'jiraSOAP/remoteEntities.rb'
7
9
  require 'jiraSOAP/remoteAPI.rb'
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
7
  - 2
9
- version: 0.1.2
8
+ - 0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Mark Rada
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-10-06 00:00:00 -04:00
17
+ date: 2010-10-12 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -32,7 +32,7 @@ dependencies:
32
32
  type: :runtime
33
33
  version_requirements: *id001
34
34
  - !ruby/object:Gem::Dependency
35
- name: minitest
35
+ name: nokogiri
36
36
  prerelease: false
37
37
  requirement: &id002 !ruby/object:Gem::Requirement
38
38
  none: false
@@ -42,8 +42,34 @@ dependencies:
42
42
  segments:
43
43
  - 0
44
44
  version: "0"
45
- type: :development
45
+ type: :runtime
46
46
  version_requirements: *id002
47
+ - !ruby/object:Gem::Dependency
48
+ name: minitest
49
+ prerelease: false
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id003
60
+ - !ruby/object:Gem::Dependency
61
+ name: yard
62
+ prerelease: false
63
+ requirement: &id004 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ segments:
69
+ - 0
70
+ version: "0"
71
+ type: :development
72
+ version_requirements: *id004
47
73
  description: Written to run fast and work on Ruby 1.9 as well as MacRuby
48
74
  email: mrada@marketcircle.com
49
75
  executables: []
@@ -60,6 +86,7 @@ files:
60
86
  - lib/jiraSOAP/macruby_stuff.rb
61
87
  - lib/jiraSOAP/remoteAPI.rb
62
88
  - lib/jiraSOAP/remoteEntities.rb
89
+ - lib/jiraSOAP/url.rb
63
90
  - LICENSE
64
91
  - README.markdown
65
92
  - test/jiraSOAP_test.rb