jiraSOAP 0.1.1
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/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
|