jiraSOAP 0.1.2 → 0.2.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.
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