better_jira 0.0.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.
@@ -0,0 +1,15 @@
1
+ class Hash
2
+ def symbolize_keys!
3
+ replace(inject({}) { |h,(k,v)| h[k.to_sym] = v; h })
4
+ end
5
+
6
+ def require_keys!(*keys)
7
+ missing = []
8
+
9
+ keys.each do |key|
10
+ missing << key unless self.has_key? key
11
+ end
12
+
13
+ raise "Required keys: #{missing.join(", ")}" unless missing.empty?
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ module BetterJira
2
+ module Exceptions
3
+ def self.wrap_soap
4
+ begin
5
+ yield
6
+ rescue SOAP::FaultError
7
+ case $!.faultstring.data
8
+ when "com.atlassian.jira.rpc.exception.RemotePermissionException: This issue does not exist or you don't have permission to view it."
9
+ raise NoSuchIssueException, "This issue does not exist or you don't have permission to view it."
10
+ end
11
+ end
12
+ end # def wrap_soap
13
+
14
+ class JiraException < RuntimeError
15
+ end
16
+
17
+ class NoSuchIssueException < JiraException
18
+
19
+ end
20
+
21
+ end # module Exceptions
22
+ end # module BetterJira
@@ -0,0 +1,240 @@
1
+ require 'soap/wsdlDriver'
2
+ require 'better_jira/utils'
3
+
4
+ module BetterJira
5
+ class Jira
6
+ # Construct the object
7
+ #
8
+ # @param [String] jira_url the base url to your jira instance, ie: https://jira.mycompany.com
9
+ # @param [String] trust_ca the path to a PEM file that contains a CA certificate to trust
10
+ def initialize(jira_url, trust_ca = nil)
11
+ @jira_url = jira_url
12
+ @soap = SOAP::WSDLDriverFactory.new(@jira_url + "/rpc/soap/jirasoapservice-v2?wsdl").create_rpc_driver
13
+ @client = HTTPClient.new
14
+ @client.ssl_config.set_trust_ca(trust_ca) unless trust_ca.nil?
15
+ end
16
+
17
+ # Login to the jira instance
18
+ #
19
+ # @param [String] username your username
20
+ # @param [String] password your password
21
+ #
22
+ # @raise if login fails
23
+ def login(username, password)
24
+ @token = @soap.login(username, password)
25
+
26
+ destination = '/success'
27
+ res = @client.post("#{@jira_url}/secure/Dashboard.jspa", {
28
+ 'os_username' => username,
29
+ 'os_password' => password,
30
+ 'os_destination' => destination
31
+ })
32
+ raise "Login Fail!" if res.status != 302 || (res.header['Location'] && res.header['Location'].first[-destination.length, destination.length] != destination)
33
+ end
34
+
35
+ # Retrieves all the fields available for edit on the particular issue key
36
+ #
37
+ # @param [String] key the issue key to check (ie: TEST-100)
38
+ # @return [Hash] a hash of jira field id to name
39
+ def fields_for_edit(key)
40
+ BetterJira::simple_soap_mapping(@soap.getFieldsForEdit(@token, key))
41
+ end
42
+
43
+ # Iterates over all the issues in a filter, passing each JiraIssue into the block
44
+ #
45
+ # @param [Integer] filter_id the filter to load
46
+ # @param [Hash] options the options to use when iterating
47
+ # @option options [Integer] :batch_size (50) the number of issues to retrieve at a time
48
+ # @option options [Integer] :exclude_if_in_filter (nil) a filter to use as an exclude list if present
49
+ # @yield a block for iterating over the list
50
+ # @yieldparam [JiraIssue] issue
51
+ def each_issue_from_filter(filter_id, options ={}, &block)
52
+ options[:batch_size] ||= 50
53
+
54
+ exclude_issues = []
55
+
56
+ if (options[:exclude_if_in_filter]) then
57
+ each_issue_from_filter(options[:exclude_if_in_filter]) { |issue|
58
+ exclude_issues << issue[:key]
59
+ }
60
+ end
61
+
62
+ offset = 0
63
+ while( (issues = @soap.getIssuesFromFilterWithLimit(@token, filter_id, offset, options[:batch_size])) != nil)
64
+ break if offset >= @soap.getIssueCountForFilter(@token, filter_id)
65
+ offset += issues.length
66
+
67
+ issues.each {|issue|
68
+ issue = JiraIssue.new(issue, self)
69
+ block.call(issue) unless exclude_issues.include?(issue[:key])
70
+ }
71
+ end
72
+ end
73
+
74
+ # Retrieves all of the versions for a particular project from the Jira server
75
+ #
76
+ # @param [String] project_key the project key to look up, ie: "MYPROJ"
77
+ def versions_for_project(project_key)
78
+ @soap.getVersions(@token, project_key).map{|x| BetterJira::JiraVersion.convert_from_soap(x)}
79
+ end
80
+
81
+ def update_issue(key, remote_field_changes)
82
+ @soap.updateIssue(@token, key, remote_field_changes)
83
+ end
84
+
85
+ def convert_custom_field_value_to_happy_jira_time(value)
86
+ if (value != nil) then
87
+ if (Array === value) then
88
+ value = value.map { |x|
89
+ q = nil
90
+ q = x if String === x
91
+ q = x['id'] if SOAP::Mapping::Object === x
92
+ q
93
+ }
94
+ elsif (SOAP::Mapping::Object === value) then
95
+ value = [value['id']]
96
+ elsif (String === value) then
97
+ value = [value]
98
+ elsif (DateTime === value) then
99
+ value = [value.strftime('%d/%b/%y')]
100
+ end
101
+ end
102
+ end
103
+
104
+ def progress_workflow(key, workflow_action_id, custom_field_values, opts = {})
105
+ puts "Shit passed in" if JIRA_DEBUG
106
+ puts "==============" if JIRA_DEBUG
107
+ p custom_field_values if JIRA_DEBUG
108
+ puts "" if JIRA_DEBUG
109
+
110
+
111
+
112
+ cfvs = []
113
+
114
+ unless @client.nil?
115
+ res = @client.get("#{@jira_url}/si/jira.issueviews:issue-xml/#{key}/#{key}.xml")
116
+ puts res.content if JIRA_DEBUG
117
+ match = res.content.match(/<timeestimate seconds="(.*?)">(.*?)<\/timeestimate>/)
118
+ time_estimate = "#{match[1].to_i/60}" unless match.nil?
119
+ end
120
+
121
+
122
+ issue = get_issue(key)
123
+ fields = fields_for_action(key, workflow_action_id)
124
+
125
+ puts "Issue" if JIRA_DEBUG
126
+ p issue if JIRA_DEBUG
127
+ puts "" if JIRA_DEBUG
128
+
129
+ moronic_map = {
130
+ 'issuetype' => 'type',
131
+ 'versions' => 'affectsVersions'
132
+ }
133
+ puts "Fields" if JIRA_DEBUG
134
+ puts "======" if JIRA_DEBUG
135
+ fields.each {|f|
136
+
137
+ p f if JIRA_DEBUG
138
+ real_field_name = moronic_map[f['id']] ? moronic_map[f['id']] : f['id']
139
+
140
+ if (real_field_name != '' && custom_field_values[real_field_name.to_sym] != nil)
141
+ value = custom_field_values[real_field_name.to_sym]
142
+ elsif (real_field_name != '' && custom_field_values[real_field_name] != nil)
143
+ value = custom_field_values[real_field_name]
144
+ elsif (real_field_name == 'timetracking') then
145
+ value = time_estimate
146
+ elsif (real_field_name =~ /customfield_/) then
147
+ q = issue.customFieldValues.find { |c| c.customfieldId == real_field_name }
148
+ value = q.values unless q.nil?
149
+ else
150
+ value = nil
151
+ value = issue.send('[]', real_field_name) if not real_field_name.empty?
152
+ end
153
+
154
+ value = convert_custom_field_value_to_happy_jira_time(value)
155
+
156
+ puts "" if JIRA_DEBUG
157
+ p value if JIRA_DEBUG
158
+ puts "" if JIRA_DEBUG
159
+
160
+ cfvs << {:id => f['id'], :values => value}
161
+ }
162
+
163
+ puts "Output!" if JIRA_DEBUG
164
+ puts "=======" if JIRA_DEBUG
165
+ p cfvs if JIRA_DEBUG
166
+
167
+
168
+
169
+ @soap.progressWorkflowAction(@token, key, workflow_action_id, cfvs)
170
+ end
171
+
172
+ # Adds a comment to the issue specified by key
173
+ #
174
+ # @param [String] key the issue to comment on
175
+ # @param [String] comment the comment to use
176
+ # @param [Hash] options
177
+ # @option options [String] :body the comment body to use, shouldn't be used
178
+ # @option options [String] :groupLevel ?
179
+ # @option options [String] :roleLevel ?
180
+ def add_comment(key, comment, options={})
181
+ options[:body] = comment if options[:body].nil?
182
+
183
+ @soap.addComment(@token, key, options)
184
+ end
185
+
186
+ # Gets the available actions for an issue from the server
187
+ #
188
+ # @param [String] key the issue to look up
189
+ #
190
+ # @return [Hash] keys are the jira id, values are the name of the action
191
+ def available_actions(key)
192
+ BetterJira::Exceptions.wrap_soap {
193
+ BetterJira::simple_soap_mapping(@soap.getAvailableActions(@token, key))
194
+ }
195
+ end
196
+
197
+ # Gets the specified issue from the jira server
198
+ #
199
+ # @param [String] key the issue key
200
+ # @return [JiraIssue] the retrieved jira issue
201
+ def get_issue(key)
202
+ JiraIssue.new(@soap.getIssue(@token, key), self)
203
+ end
204
+
205
+ # Gets the specified issue from the jira server
206
+ # @see #get_issue
207
+ def [](key)
208
+ get_issue(key)
209
+ end
210
+
211
+ # Gets the available fields for edit during an action
212
+ #
213
+ # @param [String] key the issue to look up
214
+ # @param [Integer] action_id the action to look up
215
+ #
216
+ # @return [Hash] keys are the jira id, values are the name of the field
217
+ def fields_for_action(key, action_id)
218
+ BetterJira::Exceptions.wrap_soap {
219
+ BetterJira::simple_soap_mapping(@soap.getFieldsForAction(@token, key, action_id))
220
+ }
221
+ end
222
+
223
+ def map_version_fields(project, fields, cur_depth = 0, versions = nil)
224
+ versions = versions_for_project(project)
225
+
226
+ if (cur_depth == 0 && Array === fields) then
227
+ fields.map { |q|
228
+ map_version_fields(project, q, cur_depth + 1, versions)
229
+ }.flatten
230
+ elsif (Fixnum === fields || Integer === fields)
231
+ [fields.to_s]
232
+ elsif (String === fields)
233
+ versions = versions.select { |version| version.name == fields }
234
+ versions = versions.map { |version| version['id'] }
235
+ end
236
+ end
237
+ end # class Jira
238
+
239
+
240
+ end # module BetterJira
@@ -0,0 +1,99 @@
1
+ module BetterJira
2
+ class JiraIssue
3
+ attr_reader :custom_fields
4
+
5
+ FIELDS = [:summary, :description]
6
+
7
+ def initialize(soap_jira_issue, jira)
8
+ @soap_jira_issue = soap_jira_issue
9
+ @jira = jira
10
+ @changes = []
11
+ @custom_fields = {}
12
+ @soap_jira_issue.customFieldValues.each { |cf|
13
+ @custom_fields[cf.customfieldId] = cf.values
14
+ }
15
+ end
16
+
17
+ def update_fields(fields)
18
+ fields[:fixVersions] = fields[:fix_versions] if fields.has_key? :fix_versions
19
+ fields[:versions] = fields[:affects_versions] if fields.has_key? :affects_versions
20
+
21
+ fields.each {|k,v|
22
+ case k
23
+ when :fixVersions then
24
+ fields[k] = @jira.map_version_fields(self[:project], v)
25
+ when :versions
26
+ fields[k] = @jira.map_version_fields(self[:project], v)
27
+ end
28
+ }
29
+
30
+
31
+ fields = fields.map {|k,v|
32
+ {:id => k, :values => BetterJira::convert_custom_field_value_to_happy_jira_time(v)}
33
+ }
34
+ @jira.update_issue(self[:key], fields)
35
+ initialize(@jira.get_issue(self[:key]), @jira)
36
+ end
37
+
38
+ def field_value(key)
39
+ r = self[key]
40
+ r = r.first unless (r.nil?)
41
+ return r
42
+ end
43
+
44
+ def field_values(key)
45
+ self[key]
46
+ end
47
+
48
+ def [](key)
49
+ if (key.to_s =~ /customfield_/) then
50
+ BetterJira::custom_field_values(@soap_jira_issue, key.to_s)
51
+ elsif (Symbol === key) then
52
+ begin
53
+ @soap_jira_issue.send "[]", key.to_s
54
+ rescue NoMethodError => e
55
+ nil
56
+ end
57
+ else
58
+
59
+ end
60
+ end
61
+
62
+ def field_as_date_time(key)
63
+ BetterJira::custom_field_value_as_date_time(@soap_jira_issue, key.to_s)
64
+ end
65
+
66
+ # Returns a list of available actions for this issue
67
+ def available_actions
68
+ ret = {}
69
+ @jira.available_actions(self[:key]).each{|q| ret[q['id']] = q['name']}
70
+ ret
71
+ end
72
+
73
+ # Returns a list of fields for a particular action_id
74
+ def fields_for_action_id(action_id)
75
+ ret = {}
76
+ @jira.fields_for_action(self[:key], action_id).each {|q| ret[q['id']] = q['name']}
77
+ ret
78
+ end
79
+
80
+ # Adds a comment to the issue
81
+ def add_comment(comment, options={})
82
+ @jira.add_comment(self[:key], comment, options)
83
+ end
84
+
85
+ def fields_for_action_name(action_name)
86
+ action = available_actions.find {|k, v| v == action_name}
87
+ fields_for_action_id(action[0])
88
+ end
89
+
90
+
91
+ def fields_for_edit
92
+ BetterJira::array_of_remote_fields_to_map(@jira.get_fields_for_edit(self[:key]))
93
+ end
94
+
95
+ def progress_workflow(workflow_action_id, custom_field_values, opts = {})
96
+ @jira.progress_workflow(self[:key], workflow_action_id, custom_field_values, opts)
97
+ end
98
+ end # class JiraIssue
99
+ end
@@ -0,0 +1,19 @@
1
+ module BetterJira
2
+ class JiraVersion
3
+ attr_accessor :archived, :id, :name, :release_date, :released, :sequence
4
+
5
+ def self.convert_from_soap(s)
6
+ if (s.is_a? SOAP::Mapping::Object) then
7
+ ret = JiraVersion.new
8
+ ret.archived = s.archived
9
+ ret.id = s.id
10
+ ret.name = s.name
11
+ ret.release_date = s.releaseDate
12
+ ret.released = s.released
13
+ ret.sequence = s.sequence
14
+ ret
15
+ end
16
+ end
17
+
18
+ end
19
+ end # module BetterJira
@@ -0,0 +1,12 @@
1
+ # Hack for SSL issues
2
+ require 'net/http'
3
+
4
+ class Net::HTTP
5
+ alias_method :old_initialize, :initialize
6
+ def initialize(*args)
7
+ old_initialize(*args)
8
+ @ssl_context = OpenSSL::SSL::SSLContext.new
9
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
10
+ end
11
+ end
12
+ # /Hack for SSL issues
@@ -0,0 +1,63 @@
1
+ module BetterJira
2
+ def self.custom_field_values(issue, field)
3
+ issue.customFieldValues.each { |cfv|
4
+ if (cfv.customfieldId == field) then
5
+ return cfv.values
6
+ end
7
+ }
8
+
9
+ return nil
10
+ end
11
+
12
+ def self.custom_field_value(issue, field, &block)
13
+ if (block == nil) then
14
+ block = Proc.new {|x| x}
15
+ end
16
+
17
+ values = custom_field_values(issue, field)
18
+
19
+ return block.call(values.first) if (values != nil)
20
+
21
+ return nil
22
+ end
23
+
24
+ def self.custom_field_value_as_date_time(issue, field)
25
+ custom_field_value(issue, field) { |x| DateTime.parse(x, true) }
26
+ end
27
+
28
+ # Converts an array of RemoteFields into a map of id => name
29
+ #
30
+ # @return [Hash] the converted map
31
+ def self.array_of_remote_fields_to_map(remote_fields)
32
+ ret = {}
33
+ remote_fields.each{|q| ret[q['id']] = q['name']}
34
+ ret
35
+ end
36
+
37
+ def self.simple_soap_mapping(a)
38
+ ret = {}
39
+ a.each{|q| ret[q['id']] = q['name']}
40
+ ret
41
+ end
42
+
43
+ def self.convert_custom_field_value_to_happy_jira_time(value)
44
+ if (value != nil) then
45
+ if (Array === value) then
46
+ value = value.map { |x|
47
+ q = nil
48
+ q = x if String === x
49
+ q = x['id'] if SOAP::Mapping::Object === x
50
+ q
51
+ }
52
+ elsif (SOAP::Mapping::Object === value) then
53
+ value = [value['id']]
54
+ elsif (String === value) then
55
+ value = [value]
56
+ elsif (DateTime === value) then
57
+ value = [value.strftime('%d/%b/%y')]
58
+ else
59
+ value = [value.to_s]
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2010 Eric Anderson
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module BetterJira
22
+ VERSION = "0.0.1"
23
+ end
@@ -0,0 +1,5 @@
1
+ require 'better_jira/core_ext'
2
+ require 'better_jira/utils'
3
+ require 'better_jira/jira'
4
+ require 'better_jira/jira_issue'
5
+ require 'better_jira/exceptions'
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: better_jira
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Eric Anderson
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-23 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: httpclient
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 2
30
+ - 1
31
+ - 5
32
+ - 2
33
+ version: 2.1.5.2
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: soap4r
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - "="
43
+ - !ruby/object:Gem::Version
44
+ segments:
45
+ - 1
46
+ - 5
47
+ - 8
48
+ version: 1.5.8
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: rake
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
64
+ description:
65
+ email:
66
+ - eric@ericanderson.us
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files: []
72
+
73
+ files:
74
+ - lib/better_jira/core_ext.rb
75
+ - lib/better_jira/exceptions.rb
76
+ - lib/better_jira/jira.rb
77
+ - lib/better_jira/jira_issue.rb
78
+ - lib/better_jira/jira_version.rb
79
+ - lib/better_jira/net_http_ssl_hack.rb
80
+ - lib/better_jira/utils.rb
81
+ - lib/better_jira/version.rb
82
+ - lib/better_jira.rb
83
+ has_rdoc: true
84
+ homepage: http://github.com/ericanderson/better_jira
85
+ licenses: []
86
+
87
+ post_install_message:
88
+ rdoc_options: []
89
+
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ segments:
106
+ - 1
107
+ - 3
108
+ - 6
109
+ version: 1.3.6
110
+ requirements: []
111
+
112
+ rubyforge_project:
113
+ rubygems_version: 1.3.7
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: Yet Another Jira Gem
117
+ test_files: []
118
+