mingle4r 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/History.txt +6 -0
  2. data/README +53 -20
  3. data/TODO.txt +8 -4
  4. data/lib/mingle4r.rb +9 -23
  5. data/lib/mingle4r/api/card.rb +137 -0
  6. data/lib/mingle4r/api/card/attachment.rb +47 -0
  7. data/lib/mingle4r/api/card/comment.rb +12 -0
  8. data/lib/mingle4r/api/card/transition.rb +48 -0
  9. data/lib/mingle4r/api/murmur.rb +12 -0
  10. data/lib/mingle4r/api/project.rb +64 -0
  11. data/lib/mingle4r/api/property_definition.rb +7 -0
  12. data/lib/mingle4r/api/user.rb +7 -0
  13. data/lib/mingle4r/api/wiki.rb +10 -0
  14. data/lib/mingle4r/common_class_methods.rb +0 -1
  15. data/lib/mingle4r/mingle_client.rb +16 -14
  16. data/lib/mingle4r/version.rb +1 -1
  17. metadata +11 -22
  18. data/lib/mingle4r/api.rb +0 -31
  19. data/lib/mingle4r/api/v1.rb +0 -25
  20. data/lib/mingle4r/api/v1/card.rb +0 -173
  21. data/lib/mingle4r/api/v1/card/attachment.rb +0 -51
  22. data/lib/mingle4r/api/v1/project.rb +0 -71
  23. data/lib/mingle4r/api/v1/property_definition.rb +0 -16
  24. data/lib/mingle4r/api/v1/transition_execution.rb +0 -15
  25. data/lib/mingle4r/api/v1/user.rb +0 -9
  26. data/lib/mingle4r/api/v1/wiki.rb +0 -12
  27. data/lib/mingle4r/api/v2.rb +0 -25
  28. data/lib/mingle4r/api/v2/card.rb +0 -157
  29. data/lib/mingle4r/api/v2/card/attachment.rb +0 -51
  30. data/lib/mingle4r/api/v2/card/comment.rb +0 -16
  31. data/lib/mingle4r/api/v2/card/transition.rb +0 -52
  32. data/lib/mingle4r/api/v2/murmur.rb +0 -14
  33. data/lib/mingle4r/api/v2/project.rb +0 -90
  34. data/lib/mingle4r/api/v2/property_definition.rb +0 -14
  35. data/lib/mingle4r/api/v2/user.rb +0 -9
  36. data/lib/mingle4r/api/v2/wiki.rb +0 -12
  37. data/lib/mingle4r/common_dyn_class_instance_methods.rb +0 -5
@@ -1,3 +1,9 @@
1
+ 0.4.0
2
+ -----
3
+
4
+ * Only Mingle 3.0 is supported. If you need to work with both mingle 3.0 and mingle 2.0 use the 0.3.0 of the gem.
5
+ However no new features will be supported by 0.3.0.
6
+
1
7
  0.3.0
2
8
  -----
3
9
 
data/README CHANGED
@@ -8,6 +8,8 @@ This gem is a wrapper around active resource to access the rest api exposed by m
8
8
  (http://studios.thoughtworks.com/mingle-agile-project-management).It provides a easy
9
9
  way to communicate with mingle. For the library to work you need to enable basic authentication
10
10
  (not enabled by default) in Mingle. See below to enable basic authentication in Mingle.
11
+ The typical use-case for this gem is to help someone getting started with writing code
12
+ to integrate with Mingle.
11
13
 
12
14
  However if you are planning to connect and work with mingle from the terminal, then there
13
15
  is another gem mingle-mingle which you should look at. This library provides a very easy
@@ -29,10 +31,9 @@ attachments for a particular card.
29
31
 
30
32
  == SYNOPSIS:
31
33
 
32
- The api now supports both mingle 2 and mingle 3. The gem tries as much as possible to hide
33
- away the internal details. But some api features are only supported in mingle 3 only (like
34
- murmurs). For that check the api documentation for the mingle instance(located at
35
- <mingle instance host>/help/index)
34
+ The api now supports only mingle 3. Mingle 2 is no longer supported. If you need to connect
35
+ to both Mingle 2 and Mingle 3 then try 0.3.0 of the gem. To see the api for mingle 3, check
36
+ the api documentation for the mingle instance(located at <mingle instance host>/help/index)
36
37
 
37
38
  In all the documentation below you can replace Mingle4r::MingleClient with MingleClient.
38
39
  Its an alias for easy use.
@@ -70,10 +71,12 @@ you are trying to access. It should be something like 'http://localhost:8080/pro
70
71
 
71
72
  m_c = Mingle4r::MingleClient.new('http://localhost:8080', 'testuser', 'password')
72
73
  m_c.proj_id = 'great_mingle_project'
73
- m_c.project
74
+ project = m_c.project
75
+
76
+ project is a single activeresource object
74
77
 
75
78
  C) Getting cards for a particular project
