arusarka-mingle4r 0.2.5 → 0.2.7

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,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 Arusarka Haldar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'spec/rake/spectask'
6
+ require 'rake/gempackagetask'
7
+
8
+ namespace :gem do
9
+ desc "Builds the gem"
10
+ task :build do
11
+ file_name = "#{File.dirname(__FILE__)}/mingle4r.gemspec"
12
+ spec = Gem::Specification.load(file_name)
13
+ Gem::Builder.new(spec).build
14
+ file = FileList["#{File.dirname(__FILE__)}/mingle4r*.gem"]
15
+ mv file.to_a, "#{File.dirname(__FILE__)}/pkg"
16
+ end
17
+ end
18
+
19
+ # task : run specs
20
+ Spec::Rake::SpecTask.new do |t|
21
+ t.spec_files = FileList['spec/**/*_spec.rb']
22
+ t.verbose = true
23
+ end
@@ -0,0 +1,4 @@
1
+ Write documentation
2
+ Test the new methods in card class
3
+ test wiki, property definitions api extensively
4
+ subversion configuration api
@@ -0,0 +1,23 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'logger'
4
+ require 'rubygems'
5
+ require 'active_resource'
6
+ require 'net/http'
7
+
8
+ # This module should be used to interact with mingle. Look in the readme for examples.
9
+ module Mingle4r
10
+ AUTHOR = 'Asur'
11
+ end
12
+
13
+ require 'mingle_resource'
14
+ require 'mingle4r/version'
15
+ require 'mingle4r/common_class_methods'
16
+ require 'mingle4r/common_dyn_class_instance_methods'
17
+ require 'mingle4r/helpers'
18
+ require 'mingle4r/user'
19
+ require 'mingle4r/card'
20
+ require 'mingle4r/project'
21
+ require 'mingle4r/property_definition'
22
+ require 'mingle4r/wiki'
23
+ require 'mingle4r/mingle_client'
@@ -0,0 +1,139 @@
1
+ require 'mingle4r/card/attachment'
2
+
3
+ module Mingle4r
4
+ class Card
5
+ module ClassMethods
6
+ def find_without_pagination(*args)
7
+ scope = args.slice!(0)
8
+ options = args.slice!(0) || {}
9
+ options[:params] ||= {}
10
+ options[:params].merge!({:page => 'all'})
11
+
12
+ # call ActiveResource::Base::find with proper options
13
+ find(scope, options)
14
+ end
15
+
16
+ # applies an mql filter on card types. Look at https://mingle05.thoughtworks.com/help/mql_reference.html
17
+ # for reference
18
+ def apply_filter(filter_string)
19
+ find_without_pagination(:all, :params => {'filters[mql]'.to_sym => filter_string})
20
+ end
21
+ end
22
+
23
+ module InstanceMethods
24
+ def attachments(refresh = false)
25
+ return @attachments if(!refresh && @attachments_cached)
26
+ attachment_site = File.join(self.class.site.to_s, "cards/#{self.number()}").to_s
27
+ Mingle4r::Card::Attachment.site = attachment_site
28
+ Mingle4r::Card::Attachment.user = self.class.user
29
+ Mingle4r::Card::Attachment.password = self.class.password
30
+ attachment_class = Mingle4r::Card::Attachment.send(:create_resource_class)
31
+ @attachments = attachment_class.find(:all)
32
+ @attachments_cached = true
33
+ @attachments
34
+ end
35
+
36
+ def upload_attachment(file_path)
37
+ attachment_uri = URI.parse(File.join(self.class.site.to_s, "cards/#{self.number()}/attachments.xml"))
38
+
39
+ http = Net::HTTP.new(attachment_uri.host, attachment_uri.port)
40
+ http.use_ssl = attachment_uri.is_a?(URI::HTTPS)
41
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
42
+
43
+ basic_encode = 'Basic ' + ["#{self.class.user}:#{self.class.password}"].pack('m').delete("\r\n")
44
+
45
+ post_headers = {
46
+ 'Authorization' => basic_encode,
47
+ 'Content-Type' => 'multipart/form-data; boundary=----------XnJLe9ZIbbGUYtzPQJ16u1'
48
+ }
49
+
50
+ file_content = IO.read(file_path)
51
+
52
+ post_body = <<EOS
53
+ ------------XnJLe9ZIbbGUYtzPQJ16u1\r
54
+ Content-Disposition: form-data; name="file"; filename="#{File.basename(file_path)}"\r
55
+ Content-Type: application/octet-stream\r
56
+ Content-Length: #{file_content.size}\r
57
+ \r
58
+ #{file_content}\r
59
+ ------------XnJLe9ZIbbGUYtzPQJ16u1--\r
60
+ EOS
61
+
62
+ http.post(attachment_uri.path, post_body, post_headers)
63
+ end
64
+
65
+ # returns back the version of the card given. If an invalid version is given, the latest
66
+ # version is returned, takes a number or :next or :before
67
+ def at_version(version_no)
68
+ version_2_find = 0
69
+ case version_no
70
+ when :before
71
+ version_2_find = self.version.to_i - 1
72
+ when :next
73
+ version_2_find = self.version.to_i + 1
74
+ else
75
+ version_2_find = version_no.to_i
76
+ end
77
+ self.class.find(self.number, :params => {:version => version_2_find})
78
+ end
79
+
80
+ # adds a comment to a card.
81
+ def add_comment(comment)
82
+ comment_uri = URI.parse(File.join(self.class.site.to_s, "cards/add_comment?card_id=#{self.id}"))
83
+
84
+ http = Net::HTTP.new(comment_uri.host, comment_uri.port)
85
+ http.use_ssl = comment_uri.is_a?(URI::HTTPS)
86
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
87
+
88
+ basic_encode = 'Basic ' + ["#{self.class.user}:#{self.class.password}"].pack('m').delete("\r\n")
89
+
90
+ post_headers = {
91
+ 'Authorization' => basic_encode,
92
+ 'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8'
93
+ }
94
+
95
+ post_body = "comment=#{comment}&card_id=#{self.id}"
96
+
97
+ http.post(comment_uri.path, post_body, post_headers)
98
+ end
99
+
100
+ # TODO - Check if working with https
101
+ # Executes the given transition on the card.
102
+ # Example :
103
+ # defect_card.execute_transition(:transition_name => 'Close Defect', :Owner => nil, :Status => 'Closed', :transition_comment => comment)
104
+ # transition_comment is mandatory if the transition is set that way.
105
+ # after transition 'Owner' would have value 'Not Set' and 'Status' would be 'Closed' for defect card
106
+ def execute_transition(args_hash)
107
+ project_id = File.basename(self.class.site.to_s)
108
+ transition_name = args_hash.delete(:transition_name)
109
+ raise 'Transition name for given' unless transition_name
110
+ transition_uri = URI.parse(File.join(self.class.site.to_s, 'transition_executions.xml'))
111
+
112
+ # create the form data
113
+ form_data = {
114
+ 'transition_execution[transition]' => transition_name,
115
+ 'transition_execution[card]' => self.number.to_s
116
+ }
117
+ comment = args_hash.delete(:transition_comment)
118
+ form_data['transition_execution[comment]'] = Helpers.encode2html(comment) if comment
119
+ args_hash.each do |key, value|
120
+ form_data['transition_execution[properties][][name]'] = Helpers.encode2html(key.to_s)
121
+ form_data['transition_execution[properties][][value]'] = Helpers.encode2html(value.to_s)
122
+ end
123
+
124
+ req = Net::HTTP::Post.new(transition_uri.path)
125
+ req.basic_auth self.class.user, self.class.password
126
+ req.set_form_data(form_data)
127
+
128
+ Net::HTTP.new(transition_uri.host, transition_uri.port).start { |http| http.request(req) }
129
+ end
130
+
131
+ private
132
+ def boundary
133
+ '----------XnJLe9ZIbbGUYtzPQJ16u1'
134
+ end
135
+ end
136
+
137
+ extend Mingle4r::CommonClassMethods
138
+ end
139
+ end
@@ -0,0 +1,47 @@
1
+ module Mingle4r
2
+ class Card
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 Card
47
+ end # module Mingle4r
@@ -0,0 +1,219 @@
1
+ module Mingle4r
2
+ # ActiveResource makes connecting to rest resources very easy. However it has one problem
3
+ # and a big one at that. If you try setting the authentication credentials or the site or
4
+ # collection name, element name for the class for the second time it doesn't work. E.g.
5
+ #
6
+ # class Person < ActiveResource::base
7
+ # self.site = 'http://localhost:9090/'
8
+ # end
9
+ #
10
+ # After sometime you change it to
11
+ #
12
+ # Person.site = 'https://org-server/my_proj/'
13
+ # Person.user = 'admin'
14
+ # Person.password = 'secret'
15
+ #
16
+ # Then you do
17
+ #
18
+ # Person.find(:all) => It bombs
19
+ #
20
+ # This module provides a mechanism by which you can get rid of this problem. Extend this
21
+ # class in the actual class itself. Do not extend the extended class from ActiveResource::Base.
22
+ #
23
+ # E.g.
24
+ #
25
+ # class Person
26
+ # extend CommonClassMethods
27
+ # end
28
+ #
29
+ # set the credentials
30
+ #
31
+ # Person.site = 'http://localhost:8080'
32
+ # Person.user = 'foo'
33
+ # Person.password = 'bar'
34
+ #
35
+ # Thats it. Now create some objects
36
+ #
37
+ # asur = Person.new(:name => 'Asur', :job => 'fooling around', :status => 'Single and ready 2 mingle')
38
+ # asur.save
39
+ #
40
+ # Now change the class attributes
41
+ #
42
+ # Person.site = 'https://org-server/mingle'
43
+ # Person.collection_name = 'boring_people'
44
+ #
45
+ # Now instantiate an object
46
+ #
47
+ # rakhshas = Person.new(:name => 'Rakhshas', :job => 'eating people', :status => 'just woke up and hungry')
48
+ # rakhshas.save => Voila !!!!!!! it works
49
+ #
50
+ # CUSTOMIZATIONS
51
+ # --------------
52
+ #
53
+ # No amount of wrapping can provide very detailed customizations. Either you have a lot of methods
54
+ # that are not being used or there is hardly anything at all. To oversome this problem this module
55
+ # was written to provide only those methods which are common to most active resource objects.
56
+ # However if you want to have a little more control over your active resource objects its very easy.
57
+ # Here's how you would do it normally
58
+ #
59
+ # class Person < ActiveResource::Base
60
+ # def self.count
61
+ # find(:all).size
62
+ # end
63
+ #
64
+ # def occupation
65
+ # return job if job
66
+ # 'Unemployed'
67
+ # end
68
+ # end
69
+ #
70
+ # To do the same thing, here's how you do it using this library
71
+ #
72
+ # class Person
73
+ # module ClassMethods
74
+ # def count
75
+ # find(:all).size
76
+ # end
77
+ # end
78
+ #
79
+ # module InstanceMethods
80
+ # def occupation
81
+ # return job if job
82
+ # 'Unemployed'
83
+ # end
84
+ # end
85
+ # extend CommonClassMethods
86
+ # end
87
+ #
88
+ # The instance methods will be available as instance methods in the objects created, class methods
89
+ # will be available as class methods in the class of the object.
90
+
91
+ module CommonClassMethods
92
+ attr_reader :site, :user, :password
93
+
94
+ # creates an object of the class in which this module is extended
95
+ def new(args = {})
96
+ # @resource_class = create_resource_class() # should this be commented
97
+ @resource_class.new(args)
98
+ end
99
+
100
+ # sets the site for the class in which this module is extended
101
+ def site=(site)
102
+ if site != self.site
103
+ @site = site
104
+ uri = URI.parse(site)
105
+ @user = URI.decode(uri.user) if(uri.user)
106
+ @password = URI.decode(uri.password) if(uri.password)
107
+ @resource_class = self.send(:create_resource_class)
108
+ end
109
+ @site
110
+ end
111
+
112
+ # sets the user for the class in which this module is extended
113
+ def user=(user)
114
+ if user != self.user
115
+ @user = user
116
+ @resource_class = self.send(:create_resource_class) if(site)
117
+ end
118
+ @user
119
+ end
120
+
121
+ # sets the password for the class in which this module is extended
122
+ def password=(password)
123
+ if password != self.password
124
+ @password = password
125
+ @resource_class = self.send(:create_resource_class) if(site)
126
+ end
127
+ @password
128
+ end
129
+
130
+ # sets the collection name for the class in which this module is extended
131
+ def collection_name=(collection_name)
132
+ if collection_name != self.collection_name
133
+ @collection_name = collection_name
134
+ end
135
+ @collection_name
136
+ end
137
+
138
+ # sets the elment name for the class in which this module is extended
139
+ def element_name=(element_name)
140
+ if element_name != self.element_name
141
+ @element_name = element_name
142
+ end
143
+ @element_name
144
+ end
145
+
146
+ # collection name for the class in which this module is extended
147
+ def collection_name
148
+ @collection_name || class_name.underscore.pluralize
149
+ end
150
+
151
+ # element name for the class in which this module is extended
152
+ def element_name
153
+ @element_name || class_name.underscore
154
+ end
155
+
156
+ # def all_attributes_set?
157
+ # site && user && password
158
+ # end
159
+
160
+ # routes to active resource find
161
+ def find(*args)
162
+ scope = args.slice!(0)
163
+ options = args.slice!(0) || {}
164
+ obj = @resource_class.find(scope, options)
165
+ obj
166
+ end
167
+
168
+ private
169
+ # creates an active resource class dynamically. All the attributes are set automatically. Avoid calling
170
+ # this method directly
171
+ def create_resource_class
172
+ # raise exceptions if any of site, user or password is not set
173
+ raise "Please set the site for #{self} class before using create_resource_class()." unless(self.site)
174
+
175
+ created_class = Class.new(MingleResource)
176
+
177
+ # set the resource options
178
+ created_class.site = self.site
179
+ created_class.user = self.user
180
+ created_class.password = self.password
181
+ created_class.collection_name = self.collection_name
182
+ created_class.element_name = self.element_name
183
+
184
+ created_class_name = "#{self}::#{class_name}#{Mingle4r::Helpers.fast_token()}"
185
+ eval "#{created_class_name} = created_class"
186
+
187
+ # includes a module called InstanceMethods in the class created dynamically
188
+ # if it is defined inside the wrapper class
189
+ inst_meth_mod_name = instance_methods_module_name()
190
+ created_class.send(:include, self.const_get(inst_meth_mod_name.to_sym)) if inst_meth_mod_name
191
+ created_class.send(:include, Mingle4r::CommonDynClassInstanceMethods)
192
+
193
+ # extends the class created dynamically with a module called ClassMethods if
194
+ # it is defined inside the wrapper class
195
+ class_meth_mod_name = class_methods_module_name()
196
+ created_class.extend(self.const_get(class_meth_mod_name)) if class_meth_mod_name
197
+
198
+ created_class
199
+ end
200
+
201
+ def class_name
202
+ self.name.split('::')[-1]
203
+ end
204
+
205
+ def instance_methods_module_name
206
+ inst_meth_mod_name = 'InstanceMethods'
207
+ self.constants.detect { |const| const.split('::')[-1] =~ /#{inst_meth_mod_name}/ }
208
+ end
209
+
210
+ def class_methods_module_name
211
+ class_meth_mod_name = 'ClassMethods'
212
+ self.constants.detect { |const| const.split('::')[-1] =~ /#{class_meth_mod_name}/ }
213
+ end
214
+
215
+ def method_missing(meth_id, *args, &block)
216
+ @resource_class.send(meth_id, *args, &block)
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,40 @@
1
+ module Mingle4r
2
+ # common dynamic class instance methods
3
+ module CommonDynClassInstanceMethods
4
+ # gets the value of a property. The property name can be given in several ways.
5
+ # suppose we are trying to get the property 'Iteration Number' as in mingle
6
+ #
7
+ # 1) give the name as seen card.property_value('Iteration Number')
8
+ #
9
+ # 2) give the property method name i.e. in the active resource object it would
10
+ # become by default 'cp_iteration_number' attribute unless set differently in
11
+ # Mingle
12
+ #
13
+ # 3) give the method a 'space separated' / 'camelcased' method name. e.g. - 'Iteration Number'
14
+ # 'IterationNumber'
15
+ def property_value(prop_name)
16
+ begin
17
+ column_name = PropertyDefinition.column_name_for(prop_name.to_s)
18
+ self.send(column_name.to_sym)
19
+ rescue NoMethodError
20
+ if self.attributes.has_key?(prop_name)
21
+ self.attributes[prop_name]
22
+ else
23
+ # try calling a underscorized method. eg 'Iteration Number' becomes
24
+ # 'iteration_number'
25
+ method_id = (prop_name.split(' ').map { |substring| substring.downcase }).join('_').underscore.to_sym
26
+ self.send(method_id)
27
+ end
28
+ end
29
+ end
30
+
31
+ def custom_properties
32
+ custom_props = []
33
+ props = attributes.keys
34
+ PropertyDefinition.find(:all).each do |prop|
35
+ custom_props << {prop.name => prop.column_name} if(props.include?(prop.column_name))
36
+ end
37
+ custom_props
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ module Mingle4r
2
+ module Helpers
3
+ def Helpers.fast_token
4
+ values = [
5
+ rand(0x0010000),
6
+ rand(0x0010000),
7
+ rand(0x0010000),
8
+ rand(0x0010000),
9
+ rand(0x0010000),
10
+ rand(0x1000000),
11
+ rand(0x1000000),
12
+ ]
13
+ "%04x%04x%04x%04x%04x%06x%06x" % values
14
+ end
15
+
16
+ def Helpers.encode2html(string)
17
+ html_char_map = {
18
+ '[' => '%5B', ']' => '%5D',
19
+ '(' => '%28', ')' => '%29',
20
+ ',' => '%2C', ' ' => '%20',
21
+ '=' => '%3D', '\'' => '%27',
22
+ '<' => '%3C', '>' => '%3E',
23
+ }
24
+
25
+ string.strip! if string
26
+ encoded_string = ''
27
+ string.each_char do |char|
28
+ encoded_string << (html_char_map[char] || char)
29
+ end
30
+ encoded_string
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,106 @@
1
+ module Mingle4r
2
+ class MingleClient
3
+ attr_reader :site, :user, :password, :proj_id
4
+
5
+ def initialize(site, user, password, proj_id = nil)
6
+ @site = site
7
+ @user = user
8
+ @password = password
9
+ @proj_id = proj_id
10
+ set_resource_attributes()
11
+ end
12
+
13
+ def site=(site)
14
+ if site != self.site
15
+ @site = site
16
+ uri = URI.parse(site)
17
+ @user = URI.decode(uri.user) if(uri.user)
18
+ @password = URI.decode(uri.password) if(uri.password)
19
+ set_resource_attributes()
20
+ end
21
+ @site
22
+ end
23
+
24
+ def user=(user)
25
+ if user != self.user
26
+ @user = user
27
+ set_resource_attributes()
28
+ end
29
+ @user
30
+ end
31
+
32
+ def password=(password)
33
+ if password != self.password
34
+ @password = password
35
+ set_resource_attributes()
36
+ end
37
+ @password
38
+ end
39
+
40
+ def proj_id=(proj_id)
41
+ if proj_id != @proj_id
42
+ @proj_id = proj_id
43
+ set_resource_attributes()
44
+ end
45
+ @proj_id
46
+ end
47
+
48
+ def valid_credentials?
49
+ Project.site = site
50
+ Project.user = user
51
+ Project.password = password
52
+ begin
53
+ Project.find(:all)
54
+ true
55
+ rescue Exception => e
56
+ e.message
57
+ end
58
+ end
59
+
60
+ def project
61
+ raise 'proj_id attribute not set' unless @proj_id
62
+ @project = Mingle4r::Project.find(@proj_id) unless(@project && (@proj_id == @project.identifier))
63
+ @project
64
+ end
65
+
66
+ def projects
67
+ Mingle4r::Project.find(:all)
68
+ end
69
+
70
+ def cards
71
+ raise 'proj_id attribute not set' unless @proj_id
72
+ @project = Mingle4r::Project.find(@proj_id) unless(@project && (@proj_id == @project.identifier))
73
+ @project.cards
74
+ end
75
+
76
+ def users
77
+ Mingle4r::User.find(:all)
78
+ end
79
+
80
+ private
81
+ def set_resource_attributes
82
+ set_project_attributes
83
+ set_user_attributes
84
+ end
85
+
86
+ def set_project_attributes
87
+ Project.site = @site
88
+ Project.user = @user
89
+ Project.password = @password
90
+ end
91
+
92
+ # def set_property_definition_attributes
93
+ # raise 'Project Id (proj_id attribute) not given.' unless @proj_id
94
+ # properties_site = File.join(@site.to_s, "projects/#{@proj_id}")
95
+ # PropertyDefinition.site = properties_site
96
+ # PropertyDefinition.user = @user
97
+ # PropertyDefinition.password = @password
98
+ # end
99
+
100
+ def set_user_attributes
101
+ User.site = @site
102
+ User.user = user
103
+ User.password = @password
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,64 @@
1
+ module Mingle4r
2
+ class Project
3
+ module InstanceMethods
4
+ # returns the cards for the project. To hit the resource server without returning
5
+ # cached results pass true as an argument.
6
+ def cards(refresh = false)
7
+ return @cards if(!refresh && @cards_cached)
8
+ cards_site = File.join(self.class.site.to_s, "projects/#{self.identifier()}")
9
+ Mingle4r::Card.site = cards_site
10
+ Mingle4r::Card.user = self.class.user
11
+ Mingle4r::Card.password = self.class.password
12
+ card_class = Mingle4r::Card.send(:create_resource_class)
13
+ @cards = card_class.find_without_pagination(:all)
14
+ @cards_cached = true
15
+ @cards
16
+ end
17
+
18
+ # returns the users for the project. To hit the resource server without returning
19
+ # cached results pass true as an argument.
20
+ def users(refresh = false)
21
+ return @users if(!refresh && @users_cached)
22
+ users_site = File.join(self.class.site.to_s, "projects/#{self.identifier()}")
23
+ Mingle4r::User.site = users_site
24
+ Mingle4r::User.user = self.class.user
25
+ Mingle4r::User.password = self.class.password
26
+ Mingle4r::User.element_name = nil # reset
27
+ user_class = Mingle4r::User.send(:create_resource_class)
28
+ @users = user_class.find(:all)
29
+ @users_cached = true
30
+ @users
31
+ end
32
+
33
+ # returns the wikis for the project. To hit the resource server without returning
34
+ # cached results pass true as an argument.
35
+ def wikis(refresh = false)
36
+ return @wikis if(!refresh && @wikis_cached)
37
+ wiki_site = File.join(self.class.site.to_s, "projects/#{self.identifier()}")
38
+ Mingle4r::Wiki.site = wiki_site
39
+ Mingle4r::Wiki.user = self.class.user
40
+ Mingle4r::Wiki.password = self.class.password
41
+ wiki_class = Mingle4r::Wiki.send(:create_resource_class)
42
+ @wikis = wiki_class.find(:all)
43
+ @wikis_cached = true
44
+ @wikis
45
+ end
46
+
47
+ # returns the property definitions for the project. To hit the resource server
48
+ # pass true as an argument
49
+ def property_definitions(refresh = false)
50
+ return @prop_definitions if(!refresh && @prop_definitions_cached)
51
+ properties_site = File.join(@site.to_s, "projects/#{self.identifier}")
52
+ PropertyDefinition.site = properties_site
53
+ PropertyDefinition.user = @user
54
+ PropertyDefinition.password = @password
55
+ prop_defn_class = Mingle4r::PropertyDefinition.send(:create_resource_class)
56
+ @prop_definitions = prop_defn_class.find(:all)
57
+ @prop_definitions_cached = true
58
+ @prop_definitions
59
+ end
60
+ end # module InstanceMethods
61
+
62
+ extend Mingle4r::CommonClassMethods
63
+ end # class Project
64
+ end
@@ -0,0 +1,18 @@
1
+ module Mingle4r
2
+ class PropertyDefinition
3
+ # module InstanceMethods
4
+ # end
5
+ #
6
+ # module ClassMethods
7
+ # end
8
+ #
9
+ extend Mingle4r::CommonClassMethods
10
+
11
+ self.element_name = 'record'
12
+
13
+ def self.column_name_for(prop_name)
14
+ property_def = @resource_class.find(:all).detect { |prop| prop.name == prop_name }
15
+ property_def ? property_def.column_name : nil
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module Mingle4r
2
+ class User
3
+ extend Mingle4r::CommonClassMethods
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Mingle4r
2
+ module Version
3
+ Major = '0'
4
+ Minor = '2'
5
+ Tiny = '7'
6
+
7
+ def self.to_s
8
+ Major + '.' + Minor + '.' + Tiny
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Mingle4r
2
+ class Wiki
3
+ extend Mingle4r::CommonClassMethods
4
+ self.collection_name = 'wiki'
5
+ self.element_name = 'page'
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ # to avoid writing ActiveResource::Base
2
+ MingleResource = ActiveResource::Base
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Mingle4r::Card::Attachment do
4
+ it "should be able to create a class inherited from ActiveResource::Base" do
5
+ attachment_class = mock(Object)
6
+
7
+ attachment_class.should_receive(:site=).any_number_of_times
8
+ attachment_class.should_receive(:user=).any_number_of_times
9
+ attachment_class.should_receive(:password=).any_number_of_times
10
+ attachment_class.should_receive(:collection_name=).any_number_of_times
11
+ attachment_class.should_receive(:element_name=).any_number_of_times
12
+ attachment_class.should_receive(:include).any_number_of_times
13
+ Class.should_receive(:new).with(MingleResource).any_number_of_times.and_return(attachment_class)
14
+
15
+ Mingle4r::Card::Attachment.site = ''
16
+ Mingle4r::Card::Attachment.user = ''
17
+ Mingle4r::Card::Attachment.password = ''
18
+
19
+ Mingle4r::Card::Attachment.send(:create_resource_class).should == attachment_class
20
+ end
21
+
22
+ it "should be able to download file to a location" do
23
+ Mingle4r::Card::Attachment.site = 'http://localhost/test'
24
+ Mingle4r::Card::Attachment.user = 'test'
25
+ Mingle4r::Card::Attachment.password = 'password'
26
+
27
+ attachment_obj = Mingle4r::Card::Attachment.new
28
+ attachment_obj.should_receive(:url).and_return('/attachment')
29
+ attachment_obj.should_receive(:file_name).and_return('attach')
30
+
31
+ request_obj = mock(Object)
32
+ request_obj.should_receive(:basic_auth)
33
+
34
+ response_obj = mock(Object)
35
+ response_obj.should_receive(:body).and_return('test text')
36
+
37
+ Net::HTTP::Get.should_receive(:new).and_return(request_obj)
38
+ Net::HTTP.should_receive(:start).and_return(response_obj)
39
+
40
+
41
+ attachment_obj.download()
42
+
43
+ File.exist?('attach').should == true
44
+ file_contents = IO.read('attach')
45
+ file_contents.should == 'test text'
46
+ File.delete('attach')
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Mingle4r::Card do
4
+ it "should be able to call active resource find method with proper params" do
5
+ Mingle4r::Card.site = 'http://localhost/projects/test'
6
+ Mingle4r::Card.user = 'user'
7
+ Mingle4r::Card.password = 'password'
8
+
9
+ card_class = Mingle4r::Card.send(:create_resource_class)
10
+ card_class.should_receive(:find).with(:all, :params => {:page => 'all'})
11
+ card_class.find_without_pagination(:all)
12
+ end
13
+
14
+ it "should set attributes in attachment class properly" do
15
+ Mingle4r::Card.site = 'http://localhost/projects/test'
16
+ Mingle4r::Card.user = 'user'
17
+ Mingle4r::Card.password = 'password'
18
+
19
+ card_obj = Mingle4r::Card.new
20
+ card_obj.should_receive(:number).and_return(10)
21
+
22
+ attachment_class_mock = mock(Class)
23
+ Mingle4r::Card::Attachment.should_receive(:create_resource_class).any_number_of_times.and_return(attachment_class_mock)
24
+
25
+ attachment_class_mock.should_receive(:find)
26
+ card_obj.attachments
27
+
28
+ Mingle4r::Card::Attachment.site.should == 'http://localhost/projects/test/cards/10'
29
+ Mingle4r::Card::Attachment.user.should == 'user'
30
+ Mingle4r::Card::Attachment.password.should == 'password'
31
+ end
32
+
33
+ it "should return the http body boundary" do
34
+ Mingle4r::Card.site = 'http://localhost/projects/test'
35
+ Mingle4r::Card.user = 'user'
36
+ Mingle4r::Card.password = 'password'
37
+
38
+ Mingle4r::Card.new.send(:boundary).should == '----------XnJLe9ZIbbGUYtzPQJ16u1'
39
+ end
40
+ end
@@ -0,0 +1,104 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ class DummyClass
4
+ module InstanceMethods
5
+ end
6
+
7
+ # module ClassMethods
8
+ # end
9
+ extend Mingle4r::CommonClassMethods
10
+ end
11
+
12
+ describe DummyClass do
13
+ it "should be able to set the site properly" do
14
+ url = 'http://localhost/test'
15
+ DummyClass.site = url
16
+ DummyClass.site.should == url
17
+ end
18
+
19
+ it "should be able to set the user properly" do
20
+ user = 'testuser'
21
+ DummyClass.user = user
22
+ DummyClass.user.should == user
23
+ end
24
+
25
+ it "should be able to set the password properly" do
26
+ password = 'password'
27
+ DummyClass.password = password
28
+ DummyClass.password.should == password
29
+ end
30
+
31
+ xit "should understand that the attributes have been set" do
32
+ DummyClass.site = 'http://localhost:8080'
33
+ DummyClass.user = 'test'
34
+ DummyClass.password = 'password'
35
+ assert DummyClass.all_attributes_set?
36
+ end
37
+
38
+ it "should be able to create an instance" do
39
+ dummy_obj = DummyClass.new
40
+ dummy_obj.should_not == nil
41
+ end
42
+
43
+ it "should be able to create a class inherited from ActiveResource::Base" do
44
+ dummy_obj = DummyClass.new
45
+ dummy_obj.class.superclass.should == ActiveResource::Base
46
+ end
47
+
48
+ it "should create same class when site is not changed" do
49
+ class DummyClass
50
+ class << self
51
+ attr_reader :resource_class
52
+ end
53
+ end
54
+ site = 'http://localhost/foo'
55
+ DummyClass.site = site
56
+ DummyClass.user = 'test'
57
+ DummyClass.password = 'password'
58
+ class1 = DummyClass.resource_class
59
+ DummyClass.site = site
60
+ class2 = DummyClass.resource_class
61
+ class1.should == class2
62
+ end
63
+
64
+ it "should create different classes when site is changed" do
65
+ class DummyClass
66
+ class << self
67
+ attr_reader :resource_class
68
+ end
69
+ end
70
+ site1 = 'http://localhost/foo'
71
+ site2 = 'http://localhost/bar'
72
+ DummyClass.site = site1
73
+ DummyClass.user = 'test'
74
+ DummyClass.password = 'password'
75
+ class1 = DummyClass.resource_class
76
+ DummyClass.site = site2
77
+ class2 = DummyClass.resource_class
78
+ class1.should_not == class2
79
+ end
80
+
81
+ it "should be able to find the instance methods module in the class" do
82
+ DummyClass.send(:instance_methods_module_name).should == 'InstanceMethods'
83
+ end
84
+
85
+ it "should not be able to find class methods module in the class" do
86
+ DummyClass.send(:class_methods_module_name).should be_nil
87
+ end
88
+
89
+ it "should return the class name only without nesting of namespaces" do
90
+ DummyClass.send(:class_name).should == 'DummyClass'
91
+ end
92
+
93
+ it "should call find with proper params" do
94
+ # open access to @resource var
95
+ class << DummyClass
96
+ attr_writer :resource_class
97
+ end
98
+
99
+ mock_class = mock(Class)
100
+ DummyClass.resource_class = mock_class
101
+ mock_class.should_receive(:find).with(:all, :from => 'http://localhost/test', :params => {:foo => 'bar'})
102
+ DummyClass.find(:all, :from => 'http://localhost/test', :params => {:foo => 'bar'})
103
+ end
104
+ end
@@ -0,0 +1,20 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Mingle4r::Helpers do
4
+ it "should return a token" do
5
+ assert Mingle4r::Helpers::fast_token
6
+ end
7
+
8
+ it "should not return the same token" do
9
+ token1 = Mingle4r::Helpers::fast_token
10
+ token2 = Mingle4r::Helpers::fast_token
11
+ assert_not_equal(token1, token2)
12
+ end
13
+
14
+ it "should return a string in proper html format" do
15
+ string = 'Hello(whosoever) world []'
16
+ html_result = Mingle4r::Helpers::encode2html(string)
17
+ html_expected = 'Hello%28whosoever%29%20world%20%5B%5D'
18
+ html_result.should == html_expected
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Mingle4r::Project do
4
+ it "should return the set the Card class attributes properly" do
5
+ Mingle4r::Project.site = 'http://localhost'
6
+ Mingle4r::Project.user = 'test'
7
+ Mingle4r::Project.password = 'password'
8
+
9
+ proj = Mingle4r::Project.new(:identifier => 'test')
10
+ card_class_mock = mock(Class)
11
+ Mingle4r::Card.should_receive(:create_resource_class).any_number_of_times.and_return(card_class_mock)
12
+ card_class_mock.should_receive(:find_without_pagination).with(:all)
13
+
14
+ proj.cards
15
+
16
+ Mingle4r::Card.site.should == 'http://localhost/projects/test'
17
+ Mingle4r::Card.user.should == 'test'
18
+ Mingle4r::Card.password.should == 'password'
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe Mingle4r::PropertyDefinition do
5
+ before(:all) do
6
+ Mingle4r::PropertyDefinition.site = 'http://localhost:8080/projects/test'
7
+ Mingle4r::PropertyDefinition.user = 'test'
8
+ Mingle4r::PropertyDefinition.password = 'secret'
9
+ end
10
+
11
+ it "should return the element name properly" do
12
+ Mingle4r::PropertyDefinition::element_name.should == 'record'
13
+ end
14
+
15
+ it "should return the property value" do
16
+ class Mingle4r::PropertyDefinition
17
+ class << self
18
+ attr_accessor :resource_class
19
+ end
20
+ end
21
+ resource_class = mock(Class)
22
+ property_1 = { :name => 'Test Property', :column_name => 'test_property_name' }
23
+ property_2 = { :name => 'Added in Iteration number', :column_name => 'added_in_iteration_number'}
24
+ find_results = [OpenStruct.new(property_1), OpenStruct.new(property_2)]
25
+ Mingle4r::PropertyDefinition.resource_class = resource_class
26
+ resource_class.should_receive(:find).with(:all).any_number_of_times.and_return(find_results)
27
+ Mingle4r::PropertyDefinition.column_name_for('Test Property').should == 'test_property_name'
28
+ Mingle4r::PropertyDefinition.column_name_for('Added in Iteration number').should == 'added_in_iteration_number'
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Mingle4r::Version do
4
+ it "should return proper version" do
5
+ Mingle4r::Version::to_s.should == '0.2.7'
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Mingle4r::Wiki do
4
+ it "should return the collection name properly" do
5
+ Mingle4r::Wiki::collection_name.should == 'wiki'
6
+ end
7
+
8
+ it "should return the element name properly" do
9
+ Mingle4r::Wiki::element_name.should == 'page'
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Mingle4r do
4
+ it "should return my name" do
5
+ Mingle4r::AUTHOR.should == 'Asur'
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/../lib/mingle4r'
2
+ require 'test/unit'
3
+ require 'spec/test/unit'
@@ -0,0 +1,39 @@
1
+ require '../../lib/mingle4r'
2
+
3
+
4
+ class Mingle4r::Project
5
+ class << self
6
+ attr_reader :resource_class
7
+ end
8
+ end
9
+
10
+ m_c = Mingle4r::MingleClient.new('http://localhost:9090', 'testuser', 'testuser', 'api_test')
11
+
12
+
13
+ proj = m_c.project
14
+ puts proj.users.size
15
+ cards = m_c.cards
16
+ card = cards[2]
17
+ puts '------------------------------'
18
+ puts Mingle4r::PropertyDefinition.site
19
+ puts Mingle4r::PropertyDefinition.user
20
+ puts Mingle4r::PropertyDefinition.password
21
+ puts Mingle4r::PropertyDefinition.new.class.collection_path
22
+
23
+ puts card.property_value('Name')
24
+ puts '||||||||||||||||||||||||||||||'
25
+ puts card.custom_properties
26
+ puts card.number
27
+ puts card.description
28
+ # card.description = 'test'
29
+ # card.save
30
+ attachment = card.attachments[0]
31
+ # puts attachment.url
32
+
33
+ # delete attachments
34
+ Mingle4r::Card::Attachment.site = 'http://localhost:9090/projects/api_test/cards/18'
35
+ Mingle4r::Card::Attachment.user = 'testuser'
36
+ Mingle4r::Card::Attachment.password = 'testuser'
37
+
38
+ attachment = Mingle4r::Card::Attachment.find(:all)[0]
39
+ # attachment.delete
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../../lib/mingle4r'
3
+
4
+ Mingle4r::Card.site = 'http://localhost:9090/projects/api_test'
5
+ Mingle4r::Card.user = 'testuser'
6
+ Mingle4r::Card.password = 'testuser'
7
+ card = Mingle4r::Card.find(18)
8
+
9
+ puts card.add_comment('testingcomment')
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../../lib/mingle4r'
3
+
4
+ Mingle4r::Card.site = 'http://localhost:9090/projects/api_test'
5
+ Mingle4r::Card.user = 'testuser'
6
+ Mingle4r::Card.password = 'testuser'
7
+ puts (Mingle4r::Card.apply_filter('Type IS Defect')).size
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../../lib/mingle4r'
3
+
4
+ Mingle4r::Card.site = 'http://localhost:9090/projects/api_test'
5
+ Mingle4r::Card.user = 'testuser'
6
+ Mingle4r::Card.password = 'testuser'
7
+ card = Mingle4r::Card.find(21)
8
+
9
+ card.execute_transition(:transition_name => 'Close Defect')
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../../lib/mingle4r'
3
+
4
+ Mingle4r::User.site = 'http://localhost:9090/projects/api_test'
5
+ Mingle4r::User.user = 'testuser'
6
+ Mingle4r::User.password = 'testuser'
7
+
8
+ users = Mingle4r::User.find(:all)
9
+
10
+ puts users[0].user
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../../lib/mingle4r'
3
+
4
+ Mingle4r::Card.site = 'http://localhost:9090/projects/api_test'
5
+ Mingle4r::Card.user = 'testuser'
6
+ Mingle4r::Card.password = 'testuser'
7
+
8
+ card = Mingle4r::Card.find(26)
9
+ puts card.upload_attachment(File.join(File.dirname(__FILE__), '/../../README'))
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../../lib/mingle4r'
3
+
4
+ m_c = Mingle4r::MingleClient.new('http://localhost:9090', 'testuser', 'testuser', 'api_test')
5
+
6
+ proj = m_c.project
7
+ puts proj.wiki.size
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arusarka-mingle4r
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - asur
@@ -31,8 +31,40 @@ extensions: []
31
31
  extra_rdoc_files:
32
32
  - README
33
33
  files:
34
+ - lib/mingle4r/card.rb
35
+ - lib/mingle4r/card/attachment.rb
36
+ - lib/mingle4r/common_class_methods.rb
37
+ - lib/mingle4r/common_dyn_class_instance_methods.rb
38
+ - lib/mingle4r/helpers.rb
39
+ - lib/mingle4r/mingle_client.rb
40
+ - lib/mingle4r/project.rb
41
+ - lib/mingle4r/property_definition.rb
42
+ - lib/mingle4r/user.rb
43
+ - lib/mingle4r/version.rb
44
+ - lib/mingle4r/wiki.rb
45
+ - lib/mingle4r.rb
46
+ - lib/mingle_resource.rb
47
+ - spec/mingle4r/card/attachment_spec.rb
48
+ - spec/mingle4r/card_spec.rb
49
+ - spec/mingle4r/common_class_methods_spec.rb
50
+ - spec/mingle4r/helpers_spec.rb
51
+ - spec/mingle4r/project_spec.rb
52
+ - spec/mingle4r/property_definition_spec.rb
53
+ - spec/mingle4r/version_spec.rb
54
+ - spec/mingle4r/wiki_spec.rb
55
+ - spec/mingle4r_spec.rb
56
+ - spec/spec_helper.rb
57
+ - test/integration/test.rb
58
+ - test/integration/test_adding_comment.rb
59
+ - test/integration/test_card_filter.rb
60
+ - test/integration/test_card_transition.rb
61
+ - test/integration/test_getting_users.rb
62
+ - test/integration/test_uploading_attachment.rb
63
+ - test/integration/test_wiki.rb
64
+ - MIT-LICENSE
65
+ - Rakefile
34
66
  - README
35
- - History.txt
67
+ - TODO.txt
36
68
  has_rdoc: false
37
69
  homepage: http://github.com/arusarka/mingle4r/
38
70
  post_install_message:
@@ -1,33 +0,0 @@
1
- 0.2.2
2
- -----
3
-
4
- * added add_comment to card class. allows adding comment to a card
5
-
6
- 0.2.1
7
- -----
8
-
9
- * added option to relationship methods(e.g. - cards, users, attachments) to return cached or
10
- updated results
11
- * property_definitions is a relationship method of project instead of mingle client.
12
-
13
- 0.2.0
14
- -----
15
-
16
- * added class method apply_filter to Card class. allows an MQL filter
17
- * added instance method execute_transition to Card class. allows to execute a transition on a card.
18
-
19
- 0.1.6
20
- -----
21
-
22
- * added instance method at_version to Card class. gets the particular version for the card
23
-
24
- 0.1.5
25
- -----
26
-
27
- * Rest Api's added - wiki
28
-
29
- 0.0.1
30
- -----
31
-
32
- * Rest Api's - Card, Project, User, Attachment, Property Definition
33
- * Mingle Client - supports getting cards, project etc.