mingle4r 0.3.0
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/History.txt +40 -0
- data/MIT-LICENSE +21 -0
- data/README +227 -0
- data/TODO.txt +7 -0
- data/lib/mingle4r/api/v1/card/attachment.rb +51 -0
- data/lib/mingle4r/api/v1/card.rb +173 -0
- data/lib/mingle4r/api/v1/project.rb +71 -0
- data/lib/mingle4r/api/v1/property_definition.rb +16 -0
- data/lib/mingle4r/api/v1/transition_execution.rb +15 -0
- data/lib/mingle4r/api/v1/user.rb +9 -0
- data/lib/mingle4r/api/v1/wiki.rb +12 -0
- data/lib/mingle4r/api/v1.rb +25 -0
- data/lib/mingle4r/api/v2/card/attachment.rb +51 -0
- data/lib/mingle4r/api/v2/card/comment.rb +16 -0
- data/lib/mingle4r/api/v2/card/transition.rb +52 -0
- data/lib/mingle4r/api/v2/card.rb +157 -0
- data/lib/mingle4r/api/v2/murmur.rb +14 -0
- data/lib/mingle4r/api/v2/project.rb +90 -0
- data/lib/mingle4r/api/v2/property_definition.rb +14 -0
- data/lib/mingle4r/api/v2/user.rb +9 -0
- data/lib/mingle4r/api/v2/wiki.rb +12 -0
- data/lib/mingle4r/api/v2.rb +25 -0
- data/lib/mingle4r/api.rb +31 -0
- data/lib/mingle4r/common_class_methods.rb +220 -0
- data/lib/mingle4r/common_dyn_class_instance_methods.rb +5 -0
- data/lib/mingle4r/helpers.rb +33 -0
- data/lib/mingle4r/mingle_client.rb +91 -0
- data/lib/mingle4r/version.rb +11 -0
- data/lib/mingle4r.rb +41 -0
- data/lib/mingle_resource.rb +2 -0
- metadata +93 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module Mingle4r
|
2
|
+
class API
|
3
|
+
class V2
|
4
|
+
class Card
|
5
|
+
class Attachment
|
6
|
+
module InstanceMethods
|
7
|
+
# downloads the attachment. It an additional file path is given it saves it at the
|
8
|
+
# given path. The given path should be writable
|
9
|
+
def download(file_name = nil)
|
10
|
+
collection_uri = self.class.site
|
11
|
+
rel_down_url = self.url
|
12
|
+
base_url = "#{collection_uri.scheme}://#{collection_uri.host}:#{collection_uri.port}/"
|
13
|
+
down_uri = URI.join(base_url, rel_down_url)
|
14
|
+
req = Net::HTTP::Get.new(down_uri.path)
|
15
|
+
req.basic_auth self.class.user, self.class.password
|
16
|
+
begin
|
17
|
+
res = Net::HTTP.start(down_uri.host, down_uri.port) { |http| http.request(req) }
|
18
|
+
file_name ||= self.file_name()
|
19
|
+
File.open(file_name, 'w') { |f| f.print(res.body) }
|
20
|
+
rescue Exception => e
|
21
|
+
e.message
|
22
|
+
end
|
23
|
+
end # download
|
24
|
+
|
25
|
+
# alias for file_name
|
26
|
+
def name
|
27
|
+
file_name()
|
28
|
+
end
|
29
|
+
|
30
|
+
# so that active resource tries to find by proper id
|
31
|
+
def id
|
32
|
+
name()
|
33
|
+
end
|
34
|
+
|
35
|
+
# This method had to be overriden.
|
36
|
+
# normal active resource destroy doesn't work as mingle site for deleting attachments doesn't end with .xml.
|
37
|
+
def destroy
|
38
|
+
connection = self.send(:connection)
|
39
|
+
# deletes the attachment by removing .xml at the end
|
40
|
+
connection.delete(self.send(:element_path).gsub(/\.xml\z/, ''))
|
41
|
+
end
|
42
|
+
alias_method :delete, :destroy
|
43
|
+
end #module InstanceMethods
|
44
|
+
|
45
|
+
extend Mingle4r::CommonClassMethods
|
46
|
+
|
47
|
+
end # class Attachment
|
48
|
+
end # class Card
|
49
|
+
end # class V2
|
50
|
+
end # class API
|
51
|
+
end # module Mingle4r
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Mingle4r
|
2
|
+
class API
|
3
|
+
class V2
|
4
|
+
class Card
|
5
|
+
class Transition
|
6
|
+
extend Mingle4r::CommonClassMethods
|
7
|
+
|
8
|
+
module InstanceMethods
|
9
|
+
def execute(args = {})
|
10
|
+
args.symbolize_keys!
|
11
|
+
trans_exec_xml = convert_to_xml(args)
|
12
|
+
# set_transition_execution_attributes
|
13
|
+
conn = self.class.connection
|
14
|
+
url_path =URI.parse(transition_execution_url()).path
|
15
|
+
conn.post(url_path, trans_exec_xml, self.class.headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def convert_to_xml(args)
|
20
|
+
hash = create_transition_exec_hash(args)
|
21
|
+
xmlize_trans_exec(hash)
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_transition_exec_hash(args)
|
25
|
+
transition_hash = {}
|
26
|
+
transition_hash['card'] = (args.delete(:card) || associated_card_number).to_i
|
27
|
+
args.delete(:name) || args.delete(:transition)
|
28
|
+
|
29
|
+
comment = args.delete(:comment)
|
30
|
+
transition_hash['comment'] = comment if comment
|
31
|
+
properties = []
|
32
|
+
args.each do |name, value|
|
33
|
+
property = {'name' => name.to_s, 'value' => value}
|
34
|
+
properties.push(property)
|
35
|
+
end
|
36
|
+
transition_hash['properties'] = properties unless properties.empty?
|
37
|
+
transition_hash
|
38
|
+
end
|
39
|
+
|
40
|
+
def xmlize_trans_exec(hash)
|
41
|
+
hash.to_xml(:root => 'transition_execution', :dasherize => false)
|
42
|
+
end
|
43
|
+
|
44
|
+
def associated_card_number
|
45
|
+
File.basename(self.class.site.to_s).to_i
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module Mingle4r
|
2
|
+
class API
|
3
|
+
class V2
|
4
|
+
class Card
|
5
|
+
extend Mingle4r::CommonClassMethods
|
6
|
+
|
7
|
+
# overwrite the default find in CommonClassMethods
|
8
|
+
def self.find(*args)
|
9
|
+
scope = args.slice!(0)
|
10
|
+
options = args.slice!(0) || {}
|
11
|
+
@resource_class.find_without_pagination(scope, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def find_without_pagination(*args)
|
16
|
+
scope = args.slice!(0)
|
17
|
+
options = args.slice!(0) || {}
|
18
|
+
options[:params] ||= {}
|
19
|
+
options[:params].merge!({:page => 'all'})
|
20
|
+
# call ActiveResource::Base::find with proper options
|
21
|
+
find(scope, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
# applies an mql filter on card types. Look at https://mingle05.thoughtworks.com/help/mql_reference.html
|
25
|
+
# for reference
|
26
|
+
def apply_filter(filter_string)
|
27
|
+
find_without_pagination(:all, :params => {'filters[mql]'.to_sym => filter_string})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
# so that active resource tries to find by number
|
33
|
+
def id
|
34
|
+
number()
|
35
|
+
end
|
36
|
+
|
37
|
+
def attachments(refresh = false)
|
38
|
+
return @attachments if(!refresh && @attachments)
|
39
|
+
attachment_site = File.join(self.class.site.to_s, "cards/#{self.number()}").to_s
|
40
|
+
Card::Attachment.site = attachment_site
|
41
|
+
Card::Attachment.user = self.class.user
|
42
|
+
Card::Attachment.password = self.class.password
|
43
|
+
attachment_class = Card::Attachment.send(:create_resource_class)
|
44
|
+
@attachments = attachment_class.find(:all)
|
45
|
+
end
|
46
|
+
|
47
|
+
def comments(refresh = false)
|
48
|
+
return @comments if(!refresh && @comments)
|
49
|
+
set_comment_class_attributes
|
50
|
+
@comments = Card::Comment.find(:all)
|
51
|
+
end
|
52
|
+
|
53
|
+
def transitions(refresh = false)
|
54
|
+
return @transitions if(!refresh && @transitions)
|
55
|
+
set_transition_class_attributes
|
56
|
+
@transitions = Card::Transition.find(:all)
|
57
|
+
end
|
58
|
+
|
59
|
+
def murmurs(refresh = false)
|
60
|
+
return @murmurs if(!refresh && @murmurs)
|
61
|
+
set_murmur_class_attributes
|
62
|
+
@murmurs = Murmur.find(:all)
|
63
|
+
end
|
64
|
+
|
65
|
+
def upload_attachment(file_path)
|
66
|
+
attachment_uri = URI.parse(File.join(self.class.site.to_s, "cards/#{self.number()}/attachments.xml"))
|
67
|
+
http = Net::HTTP.new(attachment_uri.host, attachment_uri.port)
|
68
|
+
http.use_ssl = attachment_uri.is_a?(URI::HTTPS)
|
69
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
|
70
|
+
basic_encode = 'Basic ' + ["#{self.class.user}:#{self.class.password}"].pack('m').delete("\r\n")
|
71
|
+
|
72
|
+
post_headers = {
|
73
|
+
'Authorization' => basic_encode,
|
74
|
+
'Content-Type' => 'multipart/form-data; boundary=----------XnJLe9ZIbbGUYtzPQJ16u1'
|
75
|
+
}
|
76
|
+
|
77
|
+
file_content = IO.read(file_path)
|
78
|
+
|
79
|
+
post_body = <<EOS
|
80
|
+
------------XnJLe9ZIbbGUYtzPQJ16u1\r
|
81
|
+
Content-Disposition: form-data; name="file"; filename="#{File.basename(file_path)}"\r
|
82
|
+
Content-Type: application/octet-stream\r
|
83
|
+
Content-Length: #{file_content.size}\r
|
84
|
+
\r
|
85
|
+
#{file_content}\r
|
86
|
+
------------XnJLe9ZIbbGUYtzPQJ16u1--\r
|
87
|
+
EOS
|
88
|
+
|
89
|
+
http.post(attachment_uri.path, post_body, post_headers)
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_comment(str)
|
93
|
+
set_comment_class_attributes
|
94
|
+
comment = Card::Comment.new(:content => str.to_s)
|
95
|
+
comment.save
|
96
|
+
end
|
97
|
+
|
98
|
+
def execute_transition(args)
|
99
|
+
trans_name = args.symbolize_keys[:name]
|
100
|
+
transition = transitions.detect { |t| t.name == trans_name}
|
101
|
+
transition.execute(args)
|
102
|
+
end
|
103
|
+
|
104
|
+
# returns back the version of the card given. If an invalid version is given, the latest
|
105
|
+
# version is returned, takes a number or :next or :before
|
106
|
+
def at_version(version_no)
|
107
|
+
version_2_find = 0
|
108
|
+
case version_no
|
109
|
+
when :before
|
110
|
+
version_2_find = self.version.to_i - 1
|
111
|
+
when :next
|
112
|
+
version_2_find = self.version.to_i + 1
|
113
|
+
else
|
114
|
+
version_2_find = version_no.to_i
|
115
|
+
end
|
116
|
+
self.class.find(self.number, :params => {:version => version_2_find})
|
117
|
+
end
|
118
|
+
|
119
|
+
# Gets and sets the value of a property. The property name given should be the same
|
120
|
+
# as the mingle property name. the value is optional
|
121
|
+
def property_value(name, val = nil)
|
122
|
+
property = properties.detect { |p| p.name == name }
|
123
|
+
val ? property.value = val : property.value
|
124
|
+
end
|
125
|
+
|
126
|
+
# Gets the custom properties in the form of an array of hashes with the property names as keys and
|
127
|
+
# property values as the value
|
128
|
+
def custom_properties
|
129
|
+
properties.map { |p| {p.name => p.value} }
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
def set_comment_class_attributes
|
134
|
+
comment_site = File.join(self.class.site.to_s, "cards/#{self.number()}").to_s
|
135
|
+
Card::Comment.site = comment_site
|
136
|
+
Card::Comment.user = self.class.user
|
137
|
+
Card::Comment.password = self.class.password
|
138
|
+
end
|
139
|
+
|
140
|
+
def set_transition_class_attributes
|
141
|
+
transition_site = File.join(self.class.site.to_s, "cards/#{self.number()}").to_s
|
142
|
+
Card::Transition.site = transition_site
|
143
|
+
Card::Transition.user = self.class.user
|
144
|
+
Card::Transition.password = self.class.password
|
145
|
+
end
|
146
|
+
|
147
|
+
def set_murmur_class_attributes
|
148
|
+
murmur_site = File.join(self.class.site.to_s, "cards/#{self.number()}").to_s
|
149
|
+
V2::Murmur.site = murmur_site
|
150
|
+
V2::Murmur.user = self.class.user
|
151
|
+
V2::Murmur.password = self.class.password
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Mingle4r
|
2
|
+
class API
|
3
|
+
class V2
|
4
|
+
class Project
|
5
|
+
module InstanceMethods
|
6
|
+
# returns the cards for the project. To hit the resource server without returning
|
7
|
+
# cached results pass true as an argument.
|
8
|
+
def cards(refresh = false)
|
9
|
+
return @cards if(!refresh && @cards_cached)
|
10
|
+
cards_site = File.join(self.class.site.to_s, "projects/#{self.identifier()}")
|
11
|
+
Card.site = cards_site
|
12
|
+
Card.user = self.class.user
|
13
|
+
Card.password = self.class.password
|
14
|
+
# @cards = Card.send(:create_resource_class).find_without_pagination(:all)
|
15
|
+
@cards = Card.find_without_pagination(:all)
|
16
|
+
@cards_cached = true
|
17
|
+
@cards
|
18
|
+
end
|
19
|
+
|
20
|
+
# returns the users for the project. To hit the resource server without returning
|
21
|
+
# cached results pass true as an argument.
|
22
|
+
def users(refresh = false)
|
23
|
+
return @users if(!refresh && @users_cached)
|
24
|
+
users_site = File.join(self.class.site.to_s, "projects/#{self.identifier()}")
|
25
|
+
User.site = users_site
|
26
|
+
User.user = self.class.user
|
27
|
+
User.password = self.class.password
|
28
|
+
User.element_name = nil # reset
|
29
|
+
user_class = User.send(:create_resource_class)
|
30
|
+
@users = user_class.find(:all)
|
31
|
+
@users_cached = true
|
32
|
+
@users
|
33
|
+
end
|
34
|
+
|
35
|
+
# returns the wikis for the project. To hit the resource server without returning
|
36
|
+
# cached results pass true as an argument.
|
37
|
+
def wikis(refresh = false)
|
38
|
+
return @wikis if(!refresh && @wikis_cached)
|
39
|
+
wiki_site = File.join(self.class.site.to_s, "projects/#{self.identifier()}")
|
40
|
+
Wiki.site = wiki_site
|
41
|
+
Wiki.user = self.class.user
|
42
|
+
Wiki.password = self.class.password
|
43
|
+
wiki_class = Wiki.send(:create_resource_class)
|
44
|
+
@wikis = wiki_class.find(:all)
|
45
|
+
@wikis_cached = true
|
46
|
+
@wikis
|
47
|
+
end
|
48
|
+
|
49
|
+
# returns the property definitions for the project. To hit the resource server
|
50
|
+
# pass true as an argument
|
51
|
+
def property_definitions(refresh = false)
|
52
|
+
return @prop_definitions if(!refresh && @prop_definitions_cached)
|
53
|
+
properties_site = File.join(self.class.site.to_s, "/projects/#{self.identifier}")
|
54
|
+
PropertyDefinition.site = properties_site
|
55
|
+
PropertyDefinition.user = self.class.user
|
56
|
+
PropertyDefinition.password = self.class.password
|
57
|
+
prop_defn_class = PropertyDefinition.send(:create_resource_class)
|
58
|
+
@prop_definitions = prop_defn_class.find(:all)
|
59
|
+
@prop_definitions_cached = true
|
60
|
+
@prop_definitions
|
61
|
+
end
|
62
|
+
|
63
|
+
# returns the murmurs for the project. To hit the resource server without returning
|
64
|
+
# cached results pass true as an argument.
|
65
|
+
def murmurs(refresh = false)
|
66
|
+
return @murmurs if(!refresh && @murmurs)
|
67
|
+
murmur_site = File.join(self.class.site.to_s, "/projects/#{self.identifier}")
|
68
|
+
Murmur.site = murmurs_site
|
69
|
+
Murmur.user = self.class.user
|
70
|
+
Murmur.password = self.class.password
|
71
|
+
@murmurs = Murmur.find(:all)
|
72
|
+
end
|
73
|
+
|
74
|
+
# posts a murmur
|
75
|
+
def post_murmur(str)
|
76
|
+
murmurs_site = File.join(self.class.site.to_s, "projects/#{self.identifier}")
|
77
|
+
Murmur.site = murmurs_site
|
78
|
+
Murmur.user = self.class.user
|
79
|
+
Murmur.password = self.class.password
|
80
|
+
|
81
|
+
murmur = Murmur.new(:body => str.to_s)
|
82
|
+
murmur.save
|
83
|
+
end
|
84
|
+
end # module InstanceMethods
|
85
|
+
|
86
|
+
extend Mingle4r::CommonClassMethods
|
87
|
+
end # class Project
|
88
|
+
end # class V1
|
89
|
+
end # class API
|
90
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Mingle4r
|
2
|
+
class API
|
3
|
+
class V2
|
4
|
+
class PropertyDefinition
|
5
|
+
extend Mingle4r::CommonClassMethods
|
6
|
+
|
7
|
+
def self.column_name_for(prop_name)
|
8
|
+
property_def = @resource_class.find(:all).detect { |prop| prop.name == prop_name }
|
9
|
+
property_def ? property_def.column_name : nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Mingle4r
|
2
|
+
class API
|
3
|
+
class V2
|
4
|
+
def initialize(host_url)
|
5
|
+
@host_uri = URI.parse(host_url)
|
6
|
+
end
|
7
|
+
|
8
|
+
def base_url
|
9
|
+
File.join(@host_uri.to_s, '/api/v2')
|
10
|
+
end
|
11
|
+
|
12
|
+
def version
|
13
|
+
2
|
14
|
+
end
|
15
|
+
|
16
|
+
def project_class
|
17
|
+
Project
|
18
|
+
end
|
19
|
+
|
20
|
+
def user_class
|
21
|
+
User
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/mingle4r/api.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
module Mingle4r
|
5
|
+
class API
|
6
|
+
class << self
|
7
|
+
def create(url)
|
8
|
+
@host_uri = URI.parse(url)
|
9
|
+
api_ver = mingle_version.to_i - 1
|
10
|
+
class_name = 'V' + api_ver.to_s
|
11
|
+
ver_class = send(:const_get, class_name.to_sym)
|
12
|
+
ver_class.new(url)
|
13
|
+
end
|
14
|
+
|
15
|
+
def mingle_version
|
16
|
+
html = mingle_about_page
|
17
|
+
match = html.match('<dd>Version</dd>\n.*<dt>([_\d]*)</dt>')
|
18
|
+
raise Exception, 'Not a proper mingle instance' unless match
|
19
|
+
@mingle_version = match[1].gsub('_','.').to_f
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def mingle_about_page
|
24
|
+
http = Net::HTTP.new(@host_uri.host, @host_uri.port)
|
25
|
+
http.use_ssl = true if @host_uri.is_a?(URI::HTTPS)
|
26
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
|
27
|
+
http.get('/about').body
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end # class API
|
31
|
+
end
|