76
- --------------------------------------
79
+ -----------------------------------------
77
80
 
78
81
  Get a mingle client object initialized as in SECTION B. Then call the cards method.
79
82
 
@@ -113,6 +116,7 @@ m_c = Mingle4r::MingleClient.new('http://localhost:8080', 'testuser', 'password'
113
116
  m_c.proj_id = 'great_mingle_project'
114
117
  defect_card = m_c.project.cards.first
115
118
  defect_card.property_value('Status', 'Closed')
119
+ defect_card.save
116
120
 
117
121
  G) Adding comment to a particular card
118
122
  --------------------------------------
@@ -122,14 +126,42 @@ m_c.proj_id = 'great_mingle_project'
122
126
  defect_card = m_c.project.cards[0]
123
127
  defect_card.add_comment('Not able to reproduce')
124
128
 
125
- H) Getting all comments for a card(only in mingle 3.0)
126
- ------------------------------------------------------
129
+ H) Getting a particular version of a card
130
+ -----------------------------------------
131
+
132
+ Mingle maintains the different versions of a card. It always shows the latest version
133
+ by default. However if you want to access a different version you can do so in the
134
+ following ways.
135
+
136
+ m_c = Mingle4r::MingleClient.new('http://localhost:8080', 'testuser', 'password')
137
+ m_c.proj_id = 'great_mingle_project'
138
+ defect_card = m_c.project.cards.first
139
+ supposing the latest version of the card is 42
140
+
141
+ 1) Get the previous version
142
+ ---------------------------
143
+
144
+ defect_card.version(:previous) # return version 41
145
+
146
+ 2) Get the next version
147
+ -----------------------
148
+
149
+ defect_card.version(:next) # returns version 42 since it is the latest version
150
+
151
+ 3) Get an arbitrary version
152
+ ---------------------------
153
+
154
+ defect_card.version(21) # returns version 21
155
+
156
+ I) Getting all comments for a card
157
+ ----------------------------------
158
+
127
159
  m_c = Mingle4r::MingleClient.new('http://localhost:8080', 'testuser', 'password')
128
160
  m_c.proj_id = 'great_mingle_project'
129
161
  defect_card = m_c.project.cards.first
130
162
  defect_cards.comments
131
163
 
132
- I) Attachments
164
+ J) Attachments
133
165
  --------------
134
166
  m_c = Mingle4r::MingleClient.new('http://localhost:8080', 'testuser', 'password')
135
167
  m_c.proj_id = 'great_mingle_project'
@@ -140,15 +172,15 @@ defect_card.attachments
140
172
  --------------------------------------
141
173
 
142
174
  attachment = defect_card.attachments.first
143
- attachment.save('page.css')
175
+ attachment.download('page.css')
144
176
 
145
177
  2) Uploading an attachment
146
178
  --------------------------
147
179
 
148
180
  defect_card.upload_attachment('page-screenshot.jpg')
149
181
 
150
- J) Murmurs(only in Mingle 3.0)
151
- ------------------------------
182
+ K) Murmurs
183
+ ----------
152
184
 
153
185
  1) Get the murmurs for a project
154
186
  --------------------------------
@@ -168,16 +200,16 @@ J) Murmurs(only in Mingle 3.0)
168
200
  -------------------------
169
201
  project.post_murmur('my first murmur, I am excited!')
170
202
 
171
- K) Get all transitions for a card(only in Mingle 3.0)
172
- -----------------------------------------------------
203
+ L) Get all transitions for a card
204
+ ---------------------------------
173
205
 
174
206
  m_c = Mingle4r::MingleClient.new('http://localhost:8080', 'testuser', 'password')
175
207
  m_c.proj_id = 'great_mingle_project'
