mingle4r 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,16 @@
1
+ module Mingle4r
2
+ class API
3
+ class V2
4
+ class Card
5
+ class Comment
6
+ module InstanceMethods
7
+ def to_s
8
+ attributes['content']
9
+ end
10
+ end
11
+ extend Mingle4r::CommonClassMethods
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -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,14 @@
1
+ module Mingle4r
2
+ class API
3
+ class V2
4
+ class Murmur
5
+ extend Mingle4r::CommonClassMethods
6
+ module InstanceMethods
7
+ def to_s
8
+ author.name.to_s + ' - ' + body
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ 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,9 @@
1
+ module Mingle4r
2
+ class API
3
+ class V2
4
+ class User
5
+ extend Mingle4r::CommonClassMethods
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Mingle4r
2
+ class API
3
+ class V2
4
+ class Wiki
5
+ extend Mingle4r::CommonClassMethods
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ Mingle4r::API::V1::Wiki.collection_name = 'wiki'
12
+ Mingle4r::API::V1::Wiki.element_name = 'page'
@@ -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
@@ -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