mingle4r 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|