jiraSOAP 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.markdown +101 -0
- data/lib/jiraSOAP.rb +12 -0
- data/lib/jiraSOAP/JIRAservice.rb +38 -0
- data/lib/jiraSOAP/handsoap_extensions.rb +19 -0
- data/lib/jiraSOAP/macruby_stuff.rb +6 -0
- data/lib/jiraSOAP/remoteAPI.rb +189 -0
- data/lib/jiraSOAP/remoteEntities.rb +297 -0
- data/test/jiraSOAP_test.rb +7 -0
- data/test/test_helper.rb +11 -0
- metadata +103 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Mark Rada
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
jiraSOAP - Ruby interface to the JIRA SOAP API
|
2
|
+
==============================================
|
3
|
+
|
4
|
+
Uses [handsoap](http://wiki.github.com/unwire/handsoap/) to build a client for the JIRA SOAP API that works on MacRuby as well as Ruby 1.9.
|
5
|
+
|
6
|
+
|
7
|
+
Motivation
|
8
|
+
----------
|
9
|
+
|
10
|
+
The `jira4r` gem already exists, and works well on Ruby 1.8, but is not compatible with Ruby 1.9 or MacRuby due to its dependance on `soap4r`.
|
11
|
+
|
12
|
+
|
13
|
+
Goals
|
14
|
+
-----
|
15
|
+
|
16
|
+
Pick up where `jira4r` left off:
|
17
|
+
|
18
|
+
- Implement the current API; `jira4r` does not implement APIs from JIRA 4.x
|
19
|
+
- More natural interface; not adhering to the API when the API is weird
|
20
|
+
- Speed; network latency is bad enough
|
21
|
+
|
22
|
+
|
23
|
+
Getting Started
|
24
|
+
---------------
|
25
|
+
|
26
|
+
`jiraSOAP` should run on Ruby 1.9.2 and MacRuby 0.7. Right now you need to build from source, it will be available from gemcutter sometime around the 0.1.0 or 0.2.0 release.
|
27
|
+
|
28
|
+
git clone git://github.com/Marketcircle/jiraSOAP.git
|
29
|
+
rake build
|
30
|
+
rake install
|
31
|
+
|
32
|
+
Once that ugliness is over with, you can run a quick demo (making appropriate substitutions):
|
33
|
+
|
34
|
+
require 'jiraSOAP'
|
35
|
+
|
36
|
+
db = JIRA::JIRAService.new 'http://jira.yourSite.com:8080'
|
37
|
+
db.login 'user', 'password'
|
38
|
+
|
39
|
+
issues = db.get_issues_from_jql_search 'reporter = currentUser()', 100
|
40
|
+
issues.each { |issue|
|
41
|
+
#do something...
|
42
|
+
puts issue.key
|
43
|
+
}
|
44
|
+
|
45
|
+
db.logout
|
46
|
+
|
47
|
+
Get the [Gist](http://gist.github.com/612186).
|
48
|
+
|
49
|
+
|
50
|
+
Notes About Using This Gem
|
51
|
+
--------------------------
|
52
|
+
|
53
|
+
To get a reference for the API, you can look at the JavaDoc stuff provided by Atalssian [here](http://docs.atlassian.com/software/jira/docs/api/rpc-jira-plugin/latest/com/atlassian/jira/rpc/soap/JiraSoapService.html).
|
54
|
+
|
55
|
+
1. All method names have been made to feel more natural in a Ruby setting. Consult the `jiraSOAP` documentation for specifics.
|
56
|
+
|
57
|
+
2. If an API call fails with a method missing error it is because the method has not been implement, yet. I started by implementing only the methods that I needed in order to port some old scripts that ran on jira4r; other methods will be added as them gem matures (or you could add it for me :D).
|
58
|
+
|
59
|
+
3. URESOLVED issues have a Resolution with a value of `nil`.
|
60
|
+
|
61
|
+
4. To empty a field (set it to nil) you can use this pattern:
|
62
|
+
jira.update_issue 'JIRA-1', JIRA::FieldValue.fieldValueWithNilValues 'description'
|
63
|
+
|
64
|
+
5. Issue creation, using #create_issue_with_issue does not make use of all the fields in a JIRA::Issue. Which fields are used seems to depend on the version of JIRA you are connecting to.
|
65
|
+
|
66
|
+
6. RemoteAPI#update_issue wants an id for each field that you pass it, but it really wants the name of the field that you want to update. See this [Gist](http://gist.github.com/612562).
|
67
|
+
|
68
|
+
|
69
|
+
TODO
|
70
|
+
----
|
71
|
+
|
72
|
+
- Performance optimizations; there are a number of places that can be optimized
|
73
|
+
+ Using GCD/Threads for parsing arrays of results; a significant speed up for large types and large arrays (ie. creating issues from JQL searches)
|
74
|
+
- Refactor for a smaller code base
|
75
|
+
- Fix type hacks;. dates should be `NSDate`s and URLs should be `NSURL`s, right now they are all strings
|
76
|
+
- Public test suite
|
77
|
+
+ Needs a mock server
|
78
|
+
- Documentation
|
79
|
+
- Error handling
|
80
|
+
- Finish implementing all of the API
|
81
|
+
|
82
|
+
|
83
|
+
Note on Patches/Pull Requests
|
84
|
+
-----------------------------
|
85
|
+
|
86
|
+
* Fork the project.
|
87
|
+
* Make your feature addition or bug fix.
|
88
|
+
* Add tests for it. This is important so I don't break it in a
|
89
|
+
future version unintentionally.
|
90
|
+
* Commit, do not mess with rakefile, version, or history.
|
91
|
+
(if you want to have your own version, that is fine but
|
92
|
+
bump version in a commit by itself I can ignore when I pull)
|
93
|
+
* Send me a pull request. Bonus points for topic branches.
|
94
|
+
|
95
|
+
|
96
|
+
License
|
97
|
+
-------
|
98
|
+
|
99
|
+
Copyright: [Marketcircle Inc.](http://www.marketcircle.com/), 2010
|
100
|
+
|
101
|
+
See LICENSE for details.
|
data/lib/jiraSOAP.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#TODO: set a requirement on the handsoap version
|
2
|
+
require 'handsoap'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
require 'jiraSOAP/handsoap_extensions.rb'
|
6
|
+
require 'jiraSOAP/remoteEntities.rb'
|
7
|
+
require 'jiraSOAP/remoteAPI.rb'
|
8
|
+
|
9
|
+
require 'jiraSOAP/JIRAservice.rb'
|
10
|
+
|
11
|
+
#overrides and additions
|
12
|
+
require 'lib/macruby_stuff.rb' if RUBY_ENGINE == 'macruby'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module JIRA
|
2
|
+
class JIRAService < Handsoap::Service
|
3
|
+
include RemoteAPI
|
4
|
+
|
5
|
+
attr_reader :auth_token, :user
|
6
|
+
|
7
|
+
def self.instance_at_url(url, user, password)
|
8
|
+
jira = JIRAService.new url
|
9
|
+
jira.login user, password
|
10
|
+
jira
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(endpoint_url)
|
14
|
+
super
|
15
|
+
|
16
|
+
@endpoint_url = endpoint_url
|
17
|
+
endpoint_data = {
|
18
|
+
:uri => "#{endpoint_url}/rpc/soap/jirasoapservice-v2",
|
19
|
+
:version => 2
|
20
|
+
}
|
21
|
+
self.class.endpoint endpoint_data
|
22
|
+
end
|
23
|
+
|
24
|
+
#PONDER: a finalizer that will try to logout
|
25
|
+
|
26
|
+
def method_missing(method, *args)
|
27
|
+
$stderr.puts "#{method} is not a defined method in the API...yet"
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
def on_create_document(doc)
|
32
|
+
doc.alias 'soap', 'http://soap.rpc.jira.atlassian.com'
|
33
|
+
end
|
34
|
+
def on_response_document(doc)
|
35
|
+
doc.add_namespace 'jir', @endpoint_url
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#PONDER: send upstream for consideration
|
2
|
+
module Handsoap
|
3
|
+
module XmlMason
|
4
|
+
class Node
|
5
|
+
#TODO: make recursive
|
6
|
+
def add_simple_array(node_name, array = [], options = {})
|
7
|
+
prefix, name = parse_ns(node_name)
|
8
|
+
node = append_child Element.new(self, prefix, name, nil, options)
|
9
|
+
array.each { |element| node.add node_name, element }
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_complex_array(node_name, array = [], options = {})
|
13
|
+
prefix, name = parse_ns(node_name)
|
14
|
+
node = append_child Element.new(self, prefix, name, nil, options)
|
15
|
+
array.each { |element| element.soapify_for node, name }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module RemoteAPI
|
2
|
+
RESPONSE_XPATH = '/node()[1]/node()[1]/node()[1]/node()[2]'
|
3
|
+
|
4
|
+
def login(user, password)
|
5
|
+
response = invoke('soap:login') { |msg|
|
6
|
+
msg.add 'soap:in0', user
|
7
|
+
msg.add 'soap:in1', password
|
8
|
+
}
|
9
|
+
#TODO: error handling (catch the exception and look at the Response node?)
|
10
|
+
#cache now that we know it is safe to do so
|
11
|
+
@user = user
|
12
|
+
@auth_token = response.document.xpath('//loginReturn').first.to_s
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def logout
|
17
|
+
response = invoke('soap:logout') { |msg|
|
18
|
+
msg.add 'soap:in0', @auth_token
|
19
|
+
}
|
20
|
+
response.document.xpath('//logoutReturn').first.to_s == 'true'
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_priorities
|
24
|
+
response = invoke('soap:getPriorities') { |msg|
|
25
|
+
msg.add 'soap:in0', @auth_token
|
26
|
+
}
|
27
|
+
response.document.xpath("#{RESPONSE_XPATH}/getPrioritiesReturn").map {
|
28
|
+
|frag|
|
29
|
+
JIRA::Priority.priority_with_xml_fragment frag
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_resolutions
|
34
|
+
response = invoke('soap:getResolutions') { |msg|
|
35
|
+
msg.add 'soap:in0', @auth_token
|
36
|
+
}
|
37
|
+
response.document.xpath("#{RESPONSE_XPATH}/getResolutionsReturn").map {
|
38
|
+
|frag|
|
39
|
+
JIRA::Resolution.resolution_with_xml_fragment frag
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_custom_fields
|
44
|
+
response = invoke('soap:getCustomFields') { |msg|
|
45
|
+
msg.add 'soap:in0', @auth_token
|
46
|
+
}
|
47
|
+
response.document.xpath("#{RESPONSE_XPATH}/getCustomFieldsReturn").map {
|
48
|
+
|frag|
|
49
|
+
JIRA::Field.field_with_xml_fragment frag
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_issue_types
|
54
|
+
response = invoke('soap:getIssueTypes') { |msg|
|
55
|
+
msg.add 'soap:in0', @auth_token
|
56
|
+
}
|
57
|
+
response.document.xpath("#{RESPONSE_XPATH}/getIssueTypesReturn").map {
|
58
|
+
|frag|
|
59
|
+
JIRA::IssueType.issue_type_with_xml_fragment frag
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_statuses
|
64
|
+
response = invoke('soap:getStatuses') { |msg|
|
65
|
+
msg.add 'soap:in0', @auth_token
|
66
|
+
}
|
67
|
+
response.document.xpath("#{RESPONSE_XPATH}/getStatusesReturn").map {
|
68
|
+
|frag|
|
69
|
+
JIRA::Status.status_with_xml_fragment frag
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_notification_schemes
|
74
|
+
response = invoke('soap:getNotificationSchemes') { |msg|
|
75
|
+
msg.add 'soap:in0', @auth_token
|
76
|
+
}
|
77
|
+
response.document.xpath("#{RESPONSE_XPATH}/getNotificationSchemesReturn").map {
|
78
|
+
|frag|
|
79
|
+
JIRA::Scheme.scheme_with_xml_fragment frag
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_versions_for_project(project_key)
|
84
|
+
response = invoke('soap:getVersions') { |msg|
|
85
|
+
msg.add 'soap:in0', @auth_token
|
86
|
+
msg.add 'soap:in1', project_key
|
87
|
+
}
|
88
|
+
response.document.xpath("#{RESPONSE_XPATH}/getVersionsReturn").map {
|
89
|
+
|frag|
|
90
|
+
JIRA::Version.version_with_xml_fragment frag
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_project_with_key(project_key)
|
95
|
+
response = invoke('soap:getProjectByKey') { |msg|
|
96
|
+
msg.add 'soap:in0', @auth_token
|
97
|
+
msg.add 'soap:in1', project_key
|
98
|
+
}
|
99
|
+
frag = response.document.xpath '//getProjectByKeyReturn'
|
100
|
+
JIRA::Project.project_with_xml_fragment frag
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_user_with_name(user_name)
|
104
|
+
response = invoke('soap:getUser') { |msg|
|
105
|
+
msg.add 'soap:in0', @auth_token
|
106
|
+
msg.add 'soap:in1', user_name
|
107
|
+
}
|
108
|
+
JIRA::User.user_with_xml_fragment response.document.xpath '//getUserReturn'
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_project_avatar_for_key(project_key)
|
112
|
+
response = invoke('soap:getProjectAvatar') { |msg|
|
113
|
+
msg.add 'soap:in0', @auth_token
|
114
|
+
msg.add 'soap:in1', project_key
|
115
|
+
}
|
116
|
+
JIRA::Avatar.avatar_with_xml_fragment response.document.xpath '//getProjectAvatarReturn'
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_issues_from_jql_search(jql_query, max_results = 500)
|
120
|
+
response = invoke('soap:getIssuesFromJqlSearch') { |msg|
|
121
|
+
msg.add 'soap:in0', @auth_token
|
122
|
+
msg.add 'soap:in1', jql_query
|
123
|
+
msg.add 'soap:in2', max_results
|
124
|
+
}
|
125
|
+
response.document.xpath("#{RESPONSE_XPATH}/getIssuesFromJqlSearchReturn").map {
|
126
|
+
|frag|
|
127
|
+
JIRA::Issue.issue_with_xml_fragment frag
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
def update_issue(issue_key, *field_values)
|
132
|
+
response = invoke('soap:updateIssue') { |msg|
|
133
|
+
msg.add 'soap:in0', @auth_token
|
134
|
+
msg.add 'soap:in1', issue_key
|
135
|
+
msg.add 'soap:in2' do |submsg|
|
136
|
+
field_values.each { |fv| fv.soapify_for submsg }
|
137
|
+
end
|
138
|
+
}
|
139
|
+
frag = response.document.xpath '//updateIssueReturn'
|
140
|
+
JIRA::Issue.issue_with_xml_fragment frag
|
141
|
+
end
|
142
|
+
|
143
|
+
def create_issue_with_issue(issue)
|
144
|
+
response = invoke('soap:createIssue') { |msg|
|
145
|
+
msg.add 'soap:in0', @auth_token
|
146
|
+
msg.add 'soap:in1' do |submsg|
|
147
|
+
issue.soapify_for submsg
|
148
|
+
end
|
149
|
+
}
|
150
|
+
frag = response.document.xpath '//createIssueReturn'
|
151
|
+
JIRA::Issue.issue_with_xml_fragment frag
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
#TODO: next block of useful methods
|
156
|
+
# addBase64EncodedAttachmentsToIssue
|
157
|
+
# addComment
|
158
|
+
# addVersion
|
159
|
+
# archiveVersion
|
160
|
+
# createProject
|
161
|
+
# createProjectRole
|
162
|
+
# createUser
|
163
|
+
# deleteProjectAvatar
|
164
|
+
# deleteUser
|
165
|
+
# editComment
|
166
|
+
# getAttachmentsFromIssue
|
167
|
+
# getAvailableActions
|
168
|
+
# getComment
|
169
|
+
# getComments
|
170
|
+
# getComponents
|
171
|
+
# getFavouriteFilters
|
172
|
+
# getIssue
|
173
|
+
# getIssueById
|
174
|
+
# getIssueCountForFilter
|
175
|
+
# getIssuesFromFilterWithLimit
|
176
|
+
# getIssuesFromTextSearchWithLimit
|
177
|
+
# getIssueTypesForProject
|
178
|
+
# getProjectAvatars
|
179
|
+
# getProjectById
|
180
|
+
# getServerInfo
|
181
|
+
# getSubTaskIssueTypes
|
182
|
+
# getSubTaskIssueTypesForProject
|
183
|
+
# progressWorkflowAction
|
184
|
+
# refreshCustomFields
|
185
|
+
# releaseVersion
|
186
|
+
# setProjectAvatar (change to different existing)
|
187
|
+
# setNewProjectAvatar (upload new and set it)
|
188
|
+
# updateProject
|
189
|
+
# progressWorkflowAction
|
@@ -0,0 +1,297 @@
|
|
1
|
+
module JIRA
|
2
|
+
|
3
|
+
class Priority
|
4
|
+
attr_accessor :id, :name, :color, :icon, :description
|
5
|
+
def self.priority_with_xml_fragment(frag)
|
6
|
+
return if frag.nil?
|
7
|
+
priority = Priority.new
|
8
|
+
priority.id = frag.xpath('id').to_s
|
9
|
+
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
|
12
|
+
priority.description = frag.xpath('description').to_s
|
13
|
+
priority
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Resolution
|
18
|
+
attr_accessor :id, :name, :icon, :description
|
19
|
+
def self.resolution_with_xml_fragment(frag)
|
20
|
+
return if frag.nil?
|
21
|
+
resolution = Resolution.new
|
22
|
+
resolution.id = frag.xpath('id').to_s
|
23
|
+
resolution.name = frag.xpath('name').to_s
|
24
|
+
resolution.icon = frag.xpath('icon').to_s #FIXME: NSURL
|
25
|
+
resolution.description = frag.xpath('description').to_s
|
26
|
+
resolution
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Field
|
31
|
+
attr_accessor :id, :name
|
32
|
+
def self.field_with_xml_fragment(frag)
|
33
|
+
return if frag.nil?
|
34
|
+
field = Field.new
|
35
|
+
field.id = frag.xpath('id').to_s
|
36
|
+
field.name = frag.xpath('name').to_s
|
37
|
+
field
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class CustomField
|
42
|
+
attr_accessor :id, :key, :values
|
43
|
+
def self.custom_field_with_xml_fragment(frag)
|
44
|
+
return if frag.nil?
|
45
|
+
custom_field = CustomField.new
|
46
|
+
custom_field.id = frag.xpath('customfieldId').to_s
|
47
|
+
custom_field.key = frag.xpath('key').to_s
|
48
|
+
custom_field.values = frag.xpath('values/*').map { |value| value.to_s }
|
49
|
+
custom_field
|
50
|
+
end
|
51
|
+
def soapify_for(msg, label = 'customFieldValues')
|
52
|
+
msg.add label do |submsg|
|
53
|
+
submsg.add 'customfieldId', @id
|
54
|
+
submsg.add 'key', @key #TODO: see if this is always nil
|
55
|
+
submsg.add_simple_array 'values', @values
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class IssueType
|
61
|
+
attr_accessor :id, :name, :icon, :description
|
62
|
+
attr_writer :subtask
|
63
|
+
def subtask?; @subtask; end
|
64
|
+
def self.issue_type_with_xml_fragment(frag)
|
65
|
+
return if frag.nil?
|
66
|
+
issue_type = IssueType.new
|
67
|
+
issue_type.id = frag.xpath('id').to_s
|
68
|
+
issue_type.name = frag.xpath('name').to_s
|
69
|
+
issue_type.icon = frag.xpath('icon').to_s #FIXME: NSURL
|
70
|
+
issue_type.subtask = frag.xpath('subtask').to_s == 'true'
|
71
|
+
issue_type.description = frag.xpath('description').to_s
|
72
|
+
issue_type
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Status
|
77
|
+
attr_accessor :id, :name, :icon, :description
|
78
|
+
def self.status_with_xml_fragment(frag)
|
79
|
+
return if frag.nil?
|
80
|
+
status = Status.new
|
81
|
+
status.id = frag.xpath('id').to_s
|
82
|
+
status.name = frag.xpath('name').to_s
|
83
|
+
status.icon = frag.xpath('icon').to_s #FIXME: NSURL
|
84
|
+
status.description = frag.xpath('description').to_s
|
85
|
+
status
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Version
|
90
|
+
attr_accessor :id, :name, :sequence, :released, :archived, :release_date
|
91
|
+
attr_writer :released, :archived
|
92
|
+
def released?; @released; end
|
93
|
+
def archived?; @archived; end
|
94
|
+
def self.version_with_xml_fragment(frag)
|
95
|
+
return if frag.nil?
|
96
|
+
#TODO: find out why we don't get a description for this type
|
97
|
+
version = Version.new
|
98
|
+
version.id = frag.xpath('id').to_s
|
99
|
+
version.name = frag.xpath('name').to_s
|
100
|
+
version.sequence = frag.xpath('sequence').to_s.to_i
|
101
|
+
version.released = frag.xpath('released').to_s == 'true'
|
102
|
+
version.archived = frag.xpath('archived').to_s == 'true'
|
103
|
+
version.release_date = frag.xpath('releaseDate').to_s #FIXME: NSDate
|
104
|
+
version
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class Scheme
|
109
|
+
attr_accessor :id, :name, :type, :description
|
110
|
+
def self.scheme_with_xml_fragment(frag)
|
111
|
+
return if frag.nil?
|
112
|
+
scheme = Scheme.new
|
113
|
+
scheme.id = frag.xpath('id').to_s
|
114
|
+
scheme.name = frag.xpath('name').to_s
|
115
|
+
scheme.type = frag.xpath('type').to_s
|
116
|
+
scheme.description = frag.xpath('description').to_s
|
117
|
+
scheme
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Component
|
122
|
+
attr_accessor :id, :name
|
123
|
+
def self.component_with_xml_fragment(frag)
|
124
|
+
return if frag.nil?
|
125
|
+
component = Component.new
|
126
|
+
component.id = frag.xpath('id').to_s
|
127
|
+
component.name = frag.xpath('name').to_s
|
128
|
+
component
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Project
|
133
|
+
attr_accessor :id, :name, :key, :url, :project_url, :lead, :description
|
134
|
+
attr_accessor :issue_security_scheme, :notification_scheme, :permission_scheme
|
135
|
+
def self.project_with_xml_fragment(frag)
|
136
|
+
return if frag.nil?
|
137
|
+
project = Project.new
|
138
|
+
project.id = frag.xpath('id').to_s
|
139
|
+
project.name = frag.xpath('name').to_s
|
140
|
+
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
|
143
|
+
project.lead = frag.xpath('lead').to_s
|
144
|
+
project.description = frag.xpath('description').to_s
|
145
|
+
#TODO: find out why the server always seems to pass nil
|
146
|
+
project.issue_security_scheme =
|
147
|
+
Scheme.scheme_with_xml_fragment frag.xpath 'issueSecurityScheme'
|
148
|
+
project.notification_scheme =
|
149
|
+
Scheme.scheme_with_xml_fragment frag.xpath 'notificationScheme'
|
150
|
+
project.permission_scheme =
|
151
|
+
Scheme.scheme_with_xml_fragment frag.xpath 'permissionScheme'
|
152
|
+
project
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class Avatar
|
157
|
+
attr_accessor :id, :owner, :type, :content_type, :base64_data
|
158
|
+
attr_writer :system
|
159
|
+
def system?; @system; end
|
160
|
+
def self.avatar_with_xml_fragment(frag)
|
161
|
+
return if frag.nil?
|
162
|
+
avatar = Avatar.new
|
163
|
+
avatar.id = frag.xpath('id').to_s
|
164
|
+
avatar.owner = frag.xpath('owner').to_s
|
165
|
+
avatar.system = frag.xpath('system').to_s == 'true'
|
166
|
+
avatar.type = frag.xpath('type').to_s
|
167
|
+
avatar.content_type = frag.xpath('contentType').to_s
|
168
|
+
avatar.base64_data = frag.xpath('base64Data').to_s
|
169
|
+
avatar
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
#easily the most convoluted structure in the API
|
174
|
+
#will most likely be the greatest source of bugs
|
175
|
+
class Issue
|
176
|
+
attr_accessor :id, :key, :summary, :description, :type_id, :last_updated
|
177
|
+
attr_accessor :votes, :status_id, :assignee_name, :reporter_name, :priority_id
|
178
|
+
attr_accessor :project_name, :affects_versions, :create_date, :due_date
|
179
|
+
attr_accessor :fix_versions, :resolution_id, :environment, :components
|
180
|
+
attr_accessor :attachment_names, :custom_field_values
|
181
|
+
def self.issue_with_xml_fragment(frag)
|
182
|
+
return if frag.nil?
|
183
|
+
issue = Issue.new
|
184
|
+
issue.affects_versions = frag.xpath('affectsVersions/*').map { |frag|
|
185
|
+
Version.version_with_xml_fragment frag
|
186
|
+
}
|
187
|
+
issue.fix_versions = frag.xpath('fixVersions/*').map { |frag|
|
188
|
+
Version.version_with_xml_fragment frag
|
189
|
+
}
|
190
|
+
issue.components = frag.xpath('components/*').map { |frag|
|
191
|
+
Component.component_with_xml_fragment frag
|
192
|
+
}
|
193
|
+
issue.custom_field_values = frag.xpath('customFieldValues/*').map { |frag|
|
194
|
+
CustomField.custom_field_with_xml_fragment frag
|
195
|
+
}
|
196
|
+
issue.attachment_names = frag.xpath('attachmentNames/*').map { |name|
|
197
|
+
name.to_s
|
198
|
+
}
|
199
|
+
issue.id = frag.xpath('id').to_s
|
200
|
+
issue.key = frag.xpath('key').to_s
|
201
|
+
issue.summary = frag.xpath('summary').to_s
|
202
|
+
issue.description = frag.xpath('description').to_s
|
203
|
+
issue.type_id = frag.xpath('type').to_s
|
204
|
+
issue.last_updated = frag.xpath('updated').to_s
|
205
|
+
issue.votes = frag.xpath('votes').to_s.to_i
|
206
|
+
issue.status_id = frag.xpath('status').to_s
|
207
|
+
issue.assignee_name = frag.xpath('assignee').to_s
|
208
|
+
issue.reporter_name = frag.xpath('reporter').to_s
|
209
|
+
issue.priority_id = frag.xpath('priority').to_s
|
210
|
+
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
|
213
|
+
issue.resolution_id = frag.xpath('resolution').to_s
|
214
|
+
issue.environment = frag.xpath('environment').to_s
|
215
|
+
issue
|
216
|
+
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
|
+
|
226
|
+
#might be going away, since it appears to have no effect at creation time
|
227
|
+
msg.add 'reporter', @reporter_name unless @reporter.nil?
|
228
|
+
|
229
|
+
msg.add 'priority', @priority_id
|
230
|
+
msg.add 'type', @type_id
|
231
|
+
msg.add 'project', @project_name
|
232
|
+
|
233
|
+
msg.add 'summary', @summary
|
234
|
+
msg.add 'description', @description
|
235
|
+
|
236
|
+
#server only accepts issues if components/versions are just ids
|
237
|
+
msg.add 'components' do |submsg|
|
238
|
+
(@components || []).each { |component|
|
239
|
+
submsg.add 'components' do |component_msg|
|
240
|
+
component_msg.add 'id', component.id
|
241
|
+
end
|
242
|
+
}
|
243
|
+
end
|
244
|
+
msg.add 'affectsVersions' do |submsg|
|
245
|
+
(@affects_versions || []).each { |version|
|
246
|
+
submsg.add 'affectsVersions' do |version_msg|
|
247
|
+
version_msg.add 'id', version.id
|
248
|
+
end
|
249
|
+
}
|
250
|
+
end
|
251
|
+
msg.add 'fixVersions' do |submsg|
|
252
|
+
(@fix_versions || []).each { |version|
|
253
|
+
submsg.add 'fixVersions' do |version_msg|
|
254
|
+
version_msg.add 'id', version.id end
|
255
|
+
}
|
256
|
+
end
|
257
|
+
|
258
|
+
#-1 is the value you send to get the automatic assignee
|
259
|
+
msg.add 'assignee', (@assignee_name || '-1')
|
260
|
+
|
261
|
+
msg.add_complex_array 'customFieldValues', (@custom_field_values || [])
|
262
|
+
|
263
|
+
#passing environment/due_date when nil seems to mess up the server
|
264
|
+
msg.add 'environment', @environment unless @environment.nil?
|
265
|
+
msg.add 'duedate', @due_date unless @due_date.nil?
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
class User
|
270
|
+
attr_accessor :name, :full_name, :email
|
271
|
+
def self.user_with_xml_fragment(frag)
|
272
|
+
return if frag.nil?
|
273
|
+
user = User.new
|
274
|
+
user.name = frag.xpath('name').to_s
|
275
|
+
user.full_name = frag.xpath('fullname').to_s
|
276
|
+
user.email = frag.xpath('email').to_s
|
277
|
+
user
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
class FieldValue
|
282
|
+
attr_accessor :id, :values
|
283
|
+
def self.field_value_with_nil_values(id)
|
284
|
+
fv = FieldValue.new
|
285
|
+
fv.id = id
|
286
|
+
fv.values = [nil]
|
287
|
+
fv
|
288
|
+
end
|
289
|
+
def soapify_for(message, label = 'fieldValue')
|
290
|
+
message.add label do |message|
|
291
|
+
message.add 'id', @id
|
292
|
+
message.add_simple_array 'values', @values
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jiraSOAP
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Mark Rada
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-06 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: handsoap
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 1
|
31
|
+
version: "1.1"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: minitest
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
version: "0"
|
45
|
+
type: :development
|
46
|
+
version_requirements: *id002
|
47
|
+
description: Written to run fast and work on Ruby 1.9 as well as MacRuby
|
48
|
+
email: mrada@marketcircle.com
|
49
|
+
executables: []
|
50
|
+
|
51
|
+
extensions: []
|
52
|
+
|
53
|
+
extra_rdoc_files:
|
54
|
+
- LICENSE
|
55
|
+
- README.markdown
|
56
|
+
files:
|
57
|
+
- lib/jiraSOAP.rb
|
58
|
+
- lib/jiraSOAP/JIRAservice.rb
|
59
|
+
- lib/jiraSOAP/handsoap_extensions.rb
|
60
|
+
- lib/jiraSOAP/macruby_stuff.rb
|
61
|
+
- lib/jiraSOAP/remoteAPI.rb
|
62
|
+
- lib/jiraSOAP/remoteEntities.rb
|
63
|
+
- LICENSE
|
64
|
+
- README.markdown
|
65
|
+
- test/jiraSOAP_test.rb
|
66
|
+
- test/test_helper.rb
|
67
|
+
has_rdoc: true
|
68
|
+
homepage: http://github.com/ferrous26/jiraSOAP
|
69
|
+
licenses: []
|
70
|
+
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options:
|
73
|
+
- --charset=UTF-8
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 1
|
83
|
+
- 9
|
84
|
+
- 2
|
85
|
+
version: 1.9.2
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.3.7
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: A Ruby client for the JIRA SOAP API
|
101
|
+
test_files:
|
102
|
+
- test/jiraSOAP_test.rb
|
103
|
+
- test/test_helper.rb
|