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.
@@ -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