harvested 2.0.0 → 3.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/{HISTORY → HISTORY.md} +43 -19
- data/harvested.gemspec +2 -0
- data/lib/harvest/api/base.rb +15 -13
- data/lib/harvest/api/reports.rb +27 -13
- data/lib/harvest/base.rb +8 -3
- data/lib/harvest/credentials.rb +24 -8
- data/lib/harvest/errors.rb +2 -4
- data/lib/harvest/version.rb +1 -1
- data/lib/harvested.rb +23 -9
- data/spec/functional/reporting_spec.rb +45 -41
- data/spec/functional/users_spec.rb +0 -18
- data/spec/harvest/base_spec.rb +1 -1
- data/spec/harvest/basic_auth_credentials_spec.rb +11 -0
- data/spec/harvest/oauth_credentials_spec.rb +11 -0
- data/spec/harvest/task_spec.rb +1 -3
- data/spec/support/harvested_helpers.rb +4 -4
- metadata +10 -8
- data/spec/harvest/credentials_spec.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d40d1bfd28ea226ff25aa4e6f6b78d6ce3df6d37
|
4
|
+
data.tar.gz: c3ebf97344f99de79afc0eae59dd6b0a6de43f7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bc9034c02ad6d9ce05a8a2c70b545db1c8b5924085c674ec07093092c2d95c18f3af451240278e858398b6c445805615fa263f2d6892de82842e7bbe35d6ee3
|
7
|
+
data.tar.gz: bcea2bb7c21a00dba42ba7f666d79fe343b618f75a855b60f0c9cada27b39a4eed577c96181b843ba3cb50fdaef0acab99b8c73763a5879ddd009e85d2587848
|
data/.gitignore
CHANGED
data/{HISTORY → HISTORY.md}
RENAMED
@@ -1,38 +1,58 @@
|
|
1
|
-
|
1
|
+
## 3.0.0.rc1 - May 30, 2014
|
2
|
+
* Require Ruby 2.0+
|
3
|
+
* Allow OAuth authentication (Thanks Brendan Loudermilk - @bloudermilk)
|
4
|
+
* Allow expenses_by_project to be retrieved (Thanks Jordan Yeo - @jordanyeo)
|
5
|
+
* Reports now pass through any remaining options (Thanks Philip Arndt - @parndt)
|
6
|
+
|
7
|
+
## 2.0.0 - April 23, 2014
|
2
8
|
* Every connection must be SSL
|
3
|
-
|
9
|
+
|
10
|
+
## 1.2.0 - February 21, 2014
|
4
11
|
* Adds time.trackable_projects: projects that the current user can create entries for
|
5
12
|
* Show hint on error for projects.all as unprivileged user
|
6
|
-
|
13
|
+
|
14
|
+
## 1.1.0 - December 13, 2013
|
7
15
|
* Adds ability to toggle timers (thanks Eli Fatsi - @efatsi)
|
8
|
-
|
16
|
+
|
17
|
+
## 1.0.1 - June 21, 2013
|
9
18
|
* Adds ability to pass updated_since paramter to report methods (thanks Pete McWilliams)
|
10
19
|
* Adds ability to create time entries for a given user
|
11
|
-
|
20
|
+
|
21
|
+
## 1.0.0 - April 26, 2013
|
12
22
|
* Same as 0.6.4 - This should have been v1.0 long ago.
|
13
|
-
|
23
|
+
|
24
|
+
## 0.6.4 - October 24, 2012
|
14
25
|
* Removes yard and redcarpet dependencies (added on accident)
|
15
|
-
|
26
|
+
|
27
|
+
## 0.6.3 - October 24, 2012
|
16
28
|
* Adds task activation (Thanks Mark Rickert - @markrickert)
|
17
29
|
* Adds basic invoice support (Thanks Jeffrey Lee - @jlee42)
|
18
30
|
* Adds basic invoice payment support (Thanks Adam Doeler - @releod)
|
19
|
-
|
31
|
+
|
32
|
+
## 0.6.2 - August 25, 2012
|
20
33
|
* Fixes Mash constructor errors
|
21
|
-
|
34
|
+
|
35
|
+
## 0.6.1 - August 22, 2012
|
22
36
|
* Adds options to "all" finder (thanks Mikel Lindsaar)
|
23
37
|
* Adds Unauthorized error type (thanks @bcobb)
|
24
|
-
|
38
|
+
|
39
|
+
## 0.6.0 - August 22, 2012
|
25
40
|
* Replaces Dash with Mash
|
26
|
-
|
41
|
+
|
42
|
+
## 0.5.3 - August 21, 2012
|
27
43
|
* Adds new fields has_timesheet_2012_beta and timesheet_2012_beta_control_group to Users (thanks Aldric Giacomoni)
|
28
|
-
|
44
|
+
|
45
|
+
## 0.5.2 - August 17, 2012
|
29
46
|
* Adds new field password_change_required to Users
|
30
|
-
|
47
|
+
|
48
|
+
## 0.5.1 - June 27, 2012
|
31
49
|
* Updates README and harvested_credentials.example.yml with warnings about normal accounts
|
32
|
-
|
50
|
+
|
51
|
+
## 0.5.0 - June 18, 2012
|
33
52
|
* Bugfixes on User: https://github.com/zmoazeni/harvested/pull/26
|
34
53
|
* Bugfixes on TimeEntry: https://github.com/zmoazeni/harvested/pull/25
|
35
|
-
|
54
|
+
|
55
|
+
## 0.4.0 - August 4, 2011
|
36
56
|
* Large rewrite of codebase
|
37
57
|
* Rewrite of library to use JSON instead of XML
|
38
58
|
* Rewrite of tests to use rspec instead of cucumber
|
@@ -41,14 +61,18 @@
|
|
41
61
|
* Implements most of the invoice functionality. Creating/Editing/Updating is disabled due to Harvest API issue
|
42
62
|
* Consolidates various forks (thanks Chris Ritterdorf, bricooke, Michelle Moon Lee, tkwong, Steve McKinney, and others that helped but aren't in the git log!)
|
43
63
|
* Tests passing against MRI 1.8, 1.9, JRuby, and Rubinius
|
44
|
-
|
64
|
+
|
65
|
+
## 0.3.3 - January 27, 2010
|
45
66
|
* Adds fields to TaskAssignment (Quilted)
|
46
|
-
|
67
|
+
|
68
|
+
## 0.3.2 - January 27, 2010
|
47
69
|
* Adds of_user support to Time Entry (Benjamin Wong - tkwong)
|
48
|
-
|
70
|
+
|
71
|
+
## 0.3.1 - October 14, 2010
|
49
72
|
* Updates docs removing :ssl => false since Harvest released SSL to everyone
|
50
73
|
* Adds department to the User model (Steve McKinney - )
|
51
|
-
|
74
|
+
|
75
|
+
## 0.3.0 - April 11, 2010
|
52
76
|
* Adds users
|
53
77
|
* Adds clients
|
54
78
|
* Adds contacts
|
data/harvested.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.required_ruby_version = '>= 2.0'
|
22
|
+
|
21
23
|
spec.add_runtime_dependency('httparty')
|
22
24
|
spec.add_runtime_dependency('hashie', '~> 1')
|
23
25
|
spec.add_runtime_dependency('json')
|
data/lib/harvest/api/base.rb
CHANGED
@@ -19,23 +19,25 @@ module Harvest
|
|
19
19
|
|
20
20
|
protected
|
21
21
|
def request(method, credentials, path, options = {})
|
22
|
-
params = {
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
params = {
|
23
|
+
path: path,
|
24
|
+
options: options,
|
25
|
+
method: method
|
26
|
+
}
|
26
27
|
|
27
|
-
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
"Accept"
|
28
|
+
httparty_options = {
|
29
|
+
query: options[:query],
|
30
|
+
body: options[:body],
|
31
|
+
format: :plain,
|
32
|
+
headers: {
|
33
|
+
"Accept" => "application/json",
|
33
34
|
"Content-Type" => "application/json; charset=utf-8",
|
34
|
-
"
|
35
|
-
"User-Agent" => "Harvestable/#{Harvest::VERSION}",
|
35
|
+
"User-Agent" => "Harvested/#{Harvest::VERSION}"
|
36
36
|
}.update(options[:headers] || {})
|
37
|
-
|
37
|
+
}
|
38
38
|
|
39
|
+
credentials.set_authentication(httparty_options)
|
40
|
+
response = HTTParty.send(method, "#{credentials.host}#{path}", httparty_options)
|
39
41
|
params[:response] = response.inspect.to_s
|
40
42
|
|
41
43
|
case response.code
|
data/lib/harvest/api/reports.rb
CHANGED
@@ -2,31 +2,45 @@ module Harvest
|
|
2
2
|
module API
|
3
3
|
class Reports < Base
|
4
4
|
|
5
|
+
TIME_FORMAT = '%Y%m%d'
|
6
|
+
|
5
7
|
def time_by_project(project, start_date, end_date, options = {})
|
6
|
-
query = {:
|
7
|
-
query[:user_id] = options
|
8
|
-
query[:billable] = (options
|
9
|
-
query[:updated_since] = options
|
8
|
+
query = { from: start_date.strftime(TIME_FORMAT), to: end_date.strftime(TIME_FORMAT) }
|
9
|
+
query[:user_id] = options.delete(:user).to_i if options[:user]
|
10
|
+
query[:billable] = (options.delete(:billable) ? "yes" : "no") unless options[:billable].nil?
|
11
|
+
query[:updated_since] = options.delete(:updated_since).to_s if options[:updated_since]
|
12
|
+
query.update(options)
|
10
13
|
|
11
|
-
response = request(:get, credentials, "/projects/#{project.to_i}/entries", :
|
14
|
+
response = request(:get, credentials, "/projects/#{project.to_i}/entries", query: query)
|
12
15
|
Harvest::TimeEntry.parse(JSON.parse(response.body).map {|h| h["day_entry"]})
|
13
16
|
end
|
14
17
|
|
15
18
|
def time_by_user(user, start_date, end_date, options = {})
|
16
|
-
query = {:
|
17
|
-
query[:project_id] = options
|
18
|
-
query[:billable] = (options
|
19
|
-
query[:updated_since] = options
|
19
|
+
query = { from: start_date.strftime(TIME_FORMAT), to: end_date.strftime(TIME_FORMAT) }
|
20
|
+
query[:project_id] = options.delete(:project).to_i if options[:project]
|
21
|
+
query[:billable] = (options.delete(:billable) ? "yes" : "no") unless options[:billable].nil?
|
22
|
+
query[:updated_since] = options.delete(:updated_since).to_s if options[:updated_since]
|
23
|
+
query.update(options)
|
20
24
|
|
21
|
-
response = request(:get, credentials, "/people/#{user.to_i}/entries", :
|
25
|
+
response = request(:get, credentials, "/people/#{user.to_i}/entries", query: query)
|
22
26
|
Harvest::TimeEntry.parse(JSON.parse(response.body).map {|h| h["day_entry"]})
|
23
27
|
end
|
24
28
|
|
25
29
|
def expenses_by_user(user, start_date, end_date, options = {})
|
26
|
-
query = {:
|
27
|
-
query[:updated_since] = options
|
30
|
+
query = { from: start_date.strftime(TIME_FORMAT), to: end_date.strftime(TIME_FORMAT) }
|
31
|
+
query[:updated_since] = options.delete(:updated_since).to_s if options[:updated_since]
|
32
|
+
query.update(options)
|
33
|
+
|
34
|
+
response = request(:get, credentials, "/people/#{user.to_i}/expenses", query: query)
|
35
|
+
Harvest::Expense.parse(response.parsed_response)
|
36
|
+
end
|
37
|
+
|
38
|
+
def expenses_by_project(project, start_date, end_date, options = {})
|
39
|
+
query = { from: start_date.strftime(TIME_FORMAT), to: end_date.strftime(TIME_FORMAT) }
|
40
|
+
query[:updated_since] = options.delete(:updated_since).to_s if options[:updated_since]
|
41
|
+
query.update(options)
|
28
42
|
|
29
|
-
response = request(:get, credentials, "/
|
43
|
+
response = request(:get, credentials, "/projects/#{project.to_i}/expenses", query: query)
|
30
44
|
Harvest::Expense.parse(response.parsed_response)
|
31
45
|
end
|
32
46
|
|
data/lib/harvest/base.rb
CHANGED
@@ -4,9 +4,14 @@ module Harvest
|
|
4
4
|
|
5
5
|
# @see Harvest.client
|
6
6
|
# @see Harvest.hardy_client
|
7
|
-
def initialize(subdomain, username, password)
|
8
|
-
@credentials =
|
9
|
-
|
7
|
+
def initialize(subdomain: nil, username: nil, password: nil, access_token: nil)
|
8
|
+
@credentials = if subdomain && username && password
|
9
|
+
BasicAuthCredentials.new(subdomain: subdomain, username: username, password: password)
|
10
|
+
elsif access_token
|
11
|
+
OAuthCredentials.new(access_token)
|
12
|
+
else
|
13
|
+
fail 'You must provide either :subdomain, :username, and :password or an oauth :access_token'
|
14
|
+
end
|
10
15
|
end
|
11
16
|
|
12
17
|
# All API actions surrounding accounts
|
data/lib/harvest/credentials.rb
CHANGED
@@ -1,21 +1,37 @@
|
|
1
1
|
module Harvest
|
2
|
-
class
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(subdomain, username, password)
|
2
|
+
class BasicAuthCredentials
|
3
|
+
def initialize(subdomain: nil, username: nil, password: nil)
|
6
4
|
@subdomain, @username, @password = subdomain, username, password
|
7
5
|
end
|
8
6
|
|
9
|
-
def
|
10
|
-
|
7
|
+
def set_authentication(request_options)
|
8
|
+
request_options[:headers] ||= {}
|
9
|
+
request_options[:headers]["Authorization"] = "Basic #{basic_auth}"
|
11
10
|
end
|
12
11
|
|
12
|
+
def host
|
13
|
+
"https://#{@subdomain}.harvestapp.com"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
13
18
|
def basic_auth
|
14
|
-
Base64.encode64("#{username}:#{password}").delete("\r\n")
|
19
|
+
Base64.encode64("#{@username}:#{@password}").delete("\r\n")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class OAuthCredentials
|
24
|
+
def initialize(access_token)
|
25
|
+
@access_token = access_token
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_authentication(request_options)
|
29
|
+
request_options[:query] ||= {}
|
30
|
+
request_options[:query]["access_token"] = @access_token
|
15
31
|
end
|
16
32
|
|
17
33
|
def host
|
18
|
-
"https
|
34
|
+
"https://api.harvestapp.com"
|
19
35
|
end
|
20
36
|
end
|
21
37
|
end
|
data/lib/harvest/errors.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
module Harvest
|
2
|
-
class InvalidCredentials < StandardError; end
|
3
|
-
|
4
2
|
class HTTPError < StandardError
|
5
3
|
attr_reader :response
|
6
4
|
attr_reader :params
|
@@ -12,12 +10,12 @@ module Harvest
|
|
12
10
|
@hint = hint
|
13
11
|
super(response)
|
14
12
|
end
|
15
|
-
|
13
|
+
|
16
14
|
def to_s
|
17
15
|
"#{self.class.to_s} : #{response.code} #{response.body}" + (hint ? "\n#{hint}" : "")
|
18
16
|
end
|
19
17
|
end
|
20
|
-
|
18
|
+
|
21
19
|
class RateLimited < HTTPError; end
|
22
20
|
class NotFound < HTTPError; end
|
23
21
|
class Unavailable < HTTPError; end
|
data/lib/harvest/version.rb
CHANGED
data/lib/harvested.rb
CHANGED
@@ -29,23 +29,38 @@ module Harvest
|
|
29
29
|
# Creates a standard client that will raise all errors it encounters
|
30
30
|
#
|
31
31
|
# == Options
|
32
|
-
# *
|
32
|
+
# * Basic Authentication
|
33
|
+
# * +:subdomain+ - Your Harvest subdomain
|
34
|
+
# * +:username+ - Your Harvest username
|
35
|
+
# * +:password+ - Your Harvest password
|
36
|
+
# * OAuth
|
37
|
+
# * +:access_token+ - An OAuth 2.0 access token
|
38
|
+
#
|
33
39
|
# == Examples
|
34
|
-
# Harvest.client('mysubdomain', 'myusername', 'mypassword'
|
40
|
+
# Harvest.client(subdomain: 'mysubdomain', username: 'myusername', password: 'mypassword')
|
41
|
+
# Harvest.client(access_token: 'myaccesstoken')
|
35
42
|
#
|
36
43
|
# @return [Harvest::Base]
|
37
|
-
def client(subdomain, username, password,
|
38
|
-
Harvest::Base.new(subdomain, username, password)
|
44
|
+
def client(subdomain: nil, username: nil, password: nil, access_token: nil)
|
45
|
+
Harvest::Base.new(subdomain: subdomain, username: username, password: password, access_token: access_token)
|
39
46
|
end
|
40
47
|
|
41
48
|
# Creates a hardy client that will retry common HTTP errors it encounters and sleep() if it determines it is over your rate limit
|
42
49
|
#
|
43
50
|
# == Options
|
44
|
-
# *
|
51
|
+
# * Basic Authentication
|
52
|
+
# * +:subdomain+ - Your Harvest subdomain
|
53
|
+
# * +:username+ - Your Harvest username
|
54
|
+
# * +:password+ - Your Harvest password
|
55
|
+
# * OAuth
|
56
|
+
# * +:access_token+ - An OAuth 2.0 access token
|
57
|
+
#
|
45
58
|
# * +:retry+ - How many times the hardy client should retry errors. Set to +5+ by default.
|
46
59
|
#
|
47
60
|
# == Examples
|
48
|
-
# Harvest.hardy_client('mysubdomain', 'myusername', 'mypassword', :
|
61
|
+
# Harvest.hardy_client(subdomain: 'mysubdomain', username: 'myusername', password: 'mypassword', retry: 3)
|
62
|
+
#
|
63
|
+
# Harvest.hardy_client(access_token: 'myaccesstoken', retries: 3)
|
49
64
|
#
|
50
65
|
# == Errors
|
51
66
|
# The hardy client will retry the following errors
|
@@ -60,9 +75,8 @@ module Harvest
|
|
60
75
|
#
|
61
76
|
# @return [Harvest::HardyClient] a Harvest::Base wrapped in a Harvest::HardyClient
|
62
77
|
# @see Harvest::Base
|
63
|
-
def hardy_client(subdomain, username, password,
|
64
|
-
|
65
|
-
Harvest::HardyClient.new(client(subdomain, username, password), (retries || 5))
|
78
|
+
def hardy_client(subdomain: nil, username: nil, password: nil, access_token: nil, retries: 5)
|
79
|
+
Harvest::HardyClient.new(client(subdomain: subdomain, username: username, password: password, access_token: access_token), retries)
|
66
80
|
end
|
67
81
|
end
|
68
82
|
end
|
@@ -4,52 +4,52 @@ describe 'harvest reporting' do
|
|
4
4
|
it 'allows project and people entry reporting' do
|
5
5
|
cassette("reports1") do
|
6
6
|
user = harvest.users.create(
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
email: "jane@example.com",
|
8
|
+
first_name: "Jane",
|
9
|
+
last_name: "Doe",
|
10
|
+
password: "secure"
|
11
11
|
)
|
12
|
-
client = harvest.clients.create(
|
13
|
-
project1 = harvest.projects.create(
|
14
|
-
project2 = harvest.projects.create(
|
15
|
-
task1 = harvest.tasks.create(
|
16
|
-
task2 = harvest.tasks.create(
|
12
|
+
client = harvest.clients.create(name: "Tim's Dry Cleaning")
|
13
|
+
project1 = harvest.projects.create(name: "Reporting Project1", client_id: client.id)
|
14
|
+
project2 = harvest.projects.create(name: "Reporting Project2", client_id: client.id)
|
15
|
+
task1 = harvest.tasks.create(name: "A billable task", "default_hourly_rate" => 120, "billable_by_default" => true)
|
16
|
+
task2 = harvest.tasks.create(name: "A non billable task", "billable_by_default" => false)
|
17
17
|
|
18
|
-
harvest.task_assignments.create(
|
19
|
-
harvest.task_assignments.create(
|
20
|
-
harvest.user_assignments.create(
|
21
|
-
harvest.user_assignments.create(
|
18
|
+
harvest.task_assignments.create(project: project1, task: task1)
|
19
|
+
harvest.task_assignments.create(project: project2, task: task2)
|
20
|
+
harvest.user_assignments.create(project: project1, user: user)
|
21
|
+
harvest.user_assignments.create(project: project2, user: user)
|
22
22
|
|
23
|
-
entry1 = harvest.time.create(
|
24
|
-
entry2 = harvest.time.create(
|
23
|
+
entry1 = harvest.time.create(notes: "billable entry1", hours: 3, spent_at: "2009/12/28", task_id: task1.id, project_id: project1.id, of_user: user.id)
|
24
|
+
entry2 = harvest.time.create(notes: "non billable entry2", hours: 6, spent_at: "2009/12/28", task_id: task2.id, project_id: project2.id, of_user: user.id)
|
25
25
|
|
26
26
|
harvest.reports.time_by_project(project1, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).first.should == entry1
|
27
27
|
|
28
|
-
harvest.reports.time_by_project(project1, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :
|
28
|
+
harvest.reports.time_by_project(project1, Time.utc(2009, 12, 20), Time.utc(2009,12,30), user: user).first.should == entry1
|
29
29
|
|
30
|
-
harvest.reports.time_by_project(project2, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :
|
30
|
+
harvest.reports.time_by_project(project2, Time.utc(2009, 12, 20), Time.utc(2009,12,30), user: user).first.should == entry2
|
31
31
|
|
32
|
-
harvest.reports.time_by_project(project2, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :
|
33
|
-
harvest.reports.time_by_project(project2, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :
|
32
|
+
harvest.reports.time_by_project(project2, Time.utc(2009, 12, 20), Time.utc(2009,12,30), billable: true).should == []
|
33
|
+
harvest.reports.time_by_project(project2, Time.utc(2009, 12, 20), Time.utc(2009,12,30), billable: false).first.should == entry2
|
34
34
|
|
35
|
-
harvest.reports.time_by_project(project1, Time.utc(2009, 12, 10), Time.utc(2009,12,30), {:
|
35
|
+
harvest.reports.time_by_project(project1, Time.utc(2009, 12, 10), Time.utc(2009,12,30), {updated_since: Time.now.utc}).should == []
|
36
36
|
|
37
37
|
harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).map(&:id).should == [entry1, entry2].map(&:id)
|
38
38
|
|
39
|
-
harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :
|
39
|
+
harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), project: project1).first.should == entry1
|
40
40
|
|
41
|
-
harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :
|
41
|
+
harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), project: project2).first.should == entry2
|
42
42
|
|
43
|
-
harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :
|
44
|
-
harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :
|
43
|
+
harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), billable: true).first.should == entry1
|
44
|
+
harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), billable: false).first.should == entry2
|
45
45
|
|
46
46
|
entry3_time = Time.now.utc
|
47
|
-
entry3 = harvest.time.create(
|
48
|
-
entries = harvest.reports.time_by_user(user, Time.utc(2009, 12, 10), Time.utc(2009,12,30), {:
|
47
|
+
entry3 = harvest.time.create(notes: "test entry for checking updated_since", hours: 5, spent_at: "2009/12/15", task_id: task1.id, project_id: project1.id, of_user: user.id)
|
48
|
+
entries = harvest.reports.time_by_user(user, Time.utc(2009, 12, 10), Time.utc(2009,12,30), {project: project1, updated_since: entry3_time})
|
49
49
|
entries.first.should == entry3
|
50
50
|
entries.size.should == 1
|
51
51
|
|
52
|
-
client2 = harvest.clients.create(
|
52
|
+
client2 = harvest.clients.create(name: "Phil's Sandwich Shop")
|
53
53
|
harvest.reports.projects_by_client(client).map(&:id).to_set.should == [project1, project2].map(&:id).to_set
|
54
54
|
harvest.reports.projects_by_client(client2).should == []
|
55
55
|
end
|
@@ -58,32 +58,36 @@ describe 'harvest reporting' do
|
|
58
58
|
it 'allows expense reporting' do
|
59
59
|
cassette("reports2") do
|
60
60
|
user = harvest.users.create(
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
email: "simon@example.com",
|
62
|
+
first_name: "Simon",
|
63
|
+
last_name: "Stir",
|
64
|
+
password: "secure"
|
65
65
|
)
|
66
66
|
|
67
|
-
category = harvest.expense_categories.create(
|
68
|
-
client = harvest.clients.create(
|
69
|
-
project = harvest.projects.create(
|
70
|
-
harvest.user_assignments.create(
|
67
|
+
category = harvest.expense_categories.create(name: "Reporting category", "unit_price" => 100, "unit_name" => "deduction")
|
68
|
+
client = harvest.clients.create(name: "Philip's Butcher")
|
69
|
+
project = harvest.projects.create(name: "Expense Reporting Project", client_id: client.id)
|
70
|
+
harvest.user_assignments.create(project: project, user: user)
|
71
71
|
|
72
72
|
expense = harvest.expenses.create({
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
73
|
+
notes: "Drive to Chicago",
|
74
|
+
total_cost: 75.0,
|
75
|
+
spent_at: Time.utc(2009, 12, 28),
|
76
|
+
expense_category_id: category.id,
|
77
|
+
project_id: project.id
|
78
78
|
}, user.id)
|
79
79
|
|
80
|
+
harvest.reports.expenses_by_project(project, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).first.should == expense
|
80
81
|
harvest.reports.expenses_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).first.should == expense
|
81
82
|
|
82
|
-
harvest.reports.expenses_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), {:
|
83
|
+
harvest.reports.expenses_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), {updated_since: Time.now.utc}).should == []
|
83
84
|
|
84
85
|
my_user = harvest.users.all.detect {|u| u.email == credentials["username"]}
|
85
86
|
my_user.should_not be_nil
|
86
87
|
harvest.reports.expenses_by_user(my_user, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).should == []
|
88
|
+
|
89
|
+
my_project = harvest.projects.create(name: "Travel Expense Project", client_id: client.id)
|
90
|
+
harvest.reports.expenses_by_project(my_project, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).should == []
|
87
91
|
end
|
88
92
|
end
|
89
93
|
end
|
@@ -44,24 +44,6 @@ describe 'harvest users' do
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
it "allows password resets" do
|
48
|
-
cassette("users3") do
|
49
|
-
pending "Something is wrong with resetting passwords"
|
50
|
-
user = harvest.users.create(
|
51
|
-
"first_name" => "Timmy",
|
52
|
-
"last_name" => "Ruth",
|
53
|
-
"email" => "timmy@ruth.com",
|
54
|
-
"timezone" => "cst",
|
55
|
-
"is_admin" => "false",
|
56
|
-
"telephone" => "444-4444"
|
57
|
-
)
|
58
|
-
user.should be_active
|
59
|
-
|
60
|
-
harvest.users.reset_password(user) # nothing else to assert
|
61
|
-
harvest.users.delete(user)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
47
|
context "assignments" do
|
66
48
|
it "allows adding, updating, and removing users from projects" do
|
67
49
|
cassette('users4') do
|
data/spec/harvest/base_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Harvest::Base do
|
4
4
|
describe "username/password errors" do
|
5
5
|
it "should raise error if missing a credential" do
|
6
|
-
lambda { Harvest::Base.new("subdomain",
|
6
|
+
lambda { Harvest::Base.new(subdomain: "subdomain", password: "secure") }.should raise_error(/must provide either/i)
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Harvest::BasicAuthCredentials do
|
4
|
+
context '#set_authentication' do
|
5
|
+
it "should set the headers Authorization" do
|
6
|
+
credentials = Harvest::BasicAuthCredentials.new(subdomain: 'some-domain', username: 'username', password: 'password')
|
7
|
+
credentials.set_authentication(options = {})
|
8
|
+
options[:headers]['Authorization'].should == "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Harvest::OAuthCredentials do
|
4
|
+
context '#set_authentication' do
|
5
|
+
it "should set the access token in the query" do
|
6
|
+
credentials = Harvest::OAuthCredentials.new('theaccesstoken')
|
7
|
+
credentials.set_authentication(options = {})
|
8
|
+
options[:query]['access_token'].should == 'theaccesstoken'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/spec/harvest/task_spec.rb
CHANGED
@@ -6,23 +6,23 @@ module HarvestedHelpers
|
|
6
6
|
def credentials; HarvestedHelpers.credentials; end
|
7
7
|
|
8
8
|
def self.simple_harvest
|
9
|
-
Harvest.client(credentials["subdomain"], credentials["username"], credentials["password"])
|
9
|
+
Harvest.client(subdomain: credentials["subdomain"], username: credentials["username"], password: credentials["password"])
|
10
10
|
end
|
11
11
|
|
12
12
|
def harvest; @harvest ||= HarvestedHelpers.simple_harvest; end
|
13
13
|
|
14
14
|
def hardy_harvest
|
15
|
-
Harvest.hardy_client(credentials["subdomain"], credentials["username"], credentials["password"])
|
15
|
+
Harvest.hardy_client(subdomain: credentials["subdomain"], username: credentials["username"], password: credentials["password"])
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.clean_remote
|
19
19
|
harvest = simple_harvest
|
20
20
|
harvest.users.all.each do |u|
|
21
|
-
harvest.reports.expenses_by_user(u, Time.utc(2000, 1, 1), Time.utc(
|
21
|
+
harvest.reports.expenses_by_user(u, Time.utc(2000, 1, 1), Time.utc(2014, 6, 21)).each do |expense|
|
22
22
|
harvest.expenses.delete(expense, u)
|
23
23
|
end
|
24
24
|
|
25
|
-
harvest.reports.time_by_user(u, Time.utc(2000, 1, 1), Time.utc(
|
25
|
+
harvest.reports.time_by_user(u, Time.utc(2000, 1, 1), Time.utc(2014, 6, 21)).each do |time|
|
26
26
|
harvest.time.delete(time, u)
|
27
27
|
end
|
28
28
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: harvested
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Moazeni
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -65,7 +65,7 @@ files:
|
|
65
65
|
- ".gitignore"
|
66
66
|
- ".ruby-version"
|
67
67
|
- Gemfile
|
68
|
-
- HISTORY
|
68
|
+
- HISTORY.md
|
69
69
|
- MIT-LICENSE
|
70
70
|
- README.md
|
71
71
|
- Rakefile
|
@@ -136,11 +136,12 @@ files:
|
|
136
136
|
- spec/functional/time_tracking_spec.rb
|
137
137
|
- spec/functional/users_spec.rb
|
138
138
|
- spec/harvest/base_spec.rb
|
139
|
-
- spec/harvest/
|
139
|
+
- spec/harvest/basic_auth_credentials_spec.rb
|
140
140
|
- spec/harvest/expense_category_spec.rb
|
141
141
|
- spec/harvest/expense_spec.rb
|
142
142
|
- spec/harvest/invoice_payment_spec.rb
|
143
143
|
- spec/harvest/invoice_spec.rb
|
144
|
+
- spec/harvest/oauth_credentials_spec.rb
|
144
145
|
- spec/harvest/project_spec.rb
|
145
146
|
- spec/harvest/task_assignment_spec.rb
|
146
147
|
- spec/harvest/task_spec.rb
|
@@ -165,12 +166,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
165
166
|
requirements:
|
166
167
|
- - ">="
|
167
168
|
- !ruby/object:Gem::Version
|
168
|
-
version: '0'
|
169
|
+
version: '2.0'
|
169
170
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
171
|
requirements:
|
171
|
-
- - "
|
172
|
+
- - ">"
|
172
173
|
- !ruby/object:Gem::Version
|
173
|
-
version:
|
174
|
+
version: 1.3.1
|
174
175
|
requirements: []
|
175
176
|
rubyforge_project:
|
176
177
|
rubygems_version: 2.2.2
|
@@ -192,11 +193,12 @@ test_files:
|
|
192
193
|
- spec/functional/time_tracking_spec.rb
|
193
194
|
- spec/functional/users_spec.rb
|
194
195
|
- spec/harvest/base_spec.rb
|
195
|
-
- spec/harvest/
|
196
|
+
- spec/harvest/basic_auth_credentials_spec.rb
|
196
197
|
- spec/harvest/expense_category_spec.rb
|
197
198
|
- spec/harvest/expense_spec.rb
|
198
199
|
- spec/harvest/invoice_payment_spec.rb
|
199
200
|
- spec/harvest/invoice_spec.rb
|
201
|
+
- spec/harvest/oauth_credentials_spec.rb
|
200
202
|
- spec/harvest/project_spec.rb
|
201
203
|
- spec/harvest/task_assignment_spec.rb
|
202
204
|
- spec/harvest/task_spec.rb
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Harvest::Credentials do
|
4
|
-
describe "#valid?" do
|
5
|
-
it "should return true if domain, username, and password is filled out" do
|
6
|
-
Harvest::Credentials.new("some-domain", "username", "password").should be_valid
|
7
|
-
end
|
8
|
-
|
9
|
-
it "should return false if either domain, username, or password is nil" do
|
10
|
-
Harvest::Credentials.new("some-domain", "username", nil).should_not be_valid
|
11
|
-
Harvest::Credentials.new("some-domain", nil, "password").should_not be_valid
|
12
|
-
Harvest::Credentials.new(nil, "username", "password").should_not be_valid
|
13
|
-
Harvest::Credentials.new(nil, nil, nil).should_not be_valid
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
describe "#basic_auth" do
|
18
|
-
it "should base64 encode the credentials" do
|
19
|
-
Harvest::Credentials.new("some-domain", "username", "password").basic_auth.should == "dXNlcm5hbWU6cGFzc3dvcmQ="
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|