harvested 2.0.0 → 3.0.0.rc1
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.
- 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
|