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 +2 -5
- data/lib/jiraSOAP/JIRAservice.rb +12 -8
- data/lib/jiraSOAP/handsoap_extensions.rb +12 -12
- data/lib/jiraSOAP/macruby_stuff.rb +12 -1
- data/lib/jiraSOAP/remoteAPI.rb +390 -89
- data/lib/jiraSOAP/remoteEntities.rb +278 -29
- data/lib/jiraSOAP/url.rb +21 -0
- data/lib/jiraSOAP.rb +3 -1
- metadata +32 -5
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
|
data/lib/jiraSOAP/JIRAservice.rb
CHANGED
@@ -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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def self.
|
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
|
-
|
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
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|
data/lib/jiraSOAP/remoteAPI.rb
CHANGED
@@ -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
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
27
|
-
#
|
28
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
109
|
-
|
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
|
-
#
|
123
|
-
|
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
|
-
#
|
135
|
-
|
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
|
-
#
|
146
|
-
#the avatars for a project, use {#get_project_avatars_for_key}.
|
147
|
-
|
148
|
-
|
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
|
-
#
|
158
|
-
#just want the default avatar, use {#get_project_avatar_for_key}.
|
159
|
-
|
160
|
-
|
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
|
-
|
175
|
-
|
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
|
-
|
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
|
-
#
|
198
|
-
|
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
|
-
|
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
|
-
|
197
|
+
# @example Setting a field to be blank/nil
|
207
198
|
# description = JIRA::FieldValue.field_value_with_nil_values 'description'
|
208
|
-
|
199
|
+
# @example Calling the method to update an issue
|
209
200
|
# jira_service_instance.update_issue 'PROJECT-1', description, custom_field
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
-
# -
|
218
|
+
# - resolution
|
228
219
|
# - attachments
|
229
220
|
# - votes
|
230
221
|
# - status
|
231
|
-
# -
|
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
|
-
|
234
|
-
|
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:
|
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
|
11
|
-
priority.icon = frag.xpath('icon').to_s
|
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
|
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
|
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
|
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
|
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')
|
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
|
142
|
-
project.project_url = frag.xpath('projectUrl').to_s
|
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
|
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
|
212
|
-
issue.due_date = frag.xpath('duedate').to_s
|
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
|
data/lib/jiraSOAP/url.rb
ADDED
@@ -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
|
-
|
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-
|
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:
|
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: :
|
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
|