forecasted 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.gitignore +39 -0
  4. data/.rspec +2 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +16 -0
  7. data/Gemfile +19 -0
  8. data/HISTORY.md +19 -0
  9. data/MIT-LICENSE +20 -0
  10. data/README.md +109 -0
  11. data/Rakefile +22 -0
  12. data/examples/basics.rb +35 -0
  13. data/examples/clear_account.rb +28 -0
  14. data/examples/project_create_script.rb +93 -0
  15. data/examples/task_assignments.rb +27 -0
  16. data/examples/user_assignments.rb +24 -0
  17. data/forecasted.gemspec +25 -0
  18. data/lib/ext/array.rb +52 -0
  19. data/lib/ext/date.rb +9 -0
  20. data/lib/ext/hash.rb +17 -0
  21. data/lib/ext/time.rb +5 -0
  22. data/lib/forecast/aggregate.rb +8 -0
  23. data/lib/forecast/api/account.rb +22 -0
  24. data/lib/forecast/api/aggregates.rb +20 -0
  25. data/lib/forecast/api/assignments.rb +63 -0
  26. data/lib/forecast/api/base.rb +68 -0
  27. data/lib/forecast/api/clients.rb +10 -0
  28. data/lib/forecast/api/expense_categories.rb +9 -0
  29. data/lib/forecast/api/expenses.rb +27 -0
  30. data/lib/forecast/api/invoice_categories.rb +26 -0
  31. data/lib/forecast/api/invoice_messages.rb +75 -0
  32. data/lib/forecast/api/invoice_payments.rb +31 -0
  33. data/lib/forecast/api/invoices.rb +35 -0
  34. data/lib/forecast/api/milestones.rb +21 -0
  35. data/lib/forecast/api/projects.rb +23 -0
  36. data/lib/forecast/api/reports.rb +53 -0
  37. data/lib/forecast/api/tasks.rb +36 -0
  38. data/lib/forecast/api/time.rb +48 -0
  39. data/lib/forecast/api/user_assignments.rb +34 -0
  40. data/lib/forecast/api/users.rb +21 -0
  41. data/lib/forecast/assignment.rb +7 -0
  42. data/lib/forecast/base.rb +111 -0
  43. data/lib/forecast/behavior/activatable.rb +31 -0
  44. data/lib/forecast/behavior/crud.rb +75 -0
  45. data/lib/forecast/client.rb +22 -0
  46. data/lib/forecast/credentials.rb +42 -0
  47. data/lib/forecast/errors.rb +26 -0
  48. data/lib/forecast/expense.rb +27 -0
  49. data/lib/forecast/expense_category.rb +10 -0
  50. data/lib/forecast/hardy_client.rb +80 -0
  51. data/lib/forecast/invoice.rb +107 -0
  52. data/lib/forecast/invoice_category.rb +9 -0
  53. data/lib/forecast/invoice_message.rb +8 -0
  54. data/lib/forecast/invoice_payment.rb +8 -0
  55. data/lib/forecast/line_item.rb +4 -0
  56. data/lib/forecast/model.rb +154 -0
  57. data/lib/forecast/project.rb +37 -0
  58. data/lib/forecast/rate_limit_status.rb +23 -0
  59. data/lib/forecast/task.rb +22 -0
  60. data/lib/forecast/time_entry.rb +25 -0
  61. data/lib/forecast/timezones.rb +130 -0
  62. data/lib/forecast/trackable_project.rb +38 -0
  63. data/lib/forecast/user_assignment.rb +30 -0
  64. data/lib/forecast/version.rb +3 -0
  65. data/lib/forecasted.rb +87 -0
  66. data/spec/factories.rb +17 -0
  67. data/spec/forecast/base_spec.rb +11 -0
  68. data/spec/functional/aggregates_spec.rb +64 -0
  69. data/spec/functional/assignments_spec.rb +131 -0
  70. data/spec/functional/errors_spec.rb +22 -0
  71. data/spec/functional/hardy_client_spec.rb +33 -0
  72. data/spec/functional/milestones_spec.rb +82 -0
  73. data/spec/functional/people_spec.rb +85 -0
  74. data/spec/functional/project_spec.rb +41 -0
  75. data/spec/spec_helper.rb +41 -0
  76. data/spec/support/forecast_credentials.example.yml +6 -0
  77. data/spec/support/forecasted_helpers.rb +66 -0
  78. data/spec/support/json_examples.rb +9 -0
  79. 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
@@ -0,0 +1,3 @@
1
+ README.md
2
+ lib/**/*.rb
3
+ MIT-LICENSE
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
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format=documentation
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
+ [![Build Status](https://travis-ci.org/westonplatter/forecasted.svg?branch=master)](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
@@ -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"'
@@ -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
@@ -0,0 +1,9 @@
1
+ # Shamelessly ripped from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/date/conversions.rb
2
+
3
+ unless ::Date.respond_to?(:to_time)
4
+ class ::Date
5
+ def to_time(*)
6
+ ::Time.utc(year, month, day)
7
+ end
8
+ end
9
+ end
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,5 @@
1
+ unless Time.respond_to?(:to_time)
2
+ class Time
3
+ def to_time; self; end
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ module Forecast
2
+
3
+ class Aggregate < Hashie::Mash
4
+ include Forecast::Model
5
+ api_path '/aggregate'
6
+ end
7
+
8
+ end
@@ -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