176
208
  defect_card = m_c.project.cards.first
177
209
 
178
- defect_card.transitions
210
+ defect_card.transitions # array of active resource objects
179
211
 
180
- L) Execute a transition on a card
212
+ M) Execute a transition on a card
181
213
  ---------------------------------
182
214
 
183
215
  m_c = Mingle4r::MingleClient.new('http://localhost:8080', 'testuser', 'password')
@@ -185,11 +217,9 @@ m_c.proj_id = 'great_mingle_project'
185
217
  defect_card = m_c.project.cards.first
186
218
 
187
219
  defect_card.execute_transition(
188
- 'name'/'transition' => name of the transition to execute exactly s in Mingle(required in 2,3 but
189
- not required in 3.0)
190
220
  'comment' => comment for the transition, required only if the transition requires a comment
191
221
  'Property Name as in Mingle exactly' => 'Property value to set for the property', required only
192
- if the transition requires to be set manually.
222
+ if the transition requires to be set manually, multiple properties might need to be set.
193
223
  )
194
224
 
195
225
  == REQUIREMENTS:
@@ -199,7 +229,10 @@ during gem install.
199
229
 
200
230
  == INSTALL:
201
231
 
202
- sudo (not on windows) gem install mingle4r
232
+ since github no longer archives gems, I am hosting the gem at gemcutter. So you would need to
233
+ add http://gemcutter.org to your gem sources : gem sources -a 'http://gemcutter.org'. Then do
234
+
235
+ gem install mingle4r
203
236
 
204
237
  == LICENSE:
205
238
 
data/TODO.txt CHANGED
@@ -1,7 +1,11 @@
1
- update documentation
2
- write tests for mingle4r/mingle_client
1
+ update documentation - write about executing transitions directly on the transition
2
+ write tests for project class
3
+
3
4
 
4
5
  refactorings
5
6
  ------------
6
- remove calling create_resource_class through send
7
- remove checking for cache through a variable. check the results container directly
7
+
8
+ maybe nice to nave
9
+ -------------------
10
+
11
+ should transtions give the list of properties to be changed directly
@@ -13,29 +13,15 @@ end
13
13
  require 'mingle_resource'
14
14
  require 'mingle4r/version'
15
15
  require 'mingle4r/common_class_methods'
16
- require 'mingle4r/common_dyn_class_instance_methods'
17
16
  require 'mingle4r/helpers'
18
17
  require 'mingle4r/mingle_client'
19
- require 'mingle4r/api'
20
18
 
