forecasted 0.0.1
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 +7 -0
- data/.document +3 -0
- data/.gitignore +39 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +19 -0
- data/HISTORY.md +19 -0
- data/MIT-LICENSE +20 -0
- data/README.md +109 -0
- data/Rakefile +22 -0
- data/examples/basics.rb +35 -0
- data/examples/clear_account.rb +28 -0
- data/examples/project_create_script.rb +93 -0
- data/examples/task_assignments.rb +27 -0
- data/examples/user_assignments.rb +24 -0
- data/forecasted.gemspec +25 -0
- data/lib/ext/array.rb +52 -0
- data/lib/ext/date.rb +9 -0
- data/lib/ext/hash.rb +17 -0
- data/lib/ext/time.rb +5 -0
- data/lib/forecast/aggregate.rb +8 -0
- data/lib/forecast/api/account.rb +22 -0
- data/lib/forecast/api/aggregates.rb +20 -0
- data/lib/forecast/api/assignments.rb +63 -0
- data/lib/forecast/api/base.rb +68 -0
- data/lib/forecast/api/clients.rb +10 -0
- data/lib/forecast/api/expense_categories.rb +9 -0
- data/lib/forecast/api/expenses.rb +27 -0
- data/lib/forecast/api/invoice_categories.rb +26 -0
- data/lib/forecast/api/invoice_messages.rb +75 -0
- data/lib/forecast/api/invoice_payments.rb +31 -0
- data/lib/forecast/api/invoices.rb +35 -0
- data/lib/forecast/api/milestones.rb +21 -0
- data/lib/forecast/api/projects.rb +23 -0
- data/lib/forecast/api/reports.rb +53 -0
- data/lib/forecast/api/tasks.rb +36 -0
- data/lib/forecast/api/time.rb +48 -0
- data/lib/forecast/api/user_assignments.rb +34 -0
- data/lib/forecast/api/users.rb +21 -0
- data/lib/forecast/assignment.rb +7 -0
- data/lib/forecast/base.rb +111 -0
- data/lib/forecast/behavior/activatable.rb +31 -0
- data/lib/forecast/behavior/crud.rb +75 -0
- data/lib/forecast/client.rb +22 -0
- data/lib/forecast/credentials.rb +42 -0
- data/lib/forecast/errors.rb +26 -0
- data/lib/forecast/expense.rb +27 -0
- data/lib/forecast/expense_category.rb +10 -0
- data/lib/forecast/hardy_client.rb +80 -0
- data/lib/forecast/invoice.rb +107 -0
- data/lib/forecast/invoice_category.rb +9 -0
- data/lib/forecast/invoice_message.rb +8 -0
- data/lib/forecast/invoice_payment.rb +8 -0
- data/lib/forecast/line_item.rb +4 -0
- data/lib/forecast/model.rb +154 -0
- data/lib/forecast/project.rb +37 -0
- data/lib/forecast/rate_limit_status.rb +23 -0
- data/lib/forecast/task.rb +22 -0
- data/lib/forecast/time_entry.rb +25 -0
- data/lib/forecast/timezones.rb +130 -0
- data/lib/forecast/trackable_project.rb +38 -0
- data/lib/forecast/user_assignment.rb +30 -0
- data/lib/forecast/version.rb +3 -0
- data/lib/forecasted.rb +87 -0
- data/spec/factories.rb +17 -0
- data/spec/forecast/base_spec.rb +11 -0
- data/spec/functional/aggregates_spec.rb +64 -0
- data/spec/functional/assignments_spec.rb +131 -0
- data/spec/functional/errors_spec.rb +22 -0
- data/spec/functional/hardy_client_spec.rb +33 -0
- data/spec/functional/milestones_spec.rb +82 -0
- data/spec/functional/people_spec.rb +85 -0
- data/spec/functional/project_spec.rb +41 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/forecast_credentials.example.yml +6 -0
- data/spec/support/forecasted_helpers.rb +66 -0
- data/spec/support/json_examples.rb +9 -0
- metadata +189 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 30f437539e2597b97dc9ecf225d2d73e3eba64fa
|
4
|
+
data.tar.gz: 321d7c0c82f628970302c1d891648ecf1e91587a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1c3a5887aa6a2fa90863485bf6df8709b210bdecf8fb806a065bbd97558f841f2e0b762d2d29a1bf173276de62fc0d9dd9ebde04b8a4f81d70b794554083237e
|
7
|
+
data.tar.gz: 4fd50309e57461e1bd9559ae240809419d15cf5d65bf1bb1d788e6b283ebbfc311b1714dfa46363fa63b6354602ff95e77657e0f19b1801081c0ab32ec59daf2
|
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
*.swp
|
15
|
+
|
16
|
+
## PROJECT::GENERAL
|
17
|
+
coverage
|
18
|
+
rdoc
|
19
|
+
pkg
|
20
|
+
doc
|
21
|
+
.yardoc
|
22
|
+
.byebug_history
|
23
|
+
|
24
|
+
## PROJECT::SPECIFIC
|
25
|
+
features/support/forecast_credentials.yml
|
26
|
+
spec/support/forecast_credentials.yml
|
27
|
+
.cassettes
|
28
|
+
.rbx
|
29
|
+
|
30
|
+
## RUBYGEMS
|
31
|
+
*.gem
|
32
|
+
Gemfile.lock
|
33
|
+
.bundle
|
34
|
+
vendor/bundle
|
35
|
+
|
36
|
+
tmp
|
37
|
+
|
38
|
+
## custom ignores
|
39
|
+
weston.md
|
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.1
|
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
cache: bundler
|
4
|
+
|
5
|
+
rvm:
|
6
|
+
# - 2.0.0
|
7
|
+
# - 2.1
|
8
|
+
- 2.2.4
|
9
|
+
- 2.3.1
|
10
|
+
# - ruby-head
|
11
|
+
|
12
|
+
install:
|
13
|
+
- bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle} --without development
|
14
|
+
|
15
|
+
script:
|
16
|
+
- bundle exec rspec spec/forecast
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :development, :test do
|
6
|
+
gem 'bundler', '>= 1.6.2'
|
7
|
+
gem 'rake'
|
8
|
+
gem 'rspec', '~> 3'
|
9
|
+
gem 'jruby-openssl', :platform => [:jruby], :require => false
|
10
|
+
gem 'webmock'
|
11
|
+
gem 'vcr'
|
12
|
+
gem 'factory_girl'
|
13
|
+
end
|
14
|
+
|
15
|
+
group :development do
|
16
|
+
gem 'byebug'
|
17
|
+
gem 'yard'
|
18
|
+
gem 'redcarpet'
|
19
|
+
end
|
data/HISTORY.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
## 0.0.1
|
2
|
+
* projects:
|
3
|
+
* index
|
4
|
+
* index with query params
|
5
|
+
* find
|
6
|
+
* find_by_harvest_id
|
7
|
+
* assignments:
|
8
|
+
* index
|
9
|
+
* index with query params
|
10
|
+
* find
|
11
|
+
* by_project
|
12
|
+
* last_by_project
|
13
|
+
* sum_allocation_seconds
|
14
|
+
* aggreagates:
|
15
|
+
* future_scheduled_hours_after
|
16
|
+
* remaining_budgeted_hours
|
17
|
+
|
18
|
+
|
19
|
+
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016 Weston Platter
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# Forecasted: A Ruby (Harvest)Forecast API
|
2
|
+
|
3
|
+
[](https://travis-ci.org/westonplatter/forecasted)
|
4
|
+
|
5
|
+
Note: I copy+pasted the API Client structure from
|
6
|
+
[from https://github.com/zmoazeni/harvested](from https://github.com/zmoazeni/harvested).
|
7
|
+
|
8
|
+
A Ruby wrapper for the [Forecast (unofficial) API](https://forecastapp.com/).
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
For the time being, add this to your gem file,
|
13
|
+
|
14
|
+
gem 'forecasted', github: 'westonplatter/forecasted'
|
15
|
+
|
16
|
+
I'll push a version to rubygems when the API endpoints are developed.
|
17
|
+
|
18
|
+
## Examples
|
19
|
+
<!--
|
20
|
+
```ruby
|
21
|
+
harvest = Harvest.client(subdomain: 'yoursubdomain', username: 'yourusername', password: 'yourpassword')
|
22
|
+
harvest.projects.all # list out projects
|
23
|
+
|
24
|
+
client = Harvest::Client.new(:name => "Billable Company LTD.")
|
25
|
+
client = harvest.clients.create(client)
|
26
|
+
harvest.clients.find(client.id) # returns a Harvest::Client
|
27
|
+
```
|
28
|
+
|
29
|
+
You can also pass query options in as the last parameter on any object's `all` finder
|
30
|
+
method, for example to find all the projects for client ID 12345:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
harvest = Harvest.client(subdomain: 'yoursubdomain', username: 'yourusername', password: 'yourpassword')
|
34
|
+
harvest.projects.all(nil, :client => 12345)
|
35
|
+
```
|
36
|
+
|
37
|
+
Note: the first parameter is a User ID field that is optional, but needs to be specified
|
38
|
+
as nil if not included.
|
39
|
+
|
40
|
+
You can pass in any hash of query attributes you wish as per the
|
41
|
+
[Harvest API](http://www.getharvest.com/api) page.
|
42
|
+
|
43
|
+
You can find more examples in `/examples` and in the documentation for Harvest::Base
|
44
|
+
-->
|
45
|
+
## Permissions
|
46
|
+
<!--
|
47
|
+
For most operations you need to be an Admin on the Harvest account. You can do a few select things as a normal user or a project manager, but you will likely get errors.
|
48
|
+
|
49
|
+
## Hardy Client
|
50
|
+
|
51
|
+
The team at Harvest built a great API, but there are always dangers in writing code that depends on an API. For example: HTTP Timeouts, Occasional Bad Gateways, and Rate Limiting issues need to be accounted for.
|
52
|
+
|
53
|
+
Using `Harvested#client` your code needs to handle all these situations. However you can also use `Harvested#hardy_client` which will retry errors and wait for Rate Limit resets.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
harvest = Harvest.hardy_client(subdomain: 'yoursubdomain', username: 'yourusername', password: 'yourpassword')
|
57
|
+
harvest.projects.all # This will wait for the Rate Limit reset if you have gone over your limit
|
58
|
+
```
|
59
|
+
-->
|
60
|
+
## Ruby support
|
61
|
+
|
62
|
+
<!-- Harvested's tests currently support Ruby version 2.0+
|
63
|
+
|
64
|
+
## Links
|
65
|
+
|
66
|
+
* [Harvested Documentation](http://rdoc.info/projects/zmoazeni/harvested)
|
67
|
+
* [Harvest API Documentation](http://www.getharvest.com/api)
|
68
|
+
* [Source Code for Harvested](http://github.com/zmoazeni/harvested)
|
69
|
+
|
70
|
+
## Who are you? ##
|
71
|
+
|
72
|
+
My name is [Zach Moazeni](https://twitter.com/zmoazeni). I work for [an
|
73
|
+
awesome company](http://www.getharvest.com/). And [we're
|
74
|
+
hiring!](http://www.getharvest.com/careers) -->
|
75
|
+
|
76
|
+
## How to Contribute
|
77
|
+
|
78
|
+
<!-- If you find what looks like a bug:
|
79
|
+
|
80
|
+
1. Check the GitHub issue tracker to see if anyone else has had the same issue.
|
81
|
+
http://github.com/westonplatter/harvested/issues/
|
82
|
+
2. If you don’t see anything, create an issue with information on how to
|
83
|
+
reproduce it.
|
84
|
+
|
85
|
+
If you want to contribute an enhancement or a fix:
|
86
|
+
|
87
|
+
1. Fork the project on github http://github.com/westonplatter/harvested
|
88
|
+
2. Make your changes with tests
|
89
|
+
3. Commit the changes without messing with the Rakefile, or Version
|
90
|
+
4. Make an entry to HISTORY.md
|
91
|
+
5. Send me a pull request
|
92
|
+
|
93
|
+
Note on running tests: most specs run against a live Forecast account. To run
|
94
|
+
the suite, sign up for a free trial account and fill out
|
95
|
+
`/spec/support/harvest_credentials.yml` *(a
|
96
|
+
sample harvest_credentials.example.yml has been included)*.
|
97
|
+
|
98
|
+
**DO NOT USE YOUR NORMAL CREDENTIALS IN `/spec/support/harvest_credentials.yml`!!!**
|
99
|
+
The test suite blasts all the data before running (similiar to DatabaseCleaner).
|
100
|
+
|
101
|
+
The tests use [VCR](https://github.com/myronmarston/vcr) to cache the API
|
102
|
+
responses. This is a great boon for running the tests offline. While uncommon,
|
103
|
+
sometimes the Harvest API will send an erroneous response, VCR will cache it,
|
104
|
+
and subsequent runs will use the incorrect cached response. In order to clear
|
105
|
+
the cached responses, you can run the specs with the `VCR_REFRESH`
|
106
|
+
environmental variable set to true (e.g. `VCR_REFRESH=true bundle exec rake
|
107
|
+
spec`).
|
108
|
+
|
109
|
+
-->
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
task :default => %w(spec)
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'yard'
|
10
|
+
YARD::Rake::YardocTask.new
|
11
|
+
rescue LoadError
|
12
|
+
task :yardoc do
|
13
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Removes all data on harvest'
|
18
|
+
task 'clean_remote' do
|
19
|
+
# require 'harvested'
|
20
|
+
# require File.expand_path('../spec/support/harvested_helpers', __FILE__)
|
21
|
+
# HarvestedHelpers.clean_remote
|
22
|
+
end
|
data/examples/basics.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# require "harvested"
|
2
|
+
|
3
|
+
# subdomain = 'yoursubdomain'
|
4
|
+
# username = 'yourusername'
|
5
|
+
# password = 'yourpassword'
|
6
|
+
|
7
|
+
# harvest = Harvest.hardy_client(subdomain: subdomain, username: username, password: password)
|
8
|
+
|
9
|
+
# # Print out all users, clients, and projects on the account
|
10
|
+
# puts "Users:"
|
11
|
+
# harvest.users.all.each {|u| p u }
|
12
|
+
|
13
|
+
# puts "Clients:"
|
14
|
+
# harvest.clients.all.each {|c| p c }
|
15
|
+
|
16
|
+
# puts "Projects:"
|
17
|
+
# harvest.projects.all.each {|project| p project }
|
18
|
+
|
19
|
+
# # Create a Client add a Project to that Client, Add a Task to the Project, track some Time against the Project/Task, and then Run a report against all time I've submitted
|
20
|
+
|
21
|
+
# client = Harvest::Client.new(name: 'SuprCorp')
|
22
|
+
# client = harvest.clients.create(client)
|
23
|
+
|
24
|
+
# project = Harvest::Project.new(name: 'SuprGlu', client_id: client.id, notes: 'Some notes about this project')
|
25
|
+
# project = harvest.projects.create(project)
|
26
|
+
|
27
|
+
# harvest.projects.create_task(project, 'Bottling Glue')
|
28
|
+
# task_assignment = harvest.task_assignments.all(project).first
|
29
|
+
|
30
|
+
# time_entry = Harvest::TimeEntry.new(notes: 'Bottled glue today', hours: 8, spent_at: "04/11/2010", project_id: project.id, task_id: task_assignment.task_id)
|
31
|
+
# time_entry = harvest.time.create(time_entry)
|
32
|
+
|
33
|
+
# entries = harvest.reports.time_by_project(project, Time.parse("04/11/2010"), Time.parse("04/12/2010"))
|
34
|
+
# puts "Entries:"
|
35
|
+
# entries.each {|e| p e}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# This is commented out because it can be very dangerous if run against a production account
|
2
|
+
|
3
|
+
# require "harvested"
|
4
|
+
#
|
5
|
+
# subdomain = 'yoursubdomain'
|
6
|
+
# username = 'yourusername'
|
7
|
+
# password = 'yourpassword'
|
8
|
+
#
|
9
|
+
# api = Harvest.hardy_client(subdomain: subdomain, username: username, password: password)
|
10
|
+
#
|
11
|
+
# raise "Are you sure you want to do this? (comment out this exception if so)"
|
12
|
+
#
|
13
|
+
# api.users.all.each do |u|
|
14
|
+
# api.users.delete(u) if u.email != username
|
15
|
+
# end
|
16
|
+
# my_user = api.users.all.first
|
17
|
+
#
|
18
|
+
# api.reports.time_by_user(my_user, Time.parse('01/01/2000'), Time.now).each do |time|
|
19
|
+
# api.time.delete(time)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# api.reports.expenses_by_user(my_user, Time.parse('01/01/2000'), Time.now).each do |time|
|
23
|
+
# api.expenses.delete(time)
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# %w(expenses expense_categories projects contacts clients tasks).each do |collection|
|
27
|
+
# api.send(collection).all.each {|m| api.send(collection).delete(m) }
|
28
|
+
# end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# # File: project_create_script.rb
|
2
|
+
# # Date Created: 2012-10-08
|
3
|
+
# # Author(s): Mark Rickert (mjar81@gmail.com) / Skookum Digital Works - http://skookum.com
|
4
|
+
# #
|
5
|
+
# # Description: This example script takes user input from the command line and
|
6
|
+
# # creates a project based the selected options. It then assigns tasks from Harvest
|
7
|
+
# # to the project based on an array. After the tasks are added, it addes all the
|
8
|
+
# # currently active users to the project.
|
9
|
+
|
10
|
+
# require "harvested"
|
11
|
+
|
12
|
+
# subdomain = 'yoursubdomain'
|
13
|
+
# username = 'yourusername'
|
14
|
+
# password = 'yourpassword'
|
15
|
+
|
16
|
+
# harvest = Harvest.hardy_client(subdomain: subdomain, username: username, password: password)
|
17
|
+
|
18
|
+
# puts "\nPlease search for a client by typing part of their name:"
|
19
|
+
# cname = gets.chomp
|
20
|
+
|
21
|
+
# # Filter out all the clients that don't match the typed string
|
22
|
+
# client_search_results = harvest.clients.all.select { |c| c.name.downcase.include?(cname) }
|
23
|
+
|
24
|
+
# case client_search_results.length
|
25
|
+
# when 0
|
26
|
+
# puts "No client found. Please try again.\n"
|
27
|
+
# abort
|
28
|
+
# when 1
|
29
|
+
# # Result is exactly 1. We got the client.
|
30
|
+
# client_index = 0
|
31
|
+
# when 1..15
|
32
|
+
# # Have the user select from a list of clients.
|
33
|
+
# puts "Please select from the following results:\n"
|
34
|
+
# client_search_results.each_with_index do |c, i|
|
35
|
+
# puts " #{i+1}. #{c.name}"
|
36
|
+
# end
|
37
|
+
|
38
|
+
# client_index = gets.chomp.to_i - 1
|
39
|
+
# else
|
40
|
+
# puts "Too many client matches. Please try a more specific search term.\n"
|
41
|
+
# abort
|
42
|
+
# end
|
43
|
+
|
44
|
+
# client = client_search_results[client_index]
|
45
|
+
|
46
|
+
# puts "\nClient found: #{client.name}\n"
|
47
|
+
# puts "Please enter the project name:"
|
48
|
+
# project_name = gets.chomp
|
49
|
+
|
50
|
+
# puts "\nIs this project billable? (y/n)"
|
51
|
+
# billable = gets.chomp.downcase == "y"
|
52
|
+
|
53
|
+
# puts "\nAny project notes? (hit 'return' for none)"
|
54
|
+
# notes = gets.chomp
|
55
|
+
|
56
|
+
# puts "\nWhat sorts of tasks does this project need? Type \"p\" for project tasks or \"s\" for sales tasks.\n"
|
57
|
+
# task_types = gets.chomp
|
58
|
+
|
59
|
+
# # Determine what task list to use based on the project type.
|
60
|
+
# if task_types.downcase == "p"
|
61
|
+
# # These are names of tasks that should already exist in Harvest
|
62
|
+
# use_tasks = ["Client Communication", "Planning", "Design", "Development", "Testing/QA", "Documentation", "Deployment"]
|
63
|
+
# else
|
64
|
+
# use_tasks = ["Sales Calls", "Client Meetings", "Travel", "Estimating"]
|
65
|
+
# end
|
66
|
+
|
67
|
+
# # Filter the list of actual tasks we want to add to the project.
|
68
|
+
# tasks = harvest.tasks.all.select { |t| use_tasks.include?(t.name) }
|
69
|
+
|
70
|
+
# # Create the project
|
71
|
+
# puts "Creating new project: \"#{project_name}\" for client: #{client.name}\n"
|
72
|
+
# project = Harvest::Project.new(name: project_name, client_id: client.id, billable: billable, notes: notes)
|
73
|
+
# project = harvest.projects.create(project)
|
74
|
+
|
75
|
+
# # Add all the project tasks to the project
|
76
|
+
# tasks.each do |t|
|
77
|
+
# puts " Adding Task: #{t.name}"
|
78
|
+
# task_assignment = Harvest::TaskAssignment.new(task_id: t.id, project_id: project.id)
|
79
|
+
# task_assignment = harvest.task_assignments.create(task_assignment)
|
80
|
+
# end
|
81
|
+
|
82
|
+
# puts
|
83
|
+
# # Add every active user to the project.
|
84
|
+
# harvest.users.all.each do |u|
|
85
|
+
# next unless u.is_active?
|
86
|
+
|
87
|
+
# puts " Adding User: #{u.first_name} #{u.last_name}"
|
88
|
+
# user_assignment = Harvest::UserAssignment.new(user_id: u.id, project_id: project.id)
|
89
|
+
# harvest.user_assignments.create(user_assignment)
|
90
|
+
# end
|
91
|
+
|
92
|
+
# puts "\nProject successfully created."
|
93
|
+
# puts "You can find the project here: http://#{subdomain}.harvestapp.com/projects/#{project.id}/edit\n\n"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# require "harvested"
|
2
|
+
|
3
|
+
# subdomain = 'yoursubdomain'
|
4
|
+
# username = 'yourusername'
|
5
|
+
# password = 'yourpassword'
|
6
|
+
|
7
|
+
# harvest = Harvest.hardy_client(subdomain: subdomain, username: username, password: password)
|
8
|
+
|
9
|
+
# # Create a Client add a Project to that Client
|
10
|
+
|
11
|
+
# client = Harvest::Client.new(name: 'SuprCorp')
|
12
|
+
# client = harvest.clients.create(client)
|
13
|
+
|
14
|
+
# project = Harvest::Project.new(name: 'SuprGlu', client_id: client.id, notes: 'Some notes about this project')
|
15
|
+
# project = harvest.projects.create(project)
|
16
|
+
|
17
|
+
# # You can create an assign a task in one call
|
18
|
+
# harvest.projects.create_task(project, 'Bottling Glue')
|
19
|
+
# puts "Assigned the task 'Bottling Glue' to the project 'SuprGlu'"
|
20
|
+
|
21
|
+
# # You can explicitly create the task and then assign it
|
22
|
+
# task = Harvest::Task.new(name: 'Packaging Glue', hourly_rate: 30, billable: true)
|
23
|
+
# task = harvest.tasks.create(task)
|
24
|
+
|
25
|
+
# task_assignment = Harvest::TaskAssignment.new(task_id: task.id, project_id: project.id)
|
26
|
+
# task_assignment = harvest.task_assignments.create(task_assignment)
|
27
|
+
# puts "Assigned the task 'Packaging Glue' to the project 'SuprGlu'"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# require "harvested"
|
2
|
+
|
3
|
+
# subdomain = 'yoursubdomain'
|
4
|
+
# username = 'userusername'
|
5
|
+
# password = 'yourpassword'
|
6
|
+
|
7
|
+
# harvest = Harvest.hardy_client(subdomain: subdomain, username: username, password: password)
|
8
|
+
|
9
|
+
# # Create a Client, Project, and a User then assign that User to that Project
|
10
|
+
|
11
|
+
# client = Harvest::Client.new(name: 'SuprCorp')
|
12
|
+
# client = harvest.clients.create(client)
|
13
|
+
|
14
|
+
# project = Harvest::Project.new(name: 'SuprGlu', client_id: client.id, notes: 'Some notes about this project')
|
15
|
+
# project = harvest.projects.create(project)
|
16
|
+
|
17
|
+
# harvest.projects.create_task(project, 'Bottling Glue')
|
18
|
+
|
19
|
+
# user = Harvest::User.new(first_name: 'Jane', last_name: 'Doe', email: 'jane@doe.com', timezone: :est, password: 'secure')
|
20
|
+
# user = harvest.users.create(user)
|
21
|
+
|
22
|
+
# user_assignment = Harvest::UserAssignment.new(user_id: user.id, project_id: project.id)
|
23
|
+
# harvest.user_assignments.create(user_assignment)
|
24
|
+
# puts 'Assigned Jane Doe to the project "SuprGlu"'
|
data/forecasted.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'forecast/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "forecasted"
|
8
|
+
spec.version = Forecast::VERSION
|
9
|
+
spec.authors = ["Weston Platter"]
|
10
|
+
spec.summary = "A Ruby Wrapper for the HarvestForecast (unofficial) API"
|
11
|
+
spec.homepage = "http://github.com/westonplater/forecasted"
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.required_ruby_version = '>= 2.0'
|
20
|
+
|
21
|
+
spec.add_runtime_dependency('httparty')
|
22
|
+
spec.add_runtime_dependency('hashie')
|
23
|
+
spec.add_runtime_dependency('json')
|
24
|
+
spec.add_runtime_dependency('business_time')
|
25
|
+
end
|
data/lib/ext/array.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Shamelessly ripped from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/array/wrap.rb
|
2
|
+
|
3
|
+
unless Array.respond_to?(:wrap)
|
4
|
+
class Array
|
5
|
+
# Wraps its argument in an array unless it is already an array (or array-like).
|
6
|
+
#
|
7
|
+
# Specifically:
|
8
|
+
#
|
9
|
+
# * If the argument is +nil+ an empty list is returned.
|
10
|
+
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
|
11
|
+
# * Otherwise, returns an array with the argument as its single element.
|
12
|
+
#
|
13
|
+
# Array.wrap(nil) # => []
|
14
|
+
# Array.wrap([1, 2, 3]) # => [1, 2, 3]
|
15
|
+
# Array.wrap(0) # => [0]
|
16
|
+
#
|
17
|
+
# This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
|
18
|
+
#
|
19
|
+
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
|
20
|
+
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Arraw.wrap</tt> returns
|
21
|
+
# such a +nil+ right away.
|
22
|
+
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
|
23
|
+
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
|
24
|
+
# * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
|
25
|
+
#
|
26
|
+
# The last point is particularly worth comparing for some enumerables:
|
27
|
+
#
|
28
|
+
# Array(:foo => :bar) # => [[:foo, :bar]]
|
29
|
+
# Array.wrap(:foo => :bar) # => [{:foo => :bar}]
|
30
|
+
#
|
31
|
+
# Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
|
32
|
+
# Array.wrap("foo\nbar") # => ["foo\nbar"]
|
33
|
+
#
|
34
|
+
# There's also a related idiom that uses the splat operator:
|
35
|
+
#
|
36
|
+
# [*object]
|
37
|
+
#
|
38
|
+
# which returns <tt>[nil]</tt> for +nil+, and calls to <tt>Array(object)</tt> otherwise.
|
39
|
+
#
|
40
|
+
# Thus, in this case the behavior is different for +nil+, and the differences with
|
41
|
+
# <tt>Kernel#Array</tt> explained above apply to the rest of +object+s.
|
42
|
+
def self.wrap(object)
|
43
|
+
if object.nil?
|
44
|
+
[]
|
45
|
+
elsif object.respond_to?(:to_ary)
|
46
|
+
object.to_ary
|
47
|
+
else
|
48
|
+
[object]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/ext/date.rb
ADDED
data/lib/ext/hash.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Shamelessly ripped from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/keys.rb
|
2
|
+
|
3
|
+
unless Hash.respond_to?(:stringify_keys)
|
4
|
+
class Hash
|
5
|
+
# Return a new hash with all keys converted to strings.
|
6
|
+
def stringify_keys
|
7
|
+
dup.stringify_keys!
|
8
|
+
end
|
9
|
+
|
10
|
+
def stringify_keys!
|
11
|
+
keys.each do |key|
|
12
|
+
self[key.to_s] = delete(key)
|
13
|
+
end
|
14
|
+
self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/ext/time.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
|
4
|
+
# API Methods to contain all account actions
|
5
|
+
class Account < Base
|
6
|
+
|
7
|
+
# Returns the current rate limit information
|
8
|
+
# @return [Harvest::RateLimitStatus]
|
9
|
+
def rate_limit_status
|
10
|
+
response = request(:get, credentials, '/account/rate_limit_status')
|
11
|
+
Harvest::RateLimitStatus.parse(response.body).first
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the current logged in user
|
15
|
+
# @return [Harvest::User]
|
16
|
+
def who_am_i
|
17
|
+
response = request(:get, credentials, '/account/who_am_i')
|
18
|
+
Harvest::User.parse(response.body).first
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Forecast
|
2
|
+
module API
|
3
|
+
class Aggregates < Base
|
4
|
+
api_model Forecast::Aggregate
|
5
|
+
|
6
|
+
def future_scheduled_hours_after(date_string, query={})
|
7
|
+
url = "#{api_model.api_path}/future_scheduled_hours/#{date_string}"
|
8
|
+
response = request(:get, credentials, url, :query => query)
|
9
|
+
api_model.parse(response.parsed_response)
|
10
|
+
end
|
11
|
+
|
12
|
+
def remaining_budgeted_hours(query={})
|
13
|
+
url = "#{api_model.api_path}/remaining_budgeted_hours"
|
14
|
+
response = request(:get, credentials, url, :query => query)
|
15
|
+
api_model.parse(response.parsed_response)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|