donedone 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 85ea18b103a503e2eda28b1f26ef959019a20442
4
+ data.tar.gz: 4f1593a019af558f1f167d21691d6d01b367a106
5
+ SHA512:
6
+ metadata.gz: c458cef331617286b28a74dab64132178ea91e05ed25aa930787afd6c3da7d544ed2f82e49e5779a2d83220580f3a4f985a580742978aafeb5f30444dff5ec55
7
+ data.tar.gz: c0ae992671cecbf2750f4dd71c9e05d9bf928752ea26448f749f93d2bcbe3797cd7545822d11dba20d56eed0d016bbf46e62cd0f918055c53415901fd0d87441
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rvmrc
6
+ .ignore
7
+ .yardoc
8
+ Gemfile.lock
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in donedone.gemspec
4
+ gemspec
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2013 Inquisitive Minds, Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,73 @@
1
+ # DoneDone API Ruby Client Library (GEM)
2
+
3
+ ## REQUIREMENT
4
+ Ruby
5
+
6
+ ## USAGE
7
+ To use the Ruby library with a DoneDone project, you will need to enable the API option under the Project Settings page.
8
+
9
+ Please see http://www.getdonedone.com/api for more detailed documentation.
10
+
11
+ The examples below work for projects with the API enabled.
12
+
13
+
14
+ ## EXAMPLES
15
+ ```
16
+
17
+ # use it in your own code:
18
+ cmd-prompt> gem install 'donedone'
19
+ require 'donedone'
20
+
21
+ # or interact via donedone ruby-cmd
22
+ cmd-prompt> donedone -h
23
+
24
+ # import your LightHouse CSV:
25
+ ./examples/lighthouse_csv_importer.rb <domain> <usr> <pwd> <project_id> /path/to/your/light_house.csv
26
+
27
+ # or via irb
28
+ cmd-prompt> irb
29
+ require 'donedone'
30
+
31
+ domain = "YOUR_COMPANY_DOMAIN" #e.g. wearemammoth
32
+ username = "YOUR_USERNAME"
33
+ password = "YOUR_PASSWORD"
34
+
35
+ issue_tracker = DoneDone::IssueTracker.new(domain, username, password)
36
+
37
+ # list all the api calls (plus the 'result' method):
38
+ issue_tracker.public_methods(false)
39
+
40
+ issue_tracker.projects
41
+ project_id = issue_tracker.result.first["ID"]
42
+
43
+ issue_tracker.priority_levels
44
+
45
+ issue_tracker.people_in_project(project_id)
46
+ tester_id = issue_tracker.result.first["ID"]
47
+ resolver_id = issue_tracker.result.last["ID"]
48
+
49
+ issue_tracker.issues_in_project(project_id)
50
+ priority_level_id = issue_tracker.result.first["PriorityLevelID"]
51
+ issue_id = issue_tracker.result.first["OrderNumber"]
52
+
53
+ issue_tracker.issue_exist?(project_id, issue_id)
54
+ issue_tracker.potential_statuses_for_issue(project_id, issue_id)
55
+ issue_tracker.issue(project_id, issue_id)
56
+ issue_tracker.people_for_issue_assignment(project_id, issue_id)
57
+
58
+ new_issue_id = issue_tracker.create_issue(project_id, title, priority_id,
59
+ resolver_id, tester_id, {:description => '', :tags=> nil, :watcher_id=>nil, :attachments=>nil})
60
+
61
+ comment = "blah blah"
62
+ file_name = "./file1.txt"
63
+ File.open(file_name, "w") {|f| f.puts "attachment one." }
64
+ comment_url = issue_tracker.create_comment(project_id, new_issue_id, comment, {:people_to_cc_ids=>nil :attachments=>[file_name]})
65
+ puts issue_tracker.result["SuccesfullyAttachedFiles"] ? "attachment uploaded successfully" : "failed to upload attachment"
66
+ File.unlink(file_name)
67
+
68
+ issue_url = issue_tracker.update_issue(project_id, new_issue_id, {:title=>nil, :priority_id=>nil, :resolver_id=>nil, :tester_id=nil, :description=>nil, :tags=>nil, :state_id=>nil, :attachments=>nil})
69
+
70
+ ```
71
+
72
+ ## TODO
73
+ improve design via specs
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ gemspec = eval(File.read("donedone.gemspec"))
7
+
8
+ task :build => "#{gemspec.full_name}.gem"
9
+
10
+ file "#{gemspec.full_name}.gem" => gemspec.files + ["donedone.gemspec"] do
11
+ system "gem build donedone.gemspec"
12
+ system "gem install donedone-#{DoneDone::VERSION}.gem"
13
+ end
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'rubygems'
5
+ gem 'donedone'
6
+ rescue LoadError
7
+ end
8
+
9
+ begin
10
+ require 'donedone'
11
+ rescue LoadError
12
+ require_relative '../lib/donedone'
13
+ end
14
+
15
+ if ([] == ARGV) || ARGV.length < 4 || (ARGV[0].start_with?("-h"))
16
+ warn "USAGE: ${$0} <domain> <usr> <pwd> <api-method> [args...]"
17
+ exit
18
+ end
19
+ init_args = []
20
+ # puts "ARGV start: #{ARGV.inspect}"
21
+ 3.times { init_args << ARGV.shift }
22
+ # puts "ARGV now: #{ARGV.inspect}"
23
+ issue_tracker = DoneDone::IssueTracker.new(*init_args)
24
+ puts issue_tracker.send(ARGV.shift, *ARGV)
25
+ exit
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "donedone/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "donedone"
7
+ s.version = DoneDone::VERSION
8
+ s.authors = ["Jonathan Thomas"]
9
+ s.email = ["buyer+donedone@his-service.net"]
10
+ s.homepage = "https://github.com/zoodles/DoneDone-API-Ruby"
11
+ s.summary = %q{donedone.com api client}
12
+ s.description = %q{Check your existing todo items, add new ones, update old one}
13
+ s.rubyforge_project = "donedone"
14
+ s.license = 'MIT'
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.bindir = 'bin'
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_dependency "mime-types", "~> 2.0"
23
+ s.add_development_dependency "rspec", "~> 2.14.0"
24
+ #s.add_development_dependency "simplecov"
25
+ end
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+ # Import projct issues from LightHouse CSV exports
3
+ #
4
+ # This is a simple example that migrates data from LightHouse to DoneDone using
5
+ # the Ruby client. It makes a couple of assumptions. First, the CSV file must
6
+ # contain columns listed below.
7
+ #
8
+ # number | state | title | milestone | assigned | created | updated | project | tags
9
+ #
10
+ # Second, it also assumes the accounts have already been created for the
11
+ # project, and throws an execption if not. Because LightHouse handles things a
12
+ # bit different from DoneDone, this script will also create the project with assignee
13
+ # as both resolver and tester.
14
+
15
+ begin
16
+ require 'rubygems'
17
+ gem 'donedone'
18
+ rescue LoadError
19
+ end
20
+
21
+ begin
22
+ require 'donedone'
23
+ rescue LoadError
24
+ require_relative '../lib/donedone'
25
+ end
26
+
27
+ require 'csv'
28
+
29
+ DEFAULT_DOMAIN = "Your company domain"
30
+ domain = ARGV.shift || DEFAULT_DOMAIN
31
+ username = ARGV.shift || "Your donedone username"
32
+ password = ARGV.shift || "Your donedone password"
33
+ project_id = ARGV.shift.to_i || "Your donedone project ID"
34
+
35
+ CSVFilePath = ARGV.shift || "Path to the CSV file exported from lighthouseapp.com"
36
+ priority_id = ARGV.shift || 2 #Assuming medium priority
37
+
38
+ # puts "domain: #{domain.inspect}, username: #{username.inspect}, password: #{password.inspect}, project_id: #{project_id.inspect}, csv: #{CSVFilePath.inspect}"
39
+ fail( "unknown file: #{CSVFilePath.inspect}") unless File.exists?(CSVFilePath)
40
+ fail( "You must edit pass-in variables or edit the default values in #{$0}" ) if DEFAULT_DOMAIN == domain
41
+
42
+ issue_tracker = DoneDone::IssueTracker.new(domain, username, password)
43
+ project_peoples = issue_tracker.people_in_project(project_id)
44
+
45
+
46
+ # read file
47
+ csv_text = File.binread(CSVFilePath)
48
+ # slurp
49
+ csv = CSV.parse(csv_text, { :headers => true, :return_headers => false, :col_sep => ',', :quote_char => '"'})
50
+
51
+ # loop over issues
52
+ csv.each do |issue|
53
+ # puts "i: #{issue.inspect}"
54
+ person_id = nil
55
+ name = issue['assigned']
56
+ next if "" == project_peoples
57
+ # puts "pp: #{project_peoples.inspect}"
58
+ if person = project_peoples.detect{|people| people["Value"] == name}
59
+ person_id = person["ID"]
60
+ else
61
+ fail "Fail to find DoneDone account for #{name}"
62
+ end
63
+
64
+ # Create issue with medium priority
65
+ print issue_tracker.create_issue(
66
+ project_id, issue['title'], priority_id,
67
+ person_id, person_id,
68
+ {:description => "Created by DoneDone API Ruby client.", :tags => issue['tags']})
69
+
70
+ # Pause execution for API request wait time.
71
+ sleep(5)
72
+ end
@@ -0,0 +1,5 @@
1
+ require_relative './donedone/version'
2
+ require_relative './donedone/multipart'
3
+ require_relative './donedone/issue_tracker'
4
+ module DoneDone
5
+ end
@@ -0,0 +1,33 @@
1
+ module DoneDone
2
+ class Constant
3
+ PROJECTS_WITH_ISSUES = 'Projects/true' unless const_defined?(:PROJECTS_WITH_ISSUES)
4
+ PROJECTS = 'Projects' unless const_defined?(:PROJECTS)
5
+ PRIORITY_LEVELS = 'PriorityLevels' unless const_defined?(:PRIORITY_LEVELS)
6
+ PEOPLE_IN_PROJECT = "PeopleInProject/%s" unless const_defined?(:PEOPLE_IN_PROJECT)
7
+ ISSUES_IN_PROJECT = "IssuesInProject/%s" unless const_defined?(:ISSUES_IN_PROJECT)
8
+ DOES_ISSUE_EXIST = "DoesIssueExist/%s/%s" unless const_defined?(:DOES_ISSUE_EXIST)
9
+ POTENTIAL_STATUSES_FOR_ISSUE = "PotentialStatusesForIssue/%s/%s" unless const_defined?(:POTENTIAL_STATUSES_FOR_ISSUE)
10
+ CREATE_ISSUE = "Issue/%s" unless const_defined?(:CREATE_ISSUE)
11
+ ISSUE = "#{CREATE_ISSUE}/%s" unless const_defined?(:ISSUE)
12
+ PEOPLE_FOR_ISSUE_ASSIGNMENT = "PeopleForIssueAssignment/%s/%s" unless const_defined?(:PEOPLE_FOR_ISSUE_ASSIGNMENT)
13
+ COMMENT = "Comment/%s/%s" unless const_defined?(:COMMENT)
14
+
15
+ HOST = "%s.mydonedone.com" unless const_defined?(:HOST)
16
+ PROTOCOL = "https" unless const_defined?(:PROTOCOL)
17
+ BASE_URL_PATH = "IssueTracker/API" unless const_defined?(:BASE_URL_PATH)
18
+ BASE_URL = "#{PROTOCOL}://%s/#{BASE_URL_PATH}/" unless const_defined?(:BASE_URL)
19
+
20
+ SSL_VERIFY_MODE = OpenSSL::SSL::VERIFY_NONE unless const_defined?(:SSL_VERIFY_MODE)
21
+ SSL_VERSION = :SSLv3 unless const_defined?(:SSL_VERSION)
22
+
23
+ def self.url_for(name, *args)
24
+ format_str = const_get(name)
25
+ if args.empty?
26
+ format_str
27
+ else
28
+ format_str % args
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,131 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+ require_relative 'multipart'
4
+
5
+ module DoneDone
6
+ class Http
7
+ attr_reader :domain, :data, :files, :method_url
8
+ attr_reader :_request, :_debug
9
+ private :_request
10
+ private :_debug
11
+
12
+ attr_reader :put_class, :get_class, :post_class, :multipart_post_class
13
+ attr_reader :http_class, :ssl_verify_mode, :ssl_version
14
+ def initialize(domain, username, password, options={})
15
+ @domain = domain
16
+ @_username = username
17
+ @_password = password
18
+
19
+ @put_class = options[:put_class] || Net::HTTP::Put
20
+ @get_class = options[:get_class] || Net::HTTP::Get
21
+ @post_class = options[:post_class] || Net::HTTP::Post
22
+ @multipart_post_class = options[:multipart_post_class] || Multipart::Post
23
+ @http_class = options[:http_class] || Net::HTTP
24
+ @ssl_verify_mode = options[:ssl_verify_mode] || Constant::SSL_VERIFY_MODE
25
+ @ssl_version = options[:ssl_version] || Constant::SSL_VERSION
26
+ end
27
+
28
+ def reset(options = {})
29
+ @data = options[:data]
30
+ @files = options[:files]
31
+ @_request = nil
32
+ @uri = nil
33
+ @base_url = nil
34
+ @host = nil
35
+ @http = nil
36
+ @_debug = options.has_key?(:debug) ? options[:debug] : false
37
+ end
38
+
39
+ def host
40
+ unless @host
41
+ @host = Constant.url_for('HOST', domain)
42
+ end
43
+ @host
44
+ end
45
+
46
+ def get(method_url, options={})
47
+ @method_url = method_url
48
+ reset(options)
49
+ request(get_class)
50
+ end
51
+
52
+ def put(method_url, options={})
53
+ @method_url = method_url
54
+ reset(options)
55
+ request(put_class) do
56
+ append_any_form_data
57
+ end
58
+ end
59
+
60
+ def post(method_url, options={})
61
+ @method_url = method_url
62
+ reset(options)
63
+ request(post_class) do
64
+ files ? append_multipart_data : append_any_form_data
65
+ end
66
+ end
67
+
68
+
69
+ private
70
+
71
+ def http
72
+ unless @http
73
+ @http = http_class.new(uri.host, uri.port)
74
+ @http.use_ssl = true
75
+ @http.verify_mode = ssl_verify_mode
76
+ @http.ssl_version = ssl_version
77
+ end
78
+ @http
79
+ end
80
+
81
+ def base_url
82
+ unless @base_url
83
+ @base_url = Constant.url_for('BASE_URL', host)
84
+ end
85
+ @base_url
86
+ end
87
+
88
+ def uri
89
+ unless @uri
90
+ @uri = URI.parse("#{base_url}#{method_url}")
91
+ end
92
+ @uri
93
+ end
94
+
95
+ def request(method_class)
96
+ debug { "#{method_class.name}, #{uri.request_uri.inspect} - host: #{uri.host}, - port: #{uri.port}" }
97
+ @_request = method_class.new(uri.request_uri)
98
+
99
+ yield if block_given?
100
+
101
+ debug { "uri: #{uri.inspect}, files: #{files.inspect}" }
102
+ _request.basic_auth(@_username, @_password)
103
+
104
+ debug { "request: #{_request.to_hash.inspect}" }
105
+ http.request(_request)
106
+ end
107
+
108
+ def append_any_form_data #(data=data)
109
+ if data
110
+ debug { "form_data: #{data.inspect}" }
111
+ _request.set_form_data(data)
112
+ end
113
+ end
114
+
115
+ def append_multipart_data #(files=files, data=data)
116
+ body, content_type, useragent =
117
+ multipart_post_class.prepare_query( files.merge(data) )
118
+
119
+ debug { "params: #{params.inspect}; body: #{body.inspect}, content_type: #{content_type.inspect}, useragent: #{useragent.inspect}" }
120
+ @_request.content_type = content_type
121
+ @_request.content_length = body.size
122
+ @_request["User-Agent"] = useragent
123
+
124
+ @_request.body = body
125
+ end
126
+
127
+ def debug
128
+ puts(yield) if _debug && block_given?
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,248 @@
1
+ require_relative "http"
2
+ require_relative "constant"
3
+ require "json"
4
+
5
+ module DoneDone
6
+ class IssueTracker
7
+ HELPER_METHODS = [:response, :result] unless const_defined?(:HELPER_METHODS)
8
+ def self.api_methods
9
+ instance_methods(false) - HELPER_METHODS
10
+ end
11
+
12
+ #Provide access to the DoneDone IssueTracker API.
13
+ #See http://www.getdonedone.com/api for complete documentation for the
14
+ #API.
15
+
16
+ attr_reader :response
17
+ attr_reader :_debug
18
+ private :_debug
19
+ attr_reader :_http_helper
20
+ private :_http_helper
21
+
22
+ #_debug - print debug messages
23
+ #domain - company's DoneDone domain
24
+ #username - DoneDone username
25
+ #password - DoneDone password
26
+
27
+ def initialize(domain, username, password=nil, options = {})
28
+ @_debug = options.has_key?(:debug) ? options[:debug] : false
29
+ @_http_helper = options[:http_helper] || DoneDone::Http.new(domain, username, password)
30
+ @response = nil
31
+ end
32
+
33
+ def result
34
+ response ? JSON.parse( response.body ) : ""
35
+ end
36
+
37
+
38
+ # Get all Projects with the API enabled
39
+ # load_with_issues - Passing true will deep load all of the projects as
40
+ # well as all of their active issues.
41
+ def projects(load_with_issues=false)
42
+ url = load_with_issues ? Constant.url_for('PROJECTS_WITH_ISSUES') : Constant.url_for('PROJECTS')
43
+ api url
44
+ end
45
+
46
+ # Get priority levels
47
+ def priority_levels
48
+ api Constant.url_for('PRIORITY_LEVELS')
49
+ end
50
+
51
+ # Get all people in a project
52
+ # project_id - project id
53
+ def people_in_project project_id
54
+ api Constant.url_for('PEOPLE_IN_PROJECT', project_id)
55
+ end
56
+
57
+ # Get all issues in a project
58
+ # project_id - project id
59
+ def issues_in_project project_id
60
+ api Constant.url_for('ISSUES_IN_PROJECT', project_id)
61
+ end
62
+
63
+ # Check if an issue exists
64
+ # project_id - project id
65
+ # issue_id - issue id
66
+ def issue_exist?(project_id, issue_id)
67
+ api Constant.url_for('DOES_ISSUE_EXIST', project_id, issue_id)
68
+ !result.empty? ? result["IssueExists"] : false
69
+ end
70
+
71
+ # Get potential statuses for issue
72
+ # Note: If you are an admin, you'll get both all allowed statuses
73
+ # as well as ALL statuses back from the server
74
+ # project_id - project id
75
+ # issue_id - issue id
76
+ def potential_statuses_for_issue( project_id, issue_id)
77
+ api Constant.url_for('POTENTIAL_STATUSES_FOR_ISSUE', project_id, issue_id)
78
+ end
79
+
80
+ # Note: You can use this to check if an issue exists as well,
81
+ # since it will return a 404 if the issue does not exist.
82
+ def issue(project_id, issue_id)
83
+ api Constant.url_for('ISSUE', project_id, issue_id)
84
+ end
85
+
86
+ # Get a list of people that cane be assigend to an issue
87
+ def people_for_issue_assignment(project_id, issue_id)
88
+ api Constant.url_for('PEOPLE_FOR_ISSUE_ASSIGNMENT', project_id, issue_id)
89
+ end
90
+
91
+ #Create Issue
92
+ # project_id - project id
93
+ # title - required title.
94
+ # priority_id - priority levels
95
+ # resolver_id - person assigned to solve this issue
96
+ # tester_id - person assigned to test and verify if a issue is
97
+ # resolved
98
+ # description - optional description of the issue
99
+ # tags - a string of tags delimited by comma
100
+ # watcher_id - a string of people's id delimited by comma
101
+ # attachments - list of file paths
102
+ def create_issue( project_id, title, priority_id, resolver_id, tester_id, options={})
103
+ description=options[:description]
104
+ tags=options[:tags]
105
+ watcher_id=options[:watcher_id]
106
+ attachments=options[:attachments]
107
+
108
+ data = {
109
+ 'title' => title,
110
+ 'priority_level_id' => priority_id,
111
+ 'resolver_id' => resolver_id,
112
+ 'tester_id' => tester_id,
113
+ }
114
+
115
+ data['description'] = description if description
116
+ data['tags'] = tags if tags
117
+ data['watcher_ids'] = watcher_id if watcher_id
118
+
119
+ params = {:data => data, :update => false, :post => true}
120
+ params[:attachments] = attachments if attachments
121
+ api Constant.url_for('CREATE_ISSUE', project_id), params
122
+ !result.empty? ? result["IssueID"] : nil
123
+ end
124
+
125
+ #Create Comment on issue
126
+ #project_id - project id
127
+ #issue_id - issue id
128
+ #comment - comment string
129
+ #people_to_cc_ids - a string of people to be CCed on this comment,
130
+ #delimited by comma
131
+ #attachments - list of absolute file path.
132
+ def create_comment(project_id, order_number, comment, options={})
133
+ people_to_cc_ids=options[:people_to_cc_ids]
134
+ attachments=options[:attachments]
135
+
136
+ data = {'comment' => comment}
137
+ data['people_to_cc_ids']= people_to_cc_ids if people_to_cc_ids
138
+
139
+ params = {:data => data, :update => false, :post => true}
140
+ params[:attachments] = attachments if attachments
141
+ api Constant.url_for('COMMENT', project_id, order_number), params
142
+ !result.empty? ? result["CommentURL"] : nil
143
+ end
144
+
145
+ #Update Issue
146
+ # If you provide any parameters then the value you pass will be
147
+ # used to update the issue. If you wish to keep the value that's
148
+ # already on an issue, then do not provide the parameter in your
149
+ # PUT data. Any value you provide, including tags, will overwrite
150
+ # the existing values on the issue. If you wish to retain the
151
+ # tags for an issue and update it by adding one new tag, then
152
+ # you'll have to provide all of the existing tags as well as the
153
+ # new tag in your tags parameter, for example.
154
+
155
+ # project_id - project id
156
+ # order_number - issue id
157
+ # title - required title
158
+ # priority_id - priority levels
159
+ # resolver_id - person assigned to solve this issue
160
+ # tester_id - person assigned to test and verify if a issue is
161
+ # resolved
162
+ # description - optional description of the issue
163
+ # tags - a string of tags delimited by comma
164
+ # state_id - a valid state that this issue can transition to
165
+ # attachments - list of file paths
166
+ def update_issue(project_id, order_number, options={})
167
+ title=options[:title]
168
+ priority_id=options[:priority_id]
169
+ resolver_id=options[:resolver_id]
170
+ tester_id=options[:tester_id]
171
+ description=options[:description]
172
+ tags=options[:tags]
173
+ state_id=options[:state_id]
174
+ attachments=options[:attachments]
175
+
176
+ data = {}
177
+ data['title'] = title if title
178
+ data['priority_level_id'] = priority_id if priority_id
179
+ data['resolver_id'] = resolver_id if resolver_id
180
+
181
+ data['tester_id'] = tester_id if tester_id
182
+ data['description'] = description if description
183
+ data['tags'] = tags if tags
184
+ data['state_id'] = state_id if state_id
185
+
186
+ params = {:update => true}
187
+ params[:data] = data unless data.empty?
188
+ params[:attachments] = attachments if attachments
189
+ api Constant.url_for('ISSUE', project_id, order_number), params
190
+ !result.empty? ? result["IssueURL"] : nil
191
+ end
192
+
193
+
194
+ private
195
+
196
+ # Perform generic API calling
197
+ #This is the base method for all IssueTracker API calls.
198
+ # method_url - IssueTracker method URL
199
+ # data - optional POST form data
200
+ # attachemnts - optional list of file paths
201
+ # update - flag to indicate if this is a PUT operation
202
+ def api(method_url, options={})
203
+ data = options[:data]
204
+ attachments = options[:attachments]
205
+ update = options.has_key?(:update) ? options[:update] : false
206
+ post = options.has_key?(:post) ? options[:post] : false
207
+
208
+ @response = nil
209
+ files = {}
210
+ request_method = nil
211
+
212
+ if attachments
213
+ debug { "attachments; using post" }
214
+ request_method = :post
215
+ attachments.each_with_index do |attachment, index|
216
+ files["attachment-#{index}"] = attachment
217
+ end
218
+ else
219
+ request_method = :get
220
+ end
221
+
222
+ if update
223
+ debug { "using put" }
224
+ request_method = :put
225
+ end
226
+
227
+ if post
228
+ debug { "using post" }
229
+ request_method = :post
230
+ end
231
+
232
+ params = { :debug => _debug }
233
+ params[:data] = data if data
234
+ params[:files] = files unless files.empty?
235
+
236
+ @response = _http_helper.send(request_method, method_url, params)
237
+ return result
238
+ rescue Exception => e
239
+ warn e.message
240
+ warn e.backtrace.inspect
241
+ return ""
242
+ end
243
+
244
+ def debug
245
+ puts(yield) if _debug && block_given?
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,78 @@
1
+ # Takes a hash of file-key and file-name and returns a string of text
2
+ # formatted to be sent as a multipart form post.
3
+
4
+ require 'mime/types'
5
+ require 'cgi'
6
+
7
+
8
+ module DoneDone
9
+ module Multipart
10
+ VERSION = "1.0.0" unless const_defined?(:VERSION)
11
+
12
+ # Formats a given hash as a multipart form post
13
+ # If determine if the params are Files or Strings and process
14
+ # appropriately
15
+ class Post
16
+ # We have to pretend like we're a web browser...
17
+ USERAGENT = "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6" unless const_defined?(:USERAGENT)
18
+ BOUNDARY = '--JTMultiPart' + rand(1000000).to_s + 'ZZZZZ' unless const_defined?(:BOUNDARY)
19
+ CONTENT_TYPE = "multipart/form-data; boundary=#{ BOUNDARY }" unless const_defined?(:CONTENT_TYPE)
20
+ #HEADER = { "Content-Type" => CONTENT_TYPE, "User-Agent" => USERAGENT } unless const_defined?(:HEADER)
21
+
22
+ def self.prepare_query(params)
23
+ fp = []
24
+
25
+ params.each do |k, v|
26
+ if File.exists?(v) # must be a file
27
+ path = File.path(v)
28
+ content = File.binread(v)
29
+ fp.push(FileParam.new(k, path, content))
30
+ else # must be a string
31
+ fp.push(StringParam.new(k, v))
32
+ end
33
+ end
34
+
35
+ # Assemble the request body using the special multipart format
36
+ query = fp.collect {|p| "--" + BOUNDARY + "\r\n" + p.to_multipart }.join("") + "--" + BOUNDARY + "--"
37
+ return query, CONTENT_TYPE, USERAGENT
38
+ end
39
+ end
40
+
41
+
42
+ private
43
+
44
+ class StringParam
45
+ attr_accessor :k, :v
46
+
47
+ def initialize(k, v)
48
+ @k = k
49
+ @v = v
50
+ end
51
+
52
+ def to_multipart
53
+ return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"\r\n\r\n#{v}\r\n"
54
+ end
55
+ end
56
+
57
+ # Formats the contents of a file for inclusion with a multipart
58
+ # form post
59
+ class FileParam
60
+ attr_accessor :k, :filename, :content
61
+
62
+ def initialize(k, filename, content)
63
+ @k = k
64
+ @filename = filename
65
+ @content = content
66
+ end
67
+
68
+ def to_multipart
69
+ # If we can tell the possible mime-type from the filename, use the
70
+ # first in the list; otherwise, use "application/octet-stream"
71
+ mime_type = MIME::Types.type_for(filename)[0] || MIME::Types["application/octet-stream"][0]
72
+ return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"; filename=\"#{ filename }\"\r\n" +
73
+ "Content-Transfer-Encoding: binary\r\n" +
74
+ "Content-Type: #{ mime_type.simplified }\r\n\r\n#{ content }\r\n"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ module DoneDone
2
+ VERSION = "0.0.4" unless const_defined?(:VERSION)
3
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe DoneDone::Constant do
4
+ describe ".url_for" do
5
+ context 'valid args' do
6
+ it "generates url with format string" do
7
+ expect(DoneDone::Constant.url_for('PEOPLE_IN_PROJECT')).to eq(DoneDone::Constant::PEOPLE_IN_PROJECT)
8
+ expect(DoneDone::Constant.url_for('PEOPLE_IN_PROJECT', '%s')).to eq(DoneDone::Constant::PEOPLE_IN_PROJECT)
9
+ end
10
+
11
+ it "generates url without format string" do
12
+ expect(DoneDone::Constant.url_for('PROJECTS_WITH_ISSUES')).to eq(DoneDone::Constant::PROJECTS_WITH_ISSUES)
13
+ expect(DoneDone::Constant.url_for('PROJECTS_WITH_ISSUES', 'bogus_arg1')).to eq(DoneDone::Constant::PROJECTS_WITH_ISSUES)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe DoneDone::Http do
4
+ class BogusNetHttp
5
+ attr_reader :host, :port, :use_ssl
6
+ attr_accessor :ssl_version, :verify_mode
7
+ def initialize(host, port)
8
+ @host = host
9
+ @port = port
10
+ clear
11
+ end
12
+
13
+ def clear
14
+ @use_ssl = false
15
+ @verify_mode = nil
16
+ @ssl_version = nil
17
+ end
18
+
19
+ def use_ssl=(bool)
20
+ @use_ssl = !!bool
21
+ end
22
+
23
+ def request(request_object)
24
+ @request = request_object
25
+ end
26
+ end
27
+
28
+ let(:net_http_class) { BogusNetHttp }
29
+ let(:domain) { "domain" }
30
+ let(:username) { "username" }
31
+ let(:password) { "password" }
32
+ let(:new_options) { {:http_class => net_http_class} }
33
+ let(:new_args) { [domain, username, password, new_options] }
34
+ let(:donedone_http_object) { DoneDone::Http.new(*new_args) }
35
+ let(:request_methods) { [:get, :put, :post] }
36
+
37
+ describe "init" do
38
+ context 'invalid args' do
39
+ it "raises an Exception for 0-2 args" do
40
+ expect { DoneDone::Http.new }.to raise_error()
41
+ expect { DoneDone::Http.new(domain) }.to raise_error()
42
+ expect { DoneDone::Http.new(domain, username) }.to raise_error()
43
+ end
44
+ it "raises an Exception for >4 args" do
45
+ expect { DoneDone::Http.new(domain, username, password, {}, :extra1) }.to raise_error()
46
+ end
47
+ end
48
+
49
+ context 'valid args' do
50
+ it "requires 3-4 args" do
51
+ expect { DoneDone::Http.new(domain, username, password) }.to_not raise_error()
52
+ expect { DoneDone::Http.new(domain, username, password, {}) }.to_not raise_error()
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "request(s)" do
58
+ let(:method_url) { "method_url" }
59
+
60
+ context 'invalid args' do
61
+ it "will raise an Exception for 0 args" do
62
+ request_methods.each do |method|
63
+ expect { donedone_http_object.send(method) }.to raise_error
64
+ end
65
+ end
66
+ it "will raise an Exception for >2 args" do
67
+ request_methods.each do |method|
68
+ expect { donedone_http_object.send(method, method_url, {}, :extra1) }.to raise_error
69
+ end
70
+ end
71
+ end
72
+ context 'valid args' do
73
+ let(:ssl_port) { 443 }
74
+ it "will not raise an Exception for 1-2 args" do
75
+ request_methods.each do |method|
76
+ expect { donedone_http_object.send(method, method_url) }.to_not raise_error
77
+ expect { donedone_http_object.send(method, method_url, {}) }.to_not raise_error
78
+ end
79
+ end
80
+
81
+ # a bit fragile: specific to Net::HTTP
82
+ it "sets-up a proper http object for :get" do
83
+ expect(donedone_http_object.host).to start_with(domain)
84
+ net_http_obj = net_http_class.new(donedone_http_object.host, ssl_port)
85
+ net_http_class.should_receive(:new).exactly(request_methods.length).times.with(donedone_http_object.host, ssl_port).and_return(net_http_obj)
86
+
87
+ request_methods.each do |method|
88
+ expect(net_http_obj.use_ssl).to eq(false)
89
+ expect(net_http_obj.verify_mode).to eq(nil)
90
+ expect(net_http_obj.ssl_version).to eq(nil)
91
+ donedone_http_object.send(method, method_url)
92
+ expect(net_http_obj.use_ssl).to eq(true)
93
+ expect(net_http_obj.verify_mode).to eq(DoneDone::Constant::SSL_VERIFY_MODE)
94
+ expect(net_http_obj.ssl_version).to eq(DoneDone::Constant::SSL_VERSION)
95
+ donedone_http_object.reset
96
+ net_http_obj.clear
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,164 @@
1
+ require 'spec_helper'
2
+
3
+ describe DoneDone::IssueTracker do
4
+ let(:bogus_http_helper) { Object.new }
5
+ let(:http_helper) { bogus_http_helper }
6
+ let(:domain) { :domain }
7
+ let(:username) { :username }
8
+ let(:password) { :password }
9
+ let(:http_helper_options) { {:debug=>false} }
10
+ let(:issue_tracker) { DoneDone::IssueTracker.new(domain, username, password, :http_helper => http_helper) }
11
+
12
+ describe "init" do
13
+ context 'invalid args' do
14
+ it "raises an Exception for 0-1 args" do
15
+ expect { DoneDone::IssueTracker.new }.to raise_error()
16
+ expect { DoneDone::IssueTracker.new(domain) }.to raise_error()
17
+ end
18
+ it "raises an Exception for >4 args" do
19
+ expect { DoneDone::IssueTracker.new(domain, username, password, {}, :extra1) }.to raise_error()
20
+ end
21
+ end
22
+
23
+ context 'valid args' do
24
+ it "requires 2-4 args" do
25
+ expect { DoneDone::IssueTracker.new(domain, username) }.to_not raise_error()
26
+ expect { DoneDone::IssueTracker.new(domain, username, password) }.to_not raise_error()
27
+ expect { DoneDone::IssueTracker.new(domain, username, password, {}) }.to_not raise_error()
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "methods" do
33
+ let(:expected_api_methods) { [ :projects, :priority_levels, :people_in_project, :issues_in_project, :issue_exist?, :potential_statuses_for_issue, :issue, :people_for_issue_assignment, :create_issue, :create_comment, :update_issue ] }
34
+
35
+ let(:actual_helper_methods) { DoneDone::IssueTracker::HELPER_METHODS }
36
+ let(:actual_api_methods) { DoneDone::IssueTracker.api_methods }
37
+
38
+ it "distinguises its api- and helper-methods" do
39
+ expect(actual_api_methods).to_not include(actual_helper_methods)
40
+ end
41
+
42
+ it "knows its api methods" do
43
+ expect(DoneDone::IssueTracker.api_methods).to eq(expected_api_methods)
44
+ end
45
+
46
+ it "responds to its expected-helper and -api methods" do
47
+ (actual_helper_methods + actual_api_methods).each do |expected_method|
48
+ expect(issue_tracker).to respond_to(expected_method)
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "api-requests" do
54
+ it "requests all projects" do
55
+ http_helper.should_receive(:get).with( DoneDone::Constant::PROJECTS, http_helper_options )
56
+
57
+ issue_tracker.projects
58
+ end
59
+
60
+ it "requests all projects with their issues" do
61
+ http_helper.should_receive(:get).with( DoneDone::Constant::PROJECTS_WITH_ISSUES, http_helper_options)
62
+
63
+ issue_tracker.projects(true)
64
+ end
65
+
66
+ it "requests all priority_levels" do
67
+ http_helper.should_receive(:get).with( DoneDone::Constant::PRIORITY_LEVELS, http_helper_options)
68
+
69
+ issue_tracker.priority_levels
70
+ end
71
+
72
+ it "requests all people_in_project" do
73
+ project_id = 1
74
+ http_helper.should_receive(:get).with( DoneDone::Constant.url_for('PEOPLE_IN_PROJECT', project_id), http_helper_options)
75
+
76
+ issue_tracker.people_in_project(project_id)
77
+ end
78
+
79
+ it "requests all issues_in_project" do
80
+ project_id = 1
81
+ http_helper.should_receive(:get).with( DoneDone::Constant.url_for('ISSUES_IN_PROJECT', project_id), http_helper_options)
82
+
83
+ issue_tracker.issues_in_project(project_id)
84
+ end
85
+
86
+ it "requests if an issue exists for a project" do
87
+ project_id = 1
88
+ issue_order_number = 1
89
+ http_helper.should_receive(:get).with( DoneDone::Constant.url_for('DOES_ISSUE_EXIST', project_id, issue_order_number), http_helper_options)
90
+
91
+ issue_tracker.issue_exist?(project_id, issue_order_number)
92
+ end
93
+
94
+ it "requests status for a project's issue" do
95
+ project_id = 1
96
+ issue_order_number = 1
97
+ http_helper.should_receive(:get).with( DoneDone::Constant.url_for('POTENTIAL_STATUSES_FOR_ISSUE', project_id, issue_order_number), http_helper_options)
98
+
99
+ issue_tracker.potential_statuses_for_issue(project_id, issue_order_number)
100
+ end
101
+
102
+ it "requests a project's issue's details" do
103
+ project_id = 1
104
+ issue_order_number = 1
105
+ http_helper.should_receive(:get).with( DoneDone::Constant.url_for('ISSUE', project_id, issue_order_number), http_helper_options)
106
+
107
+ issue_tracker.issue(project_id, issue_order_number)
108
+ end
109
+
110
+ it "requests people for issue assignment" do
111
+ project_id = 1
112
+ issue_order_number = 1
113
+ http_helper.should_receive(:get).with( DoneDone::Constant.url_for('PEOPLE_FOR_ISSUE_ASSIGNMENT', project_id, issue_order_number), http_helper_options)
114
+
115
+ issue_tracker.people_for_issue_assignment(project_id, issue_order_number)
116
+ end
117
+
118
+ it "requests to create an issue" do
119
+ project_id = 1
120
+ title = 'required title'
121
+ priority_level_id = 2 # required
122
+ resolver_id = 2 # required
123
+ tester_id = 2 # required
124
+
125
+ data = {
126
+ 'title' => title,
127
+ 'priority_level_id' => priority_level_id,
128
+ 'resolver_id' => resolver_id,
129
+ 'tester_id' => tester_id,
130
+ }
131
+
132
+ options = http_helper_options.merge(:data => data)
133
+ http_helper.should_receive(:post).with( DoneDone::Constant.url_for('CREATE_ISSUE', project_id), options)
134
+
135
+ issue_tracker.create_issue(project_id, title, priority_level_id, resolver_id, tester_id)
136
+ end
137
+
138
+ # todo: test for passing file upload(s)
139
+ it "requests to create a comment for a project's issue" do
140
+ project_id = 1
141
+ issue_order_number = 1
142
+ comment = 'required comment'
143
+
144
+ data = { 'comment' => comment }
145
+
146
+ options = http_helper_options.merge(:data => data)
147
+ http_helper.should_receive(:post).with( DoneDone::Constant.url_for('COMMENT', project_id, issue_order_number), options)
148
+
149
+ issue_tracker.create_comment(project_id, issue_order_number, comment)
150
+ end
151
+
152
+ it "requests to update a project's issue" do
153
+ project_id = 1
154
+ issue_order_number = 1
155
+
156
+ options = http_helper_options
157
+ http_helper.should_receive(:put).with( DoneDone::Constant.url_for('ISSUE', project_id, issue_order_number), options)
158
+
159
+ issue_tracker.update_issue(project_id, issue_order_number, options)
160
+ end
161
+
162
+ end
163
+
164
+ end
@@ -0,0 +1,12 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ require "rspec"
5
+ require "donedone"
6
+
7
+ #require 'simplecov'
8
+
9
+ #SimpleCov.start do
10
+ # coverage_dir 'coverage'
11
+ # add_filter "/spec/"
12
+ #end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: donedone
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Thomas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mime-types
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 2.14.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 2.14.0
41
+ description: Check your existing todo items, add new ones, update old one
42
+ email:
43
+ - buyer+donedone@his-service.net
44
+ executables:
45
+ - donedone
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - Gemfile
51
+ - MIT-LICENSE
52
+ - README.md
53
+ - Rakefile
54
+ - bin/donedone
55
+ - donedone.gemspec
56
+ - examples/lighthouse_csv_importer.rb
57
+ - lib/donedone.rb
58
+ - lib/donedone/constant.rb
59
+ - lib/donedone/http.rb
60
+ - lib/donedone/issue_tracker.rb
61
+ - lib/donedone/multipart.rb
62
+ - lib/donedone/version.rb
63
+ - spec/donedone/constant_spec.rb
64
+ - spec/donedone/http_spec.rb
65
+ - spec/donedone/issue_tracker_spec.rb
66
+ - spec/spec_helper.rb
67
+ homepage: https://github.com/zoodles/DoneDone-API-Ruby
68
+ licenses:
69
+ - MIT
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project: donedone
87
+ rubygems_version: 2.0.6
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: donedone.com api client
91
+ test_files:
92
+ - spec/donedone/constant_spec.rb
93
+ - spec/donedone/http_spec.rb
94
+ - spec/donedone/issue_tracker_spec.rb
95
+ - spec/spec_helper.rb