21
- # Version 1 of api
22
- require 'mingle4r/api/v1'
23
- require 'mingle4r/api/v1/card'
24
- require 'mingle4r/api/v1/card/attachment'
25
- require 'mingle4r/api/v1/project'
26
- require 'mingle4r/api/v1/property_definition'
27
- require 'mingle4r/api/v1/transition_execution'
28
- require 'mingle4r/api/v1/user'
29
- require 'mingle4r/api/v1/wiki'
30
-
31
- # version 2 of api
32
- require 'mingle4r/api/v2'
33
- require 'mingle4r/api/v2/card'
34
- require 'mingle4r/api/v2/card/attachment'
35
- require 'mingle4r/api/v2/card/comment'
36
- require 'mingle4r/api/v2/card/transition'
37
- require 'mingle4r/api/v2/murmur'
38
- require 'mingle4r/api/v2/project'
39
- require 'mingle4r/api/v2/property_definition'
40
- require 'mingle4r/api/v2/user'
41
- require 'mingle4r/api/v2/wiki'
19
+ require 'mingle4r/api/card'
20
+ require 'mingle4r/api/card/attachment'
21
+ require 'mingle4r/api/card/comment'
22
+ require 'mingle4r/api/card/transition'
23
+ require 'mingle4r/api/murmur'
24
+ require 'mingle4r/api/project'
25
+ require 'mingle4r/api/property_definition'
26
+ require 'mingle4r/api/user'
27
+ require 'mingle4r/api/wiki'
@@ -0,0 +1,137 @@
1
+ module Mingle4r
2
+ module API
3
+ class Card
4
+ extend Mingle4r::CommonClassMethods
5
+
6
+ # overwrite the default find in CommonClassMethods
7
+ def self.find(*args)
8
+ scope = args.slice!(0)
9
+ options = args.slice!(0) || {}
10
+ @resource_class.find_without_pagination(scope, options)
11
+ end
12
+
13
+ module ClassMethods
14
+ def find_without_pagination(*args)
15
+ scope = args.slice!(0)
16
+ options = args.slice!(0) || {}
17
+ options[:params] ||= {}
18
+ options[:params].merge!({:page => 'all'})
19
+ # call ActiveResource::Base::find with proper options
20
+ find(scope, options)
21
+ end
22
+
23
+ # applies an mql filter on card types. Look at https://mingle05.thoughtworks.com/help/mql_reference.html
24
+ # for reference
25
+ def apply_filter(filter_string)
26
+ find_without_pagination(:all, :params => {'filters[mql]'.to_sym => filter_string})
27
+ end
28
+ end
29
+
30
+ module InstanceMethods
31
+ # so that active resource tries to find by number
32
+ def id
33
+ number()
34
+ end
35
+
36
+ def attachments(refresh = false)
37
+ return @attachments if(!refresh && @attachments)
38
+ set_attributes_for(Attachment)
39
+ @attachments = Attachment.find(:all)
40
+ end
41
+
42
+ def comments(refresh = false)
43
+ return @comments if(!refresh && @comments)
44
+ set_attributes_for(Comment)
45
+ @comments = Card::Comment.find(:all)
46
+ end
47
+
48
+ def transitions(refresh = false)
49
+ return @transitions if(!refresh && @transitions)
50
+ set_attributes_for(Transition)
51
+ @transitions = Card::Transition.find(:all)
52
+ end
53
+
54
+ def murmurs(refresh = false)
55
+ return @murmurs if(!refresh && @murmurs)
56
+ set_attributes_for(Murmur)
57
+ @murmurs = Murmur.find(:all)
58
+ end
59
+
60
+ def upload_attachment(file_path)
61
+ attachment_uri = URI.parse(File.join(self.class.site.to_s, "cards/#{self.number()}/attachments.xml"))
62
+ http = Net::HTTP.new(attachment_uri.host, attachment_uri.port)
63
+ http.use_ssl = attachment_uri.is_a?(URI::HTTPS)
64
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
65
+ basic_encode = 'Basic ' + ["#{self.class.user}:#{self.class.password}"].pack('m').delete("\r\n")
66
+
67
+ post_headers = {
68
+ 'Authorization' => basic_encode,
69
+ 'Content-Type' => 'multipart/form-data; boundary=----------XnJLe9ZIbbGUYtzPQJ16u1'
70
+ }
71
+
72
+ file_content = IO.read(file_path)
73
+
74
+ post_body = <<EOS
75
+ ------------XnJLe9ZIbbGUYtzPQJ16u1\r
76
+ Content-Disposition: form-data; name="file"; filename="#{File.basename(file_path)}"\r
77
+ Content-Type: application/octet-stream\r
78
+ Content-Length: #{file_content.size}\r
79
+ \r
80
+ #{file_content}\r
81
+ ------------XnJLe9ZIbbGUYtzPQJ16u1--\r
82
+ EOS
83
+
84
+ http.post(attachment_uri.path, post_body, post_headers)
85
+ end
86
+
87
+ def add_comment(str)
88
+ set_attributes_for(Comment)
89
+ comment = Comment.new(:content => str.to_s)
90
+ comment.save
91
+ end
92
+
93
+ def execute_transition(args)
94
+ trans_name = args.symbolize_keys[:name]
95
+ transition = transitions.detect { |t| t.name == trans_name}
96
+ transition.execute(args)
97
+ end
98
+
99
+ # returns back the version of the card given. If an invalid version is given, the latest
100
+ # version is returned, takes a number or :next or :before
101
+ def version(version_no)
102
+ version_2_find = 0
103
+ case version_no
104
+ when :previous
105
+ version_2_find = self.version.to_i - 1
106
+ when :next
107
+ version_2_find = self.version.to_i + 1
108
+ else
109
+ version_2_find = version_no.to_i
110
+ end
111
+ self.class.find(self.number, :params => {:version => version_2_find})
112
+ end
113
+
114
+ # Gets and sets the value of a property. The property name given should be the same
115
+ # as the mingle property name. the value is optional
116
+ def property_value(name, val = nil)
117
+ property = properties.detect { |p| p.name == name }
118
+ val ? property.value = val : property.value
119
+ end
120
+
121
+ # Gets the custom properties in the form of an array of hashes with the property names as keys and
122
+ # property values as the value
123
+ def custom_properties
124
+ properties.map { |p| {p.name => p.value} }
125
+ end
126
+
127
+ private
128
+ def set_attributes_for(klass)
129
+ resource_site = File.join(self.class.site.to_s, "cards/#{self.number()}").to_s
130
+ klass.site = resource_site
131
+ klass.user = self.class.user
132
+ klass.password = self.class.password
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,47 @@
1
+ module Mingle4r
2
+ module API
3
+ class Attachment
4
+ module InstanceMethods
5
+ # downloads the attachment. It an additional file path is given it saves it at the
6
+ # given path. The given path should be writable
7
+ def download(file_name = nil)
8
+ collection_uri = self.class.site
9
+ rel_down_url = self.url
10
+ base_url = "#{collection_uri.scheme}://#{collection_uri.host}:#{collection_uri.port}/"
11
+ down_uri = URI.join(base_url, rel_down_url)
12
+ req = Net::HTTP::Get.new(down_uri.path)
13
+ req.basic_auth self.class.user, self.class.password
14
+ begin
15
+ res = Net::HTTP.start(down_uri.host, down_uri.port) { |http| http.request(req) }
16
+ file_name ||= self.file_name()
17
+ File.open(file_name, 'w') { |f| f.print(res.body) }
18
+ rescue Exception => e
19
+ e.message
20
+ end
21
+ end # download
22
+
23
+ # alias for file_name
24
+ def name
25
+ file_name()
26
+ end
27
+
28
+ # so that active resource tries to find by proper id
29
+ def id
30
+ name()
31
+ end
32
+
33
+ # This method had to be overriden.
34
+ # normal active resource destroy doesn't work as mingle site for deleting attachments doesn't end with .xml.
35
+ def destroy
36
+ connection = self.send(:connection)
37
+ # deletes the attachment by removing .xml at the end
38
+ connection.delete(self.send(:element_path).gsub(/\.xml\z/, ''))
39
+ end
40
+ alias_method :delete, :destroy
41
+ end #module InstanceMethods
42
+
43
+ extend Mingle4r::CommonClassMethods
44
+
45
+ end # class Attachment
46
+ end # class API
47
+ end # module Mingle4r
@@ -0,0 +1,12 @@
1
+ module Mingle4r
2
+ module API
3
+ class Comment
4
+ module InstanceMethods
5
+ def to_s
6
+ attributes['content']
7
+ end
8
+ end
9
+ extend Mingle4r::CommonClassMethods
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,48 @@
1
+ module Mingle4r
2
+ module API
3
+ class Transition
4
+ extend Mingle4r::CommonClassMethods
5
+
6
+ module InstanceMethods
7
+ def execute(args = {})
8
+ args.symbolize_keys!
9
+ trans_exec_xml = convert_to_xml(args)
10
+ # set_transition_execution_attributes
11
+ conn = self.class.connection
12
+ url_path =URI.parse(transition_execution_url()).path
13
+ conn.post(url_path, trans_exec_xml, self.class.headers)
14
+ end
15
+
16
+ private
17
+ def convert_to_xml(args)
18
+ hash = create_transition_exec_hash(args)
19
+ xmlize_trans_exec(hash)
20
+ end
21
+
22
+ def create_transition_exec_hash(args)
23
+ transition_hash = {}
24
+ transition_hash['card'] = (args.delete(:card) || associated_card_number).to_i
25
+ args.delete(:name) || args.delete(:transition)
26
+
27
+ comment = args.delete(:comment)
28
+ transition_hash['comment'] = comment if comment
29
+ properties = []
30
+ args.each do |name, value|
31
+ property = {'name' => name.to_s, 'value' => value}
32
+ properties.push(property)
33
+ end
34
+ transition_hash['properties'] = properties unless properties.empty?
35
+ transition_hash
36
+ end
37
+
38
+ def xmlize_trans_exec(hash)
39
+ hash.to_xml(:root => 'transition_execution', :dasherize => false)
40
+ end
41
+
42
+ def associated_card_number
43
+ File.basename(self.class.site.to_s).to_i
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end