mingle4r 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/README +53 -20
- data/TODO.txt +8 -4
- data/lib/mingle4r.rb +9 -23
- data/lib/mingle4r/api/card.rb +137 -0
- data/lib/mingle4r/api/card/attachment.rb +47 -0
- data/lib/mingle4r/api/card/comment.rb +12 -0
- data/lib/mingle4r/api/card/transition.rb +48 -0
- data/lib/mingle4r/api/murmur.rb +12 -0
- data/lib/mingle4r/api/project.rb +64 -0
- data/lib/mingle4r/api/property_definition.rb +7 -0
- data/lib/mingle4r/api/user.rb +7 -0
- data/lib/mingle4r/api/wiki.rb +10 -0
- data/lib/mingle4r/common_class_methods.rb +0 -1
- data/lib/mingle4r/mingle_client.rb +16 -14
- data/lib/mingle4r/version.rb +1 -1
- metadata +11 -22
- data/lib/mingle4r/api.rb +0 -31
- data/lib/mingle4r/api/v1.rb +0 -25
- data/lib/mingle4r/api/v1/card.rb +0 -173
- data/lib/mingle4r/api/v1/card/attachment.rb +0 -51
- data/lib/mingle4r/api/v1/project.rb +0 -71
- data/lib/mingle4r/api/v1/property_definition.rb +0 -16
- data/lib/mingle4r/api/v1/transition_execution.rb +0 -15
- data/lib/mingle4r/api/v1/user.rb +0 -9
- data/lib/mingle4r/api/v1/wiki.rb +0 -12
- data/lib/mingle4r/api/v2.rb +0 -25
- data/lib/mingle4r/api/v2/card.rb +0 -157
- data/lib/mingle4r/api/v2/card/attachment.rb +0 -51
- data/lib/mingle4r/api/v2/card/comment.rb +0 -16
- data/lib/mingle4r/api/v2/card/transition.rb +0 -52
- data/lib/mingle4r/api/v2/murmur.rb +0 -14
- data/lib/mingle4r/api/v2/project.rb +0 -90
- data/lib/mingle4r/api/v2/property_definition.rb +0 -14
- data/lib/mingle4r/api/v2/user.rb +0 -9
- data/lib/mingle4r/api/v2/wiki.rb +0 -12
- data/lib/mingle4r/common_dyn_class_instance_methods.rb +0 -5
data/History.txt
CHANGED
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
|
33
|
-
|
34
|
-
|
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
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
7
|
-
|
7
|
+
|
8
|
+
maybe nice to nave
|
9
|
+
-------------------
|
10
|
+
|
11
|
+
should transtions give the list of properties to be changed directly
|
data/lib/mingle4r.rb
CHANGED
@@ -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
|
-
|
22
|
-
require 'mingle4r/api/
|
23
|
-
require 'mingle4r/api/
|
24
|
-
require 'mingle4r/api/
|
25
|
-
require 'mingle4r/api/
|
26
|
-
require 'mingle4r/api/
|
27
|
-
require 'mingle4r/api/
|
28
|
-
require 'mingle4r/api/
|
29
|
-
require 'mingle4r/api/
|
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,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
|