togglv8-ng 1.4.0
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/.codeclimate.yml +16 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +4 -0
- data/.rdoc_options +23 -0
- data/.rspec +1 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +101 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +160 -0
- data/Rakefile +6 -0
- data/lib/logging.rb +38 -0
- data/lib/reportsv2.rb +177 -0
- data/lib/togglv8/clients.rb +37 -0
- data/lib/togglv8/connection.rb +99 -0
- data/lib/togglv8/dashboard.rb +14 -0
- data/lib/togglv8/project_users.rb +32 -0
- data/lib/togglv8/projects.rb +112 -0
- data/lib/togglv8/tags.rb +25 -0
- data/lib/togglv8/tasks.rb +55 -0
- data/lib/togglv8/time_entries.rb +114 -0
- data/lib/togglv8/togglv8.rb +55 -0
- data/lib/togglv8/users.rb +74 -0
- data/lib/togglv8/version.rb +4 -0
- data/lib/togglv8/workspaces.rb +47 -0
- data/lib/togglv8.rb +13 -0
- data/spec/lib/reportsv2_spec.rb +254 -0
- data/spec/lib/togglv8/clients_spec.rb +145 -0
- data/spec/lib/togglv8/dashboard_spec.rb +31 -0
- data/spec/lib/togglv8/projects_spec.rb +111 -0
- data/spec/lib/togglv8/tags_spec.rb +54 -0
- data/spec/lib/togglv8/tasks_spec.rb +100 -0
- data/spec/lib/togglv8/time_entries_spec.rb +425 -0
- data/spec/lib/togglv8/users_spec.rb +82 -0
- data/spec/lib/togglv8/workspaces_spec.rb +45 -0
- data/spec/lib/togglv8_spec.rb +88 -0
- data/spec/spec_helper.rb +87 -0
- data/spec/togglv8_spec_helper.rb +75 -0
- data/togglv8.gemspec +33 -0
- metadata +223 -0
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- ruby-head
|
4
|
+
- 2.4.0
|
5
|
+
- 2.3.3
|
6
|
+
- 2.2.6
|
7
|
+
- 2.1.10
|
8
|
+
- 2.0.0-p648
|
9
|
+
script: bundle exec rspec --format documentation
|
10
|
+
cache: bundler
|
11
|
+
branches:
|
12
|
+
only:
|
13
|
+
- master
|
14
|
+
addons:
|
15
|
+
code_climate:
|
16
|
+
repo_token: eb44970f9efc497fb68c6ec1ce051dea530776b6a5bbe75e57734625f761ac86
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
Notable changes are documented here following conventions outlined at [Keep a CHANGELOG](http://keepachangelog.com/).
|
4
|
+
|
5
|
+
Changes that are not intended to affect usage (e.g. documentation, specs, removal of dead code, etc.) are generally not documented here.
|
6
|
+
|
7
|
+
Version numbers are meant to adhere to [Semantic Versioning](http://semver.org/).
|
8
|
+
|
9
|
+
|
10
|
+
## [Unreleased]
|
11
|
+
|
12
|
+
|
13
|
+
## [1.2.1] - 2016-07-25
|
14
|
+
### Changed
|
15
|
+
|
16
|
+
* Fix ReportsV2#project by requiring project_id.
|
17
|
+
* Improve spec code coverage.
|
18
|
+
* Include version in `user_agent` and `created_with` (e.g. TogglV8 v1.2.1)
|
19
|
+
|
20
|
+
|
21
|
+
## [1.2.0] - 2016-07-24
|
22
|
+
### Added
|
23
|
+
|
24
|
+
* Add support for [Toggl Reports API v2](https://github.com/toggl/toggl_api_docs/blob/master/reports.md).
|
25
|
+
|
26
|
+
|
27
|
+
## [1.1.0] - 2016-02-22
|
28
|
+
### Added
|
29
|
+
|
30
|
+
* Add `tags(workspace_id)`.
|
31
|
+
|
32
|
+
|
33
|
+
## [1.0.5] - 2016-02-22
|
34
|
+
### Added
|
35
|
+
|
36
|
+
* Add specs for encoding of ISO8601 times with + UTC offset. (See [1.0.4](#104---2016-01-22))
|
37
|
+
|
38
|
+
|
39
|
+
## [1.0.4] - 2016-01-22
|
40
|
+
### Fixed
|
41
|
+
|
42
|
+
* Manually encode `+` to `%2B` before every API call. (Fixes #11)
|
43
|
+
|
44
|
+
|
45
|
+
## [1.0.3] - 2016-01-22
|
46
|
+
### Added
|
47
|
+
|
48
|
+
* Add `debug()` method to enable debugging output including full API response.
|
49
|
+
|
50
|
+
## [1.0.2] - 2015-12-12
|
51
|
+
### Changed
|
52
|
+
|
53
|
+
* Require params 'tags' and 'tag_action' in `update_time_entries_tags()`.
|
54
|
+
|
55
|
+
## [1.0.1] - 2015-12-10
|
56
|
+
### Fixed
|
57
|
+
|
58
|
+
* Fix Toggl API call in `get_project_tasks()`. (Fixes #5)
|
59
|
+
|
60
|
+
### Added
|
61
|
+
|
62
|
+
* Add `my_tasks()`.
|
63
|
+
* Add null checks to various methods.
|
64
|
+
|
65
|
+
### Changed
|
66
|
+
|
67
|
+
* Require params 'name' and 'pid' in `create_task()`.
|
68
|
+
|
69
|
+
## [1.0.0] - 2015-12-06
|
70
|
+
### Added
|
71
|
+
|
72
|
+
* Add `my_deleted_projects()`.
|
73
|
+
|
74
|
+
### Changed
|
75
|
+
|
76
|
+
* Exclude deleted projects from `my_projects()` results.
|
77
|
+
* Change `get_time_entries()` parameters.
|
78
|
+
- old: `start_timestamp=nil, end_timestamp=nil`
|
79
|
+
- new: `dates = {}`
|
80
|
+
* Raise RuntimeError w/ HTTP Status code if request is not successful.
|
81
|
+
* Handle 429 (Too Many Requests) by pausing for 1 second and retrying up to 3 times.
|
82
|
+
- API calls are limited to 1/sec due to toggl.com limits
|
83
|
+
* Refactor duplication out of GET/POST/PUT/DELETE API calls.
|
84
|
+
|
85
|
+
## [0.2.0] - 2015-08-21
|
86
|
+
### Added
|
87
|
+
|
88
|
+
* Add Ruby interface to most functions of [Toggl V8 API](https://github.com/toggl/toggl_api_docs/blob/master/toggl_api.md) (as of 2015-08-21).
|
89
|
+
|
90
|
+
|
91
|
+
[Unreleased]: https://github.com/kanet77/togglv8/compare/v1.2.1...HEAD
|
92
|
+
[1.2.1]: https://github.com/kanet77/togglv8/compare/v1.2.0...v1.2.1
|
93
|
+
[1.2.0]: https://github.com/kanet77/togglv8/compare/v1.1.0...v1.2.0
|
94
|
+
[1.1.0]: https://github.com/kanet77/togglv8/compare/v1.0.5...v1.1.0
|
95
|
+
[1.0.5]: https://github.com/kanet77/togglv8/compare/v1.0.4...v1.0.5
|
96
|
+
[1.0.4]: https://github.com/kanet77/togglv8/compare/v1.0.3...v1.0.4
|
97
|
+
[1.0.3]: https://github.com/kanet77/togglv8/compare/v1.0.2...v1.0.3
|
98
|
+
[1.0.2]: https://github.com/kanet77/togglv8/compare/v1.0.1...v1.0.2
|
99
|
+
[1.0.1]: https://github.com/kanet77/togglv8/compare/v1.0.0...v1.0.1
|
100
|
+
[1.0.0]: https://github.com/kanet77/togglv8/compare/v0.2.0...v1.0.0
|
101
|
+
[0.2.0]: https://github.com/kanet77/togglv8/compare/a1d5cc5...v0.2.0
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013-2015 Tom Kane
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
|
2
|
+
# Toggl API v8
|
3
|
+
|
4
|
+
[](https://badge.fury.io/rb/togglv8) [](https://travis-ci.org/kanet77/togglv8) [](https://coveralls.io/github/kanet77/togglv8?branch=master) [](https://codeclimate.com/github/kanet77/togglv8)
|
5
|
+
|
6
|
+
[Toggl](http://www.toggl.com) is a time tracking tool.
|
7
|
+
|
8
|
+
[togglv8](/) is a Ruby Wrapper for [Toggl API v8](https://github.com/toggl/toggl_api_docs). It is designed to mirror the Toggl API as closely as possible.
|
9
|
+
|
10
|
+
togglv8 supports both [Toggl API](https://github.com/toggl/toggl_api_docs/blob/master/toggl_api.md) and [Reports API](https://github.com/toggl/toggl_api_docs/blob/master/reports.md)
|
11
|
+
|
12
|
+
## Change Log
|
13
|
+
|
14
|
+
See [CHANGELOG](CHANGELOG.md) for a summary of notable changes in each version.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'togglv8'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
$ bundle
|
27
|
+
|
28
|
+
Or install it yourself as:
|
29
|
+
|
30
|
+
$ gem install togglv8
|
31
|
+
|
32
|
+
## Initialization
|
33
|
+
|
34
|
+
### TogglV8::API
|
35
|
+
|
36
|
+
TogglV8::API communicates with [Toggl API v8](https://github.com/toggl/toggl_api_docs/blob/master/toggl_api.md) and can be initialized in one of three ways.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
TogglV8::API.new # reads API token from file ~/.toggl
|
40
|
+
TogglV8::API.new(api_token) # explicit API token
|
41
|
+
TogglV8::API.new(email, password) # email & password
|
42
|
+
```
|
43
|
+
|
44
|
+
### TogglV8::ReportsV2
|
45
|
+
|
46
|
+
TogglV8::ReportsV2 communicates with [Toggl Reports API v2](https://github.com/toggl/toggl_api_docs/blob/master/reports.md) and can be initialized in one of three ways. Toggl.com requires authentication with an API token for Reports API v2.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
TogglV8::ReportsV2.new # reads API token from file ~/.toggl
|
50
|
+
TogglV8::ReportsV2.new(toggl_api_file: toggl_file) # reads API token from toggl_file
|
51
|
+
TogglV8::ReportsV2.new(api_token: api_token) # explicit API token
|
52
|
+
```
|
53
|
+
|
54
|
+
**Note:** `workspace_id` must be set in order to generate reports.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
toggl = TogglV8::API.new
|
58
|
+
reports = TogglV8::ReportsV2.new
|
59
|
+
reports.workspace_id = toggl.workspaces.first['id']
|
60
|
+
```
|
61
|
+
|
62
|
+
## Usage
|
63
|
+
|
64
|
+
This short example shows one way to create a time entry for the first workspace of the user identified by `<API_TOKEN>`. It then generates various reports containing that time entry.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
require 'togglv8'
|
68
|
+
require 'json'
|
69
|
+
|
70
|
+
toggl_api = TogglV8::API.new(<API_TOKEN>)
|
71
|
+
user = toggl_api.me(all=true)
|
72
|
+
workspaces = toggl_api.my_workspaces(user)
|
73
|
+
workspace_id = workspaces.first['id']
|
74
|
+
time_entry = toggl_api.create_time_entry({
|
75
|
+
'description' => "My awesome workspace time entry",
|
76
|
+
'wid' => workspace_id,
|
77
|
+
'duration' => 1200,
|
78
|
+
'start' => toggl_api.iso8601((Time.now - 3600).to_datetime),
|
79
|
+
'created_with' => "My awesome Ruby application"
|
80
|
+
})
|
81
|
+
|
82
|
+
begin
|
83
|
+
reports = TogglV8::ReportsV2.new(api_token: <API_TOKEN>)
|
84
|
+
begin
|
85
|
+
reports.summary
|
86
|
+
rescue Exception => e
|
87
|
+
puts e.message # workspace_id is required
|
88
|
+
end
|
89
|
+
reports.workspace_id = workspace_id
|
90
|
+
summary = reports.summary
|
91
|
+
puts "Generating summary JSON..."
|
92
|
+
puts JSON.pretty_generate(summary)
|
93
|
+
puts "Generating summary PDF..."
|
94
|
+
reports.write_summary('toggl_summary.pdf')
|
95
|
+
puts "Generating weekly CSV..."
|
96
|
+
reports.write_weekly('toggl_weekly.csv')
|
97
|
+
puts "Generating details XLS..."
|
98
|
+
reports.write_details('toggl_details.xls')
|
99
|
+
# Note: toggl.com does not generate Weekly XLS report (as of 2016-07-24)
|
100
|
+
ensure
|
101
|
+
toggl_api.delete_time_entry(time_entry['id'])
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
See specs for more examples.
|
106
|
+
|
107
|
+
**Note:** Requests are rate-limited. The togglv8 gem will handle a 429 response by pausing for 1 second and trying again, for up to 3 attempts. See [Toggl API docs](https://github.com/toggl/toggl_api_docs#the-api-format):
|
108
|
+
|
109
|
+
> For rate limiting we have implemented a Leaky bucket. When a limit has been hit the request will get a HTTP 429 response and it's the task of the client to sleep/wait until bucket is empty. Limits will and can change during time, but a safe window will be 1 request per second. Limiting is applied per api token per IP, meaning two users from the same IP will get their rate allocated separately.
|
110
|
+
|
111
|
+
## Debugging
|
112
|
+
|
113
|
+
The `TogglV8::API#debug` method determines if debug output is printed to STDOUT. This code snippet demonstrates the debug output.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
require 'togglv8'
|
117
|
+
|
118
|
+
toggl = TogglV8::API.new
|
119
|
+
|
120
|
+
toggl.debug(true) # or simply toggl.debug
|
121
|
+
user1 = toggl.me
|
122
|
+
puts "user: #{user1['fullname']}, debug: true"
|
123
|
+
|
124
|
+
puts '-'*80
|
125
|
+
|
126
|
+
toggl.debug(false)
|
127
|
+
user2 = toggl.me
|
128
|
+
puts "user: #{user2['fullname']}, debug: false"
|
129
|
+
```
|
130
|
+
|
131
|
+
## Documentation
|
132
|
+
|
133
|
+
Run `rdoc` to generate documentation. Open `doc/index.html` in your browser.
|
134
|
+
|
135
|
+
Also available on [DocumentUp](https://documentup.com/kanet77/togglv8)
|
136
|
+
|
137
|
+
## Acknowledgements
|
138
|
+
|
139
|
+
- Thanks to the following contributors (in alphabetical order):
|
140
|
+
* [archonic](https://github.com/archonic) ([fork](https://github.com/archonic/togglv8))
|
141
|
+
* [ddiatmb](https://github.com/ddiatmb) ([fork](https://github.com/ddiatmb/togglv8))
|
142
|
+
* [itaymendel](https://github.com/itaymendel)
|
143
|
+
* [ppawlikmb](https://github.com/ppawlikmb) ([fork](https://github.com/ppawlikmb/togglv8))
|
144
|
+
* [worldsmithroy](https://github.com/worldsmithroy) ([fork](https://github.com/worldsmithroy/togglv8))
|
145
|
+
- Thanks to [Koen Van der Auwera](https://github.com/atog) for the [Ruby Wrapper for Toggl API v6](https://github.com/atog/toggl)
|
146
|
+
- Thanks to the Toggl team for exposing the API.
|
147
|
+
|
148
|
+
## Contributing
|
149
|
+
|
150
|
+
1. Fork it ( https://github.com/kanet77/togglv8/fork )
|
151
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
152
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
153
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
154
|
+
5. Create a new Pull Request
|
155
|
+
|
156
|
+
Pull Requests that include tests are **much** more likely to be accepted and merged quickly.
|
157
|
+
|
158
|
+
## License
|
159
|
+
|
160
|
+
Copyright (c) 2013-2016 Tom Kane. Released under the [MIT License](http://opensource.org/licenses/mit-license.php). See [LICENSE.txt](LICENSE.txt) for details.
|
data/Rakefile
ADDED
data/lib/logging.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# :nocov:
|
2
|
+
require 'logger'
|
3
|
+
# require 'awesome_print' # for debug output
|
4
|
+
|
5
|
+
# From http://stackoverflow.com/questions/917566/ruby-share-logger-instance-among-module-classes
|
6
|
+
module Logging
|
7
|
+
class << self
|
8
|
+
def logger
|
9
|
+
@logger ||= Logger.new($stdout)
|
10
|
+
end
|
11
|
+
|
12
|
+
def logger=(logger)
|
13
|
+
@logger = logger
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Addition
|
18
|
+
def self.included(base)
|
19
|
+
class << base
|
20
|
+
def logger
|
21
|
+
Logging.logger
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def logger
|
27
|
+
Logging.logger
|
28
|
+
end
|
29
|
+
|
30
|
+
def debug(debug=true)
|
31
|
+
if debug
|
32
|
+
logger.level = Logger::DEBUG
|
33
|
+
else
|
34
|
+
logger.level = Logger::WARN
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
# :nocov:
|
data/lib/reportsv2.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
module TogglV8
|
2
|
+
TOGGL_REPORTS_URL = 'https://api.track.toggl.com/reports/api/v2'
|
3
|
+
|
4
|
+
class ReportsV2
|
5
|
+
include TogglV8::Connection
|
6
|
+
|
7
|
+
REPORTS_V2_URL = TOGGL_REPORTS_URL + 'v2/'
|
8
|
+
|
9
|
+
attr_reader :conn
|
10
|
+
|
11
|
+
attr_accessor :workspace_id
|
12
|
+
|
13
|
+
def initialize(opts={})
|
14
|
+
debug(false)
|
15
|
+
|
16
|
+
@user_agent = TogglV8::NAME
|
17
|
+
|
18
|
+
username = opts[:api_token]
|
19
|
+
if username.nil?
|
20
|
+
toggl_api_file = opts[:toggl_api_file] || File.join(Dir.home, TOGGL_FILE)
|
21
|
+
if File.exist?(toggl_api_file) then
|
22
|
+
username = IO.read(toggl_api_file)
|
23
|
+
else
|
24
|
+
raise "Expecting one of:\n" +
|
25
|
+
" 1) api_token in file #{toggl_api_file}, or\n" +
|
26
|
+
" 2) parameter: (toggl_api_file), or\n" +
|
27
|
+
" 3) parameter: (api_token), or\n" +
|
28
|
+
"\n\tSee https://github.com/kanet77/togglv8#togglv8reportsv2" +
|
29
|
+
"\n\tand https://github.com/toggl/toggl_api_docs/blob/master/reports.md#authentication"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@conn = TogglV8::Connection.open(username, API_TOKEN, REPORTS_V2_URL, opts)
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# ---------
|
38
|
+
# :section: Report
|
39
|
+
#
|
40
|
+
# The following parameters and filters can be used in all of the reports
|
41
|
+
#
|
42
|
+
# user_agent : the name of this application so Toggl can get in touch
|
43
|
+
# (string, *required*)
|
44
|
+
# workspace_id : The workspace whose data you want to access.
|
45
|
+
# (integer, *required*)
|
46
|
+
# since : ISO 8601 date (YYYY-MM-DD), by default until - 6 days.
|
47
|
+
# (string)
|
48
|
+
# until : ISO 8601 date (YYYY-MM-DD), by default today
|
49
|
+
# (string)
|
50
|
+
# billable : possible values: yes/no/both, default both
|
51
|
+
# client_ids : client ids separated by a comma, 0 if you want to filter out time entries without a client
|
52
|
+
# project_ids : project ids separated by a comma, 0 if you want to filter out time entries without a project
|
53
|
+
# user_ids : user ids separated by a comma
|
54
|
+
# members_of_group_ids : group ids separated by a comma. This limits provided user_ids to the provided group members
|
55
|
+
# or_members_of_group_ids : group ids separated by a comma. This extends provided user_ids with the provided group members
|
56
|
+
# tag_ids : tag ids separated by a comma, 0 if you want to filter out time entries without a tag
|
57
|
+
# task_ids : task ids separated by a comma, 0 if you want to filter out time entries without a task
|
58
|
+
# time_entry_ids : time entry ids separated by a comma
|
59
|
+
# description : time entry description
|
60
|
+
# (string)
|
61
|
+
# without_description : filters out the time entries which do not have a description ('(no description)')
|
62
|
+
# (true/false)
|
63
|
+
# order_field : date/description/duration/user in detailed reports
|
64
|
+
# title/duration/amount in summary reports
|
65
|
+
# title/day1/day2/day3/day4/day5/day6/day7/week_total in weekly report
|
66
|
+
# order_desc : on for descending and off for ascending order
|
67
|
+
# (on/off)
|
68
|
+
# distinct_rates : on/off, default off
|
69
|
+
# rounding : on/off, default off, rounds time according to workspace settings
|
70
|
+
# display_hours : decimal/minutes, display hours with minutes or as a decimal number, default minutes
|
71
|
+
#
|
72
|
+
# NB! Maximum date span (until - since) is one year.
|
73
|
+
|
74
|
+
# extension can be one of ['.pdf', '.csv', '.xls']. Possibly others?
|
75
|
+
def report(type, extension, params)
|
76
|
+
raise "workspace_id is required" if @workspace_id.nil?
|
77
|
+
get "#{type}#{extension}", {
|
78
|
+
:'user_agent' => @user_agent,
|
79
|
+
:'workspace_id' => @workspace_id,
|
80
|
+
}.merge(params)
|
81
|
+
end
|
82
|
+
|
83
|
+
def weekly(extension='', params={})
|
84
|
+
report('weekly', extension, params)
|
85
|
+
end
|
86
|
+
|
87
|
+
def details(extension='', params={})
|
88
|
+
report('details', extension, params)
|
89
|
+
end
|
90
|
+
|
91
|
+
def summary(extension='', params={})
|
92
|
+
report('summary', extension, params)
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# ---------
|
97
|
+
# :section: Write report to file
|
98
|
+
#
|
99
|
+
def write_report(filename)
|
100
|
+
extension = File.extname(filename)
|
101
|
+
report = yield(extension)
|
102
|
+
File.open(filename, "wb") do |file|
|
103
|
+
file.write(report)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def write_weekly(filename, params={})
|
108
|
+
write_report(filename) do |extension|
|
109
|
+
weekly(extension, params)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def write_details(filename, params={})
|
114
|
+
write_report(filename) do |extension|
|
115
|
+
details(extension, params)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def write_summary(filename, params={})
|
120
|
+
write_report(filename) do |extension|
|
121
|
+
summary(extension, params)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
##
|
127
|
+
# ---------
|
128
|
+
# :section: Miscellaneous information
|
129
|
+
#
|
130
|
+
def revision
|
131
|
+
get "revision"
|
132
|
+
end
|
133
|
+
|
134
|
+
def index
|
135
|
+
get "index"
|
136
|
+
end
|
137
|
+
|
138
|
+
def env
|
139
|
+
get "env"
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# ---------
|
144
|
+
# :section: Project Dashboard
|
145
|
+
#
|
146
|
+
# Project dashboard returns at-a-glance information for a single project.
|
147
|
+
# This feature is only available with Toggl pro.
|
148
|
+
#
|
149
|
+
# user_agent : email, or other way to contact client application developer
|
150
|
+
# (string, *required*)
|
151
|
+
# workspace_id : The workspace whose data you want to access
|
152
|
+
# (integer, *required*)
|
153
|
+
# project_id : The project whose data you want to access
|
154
|
+
# (integer, *required*)
|
155
|
+
# page : number of 'tasks_page' you want to fetch
|
156
|
+
# (integer, optional)
|
157
|
+
# order_field string : name/assignee/duration/billable_amount/estimated_seconds
|
158
|
+
# order_desc string : on/off, on for descending and off for ascending order
|
159
|
+
def project(project_id, params={})
|
160
|
+
raise "workspace_id is required" if @workspace_id.nil?
|
161
|
+
get "project", {
|
162
|
+
:'user_agent' => @user_agent,
|
163
|
+
:'workspace_id' => @workspace_id,
|
164
|
+
:'project_id' => project_id,
|
165
|
+
}.merge(params)
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# ---------
|
170
|
+
# :section: Error (for testing)
|
171
|
+
#
|
172
|
+
# excludes endpoints 'error500' and 'division_by_zero_error'
|
173
|
+
def error400
|
174
|
+
get "error400"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module TogglV8
|
2
|
+
class API
|
3
|
+
|
4
|
+
##
|
5
|
+
# ---------
|
6
|
+
# :section: Clients
|
7
|
+
#
|
8
|
+
# name : The name of the client (string, required, unique in workspace)
|
9
|
+
# wid : workspace ID, where the client will be used (integer, required)
|
10
|
+
# notes : Notes for the client (string, not required)
|
11
|
+
# hrate : The hourly rate for this client (float, not required, available only for pro workspaces)
|
12
|
+
# cur : The name of the client's currency (string, not required, available only for pro workspaces)
|
13
|
+
# at : timestamp that is sent in the response, indicates the time client was last updated
|
14
|
+
|
15
|
+
def create_client(params)
|
16
|
+
requireParams(params, ['name', 'wid'])
|
17
|
+
post "clients", { 'client' => params }
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_client(client_id)
|
21
|
+
get "clients/#{client_id}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_client(client_id, params)
|
25
|
+
put "clients/#{client_id}", { 'client' => params }
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete_client(client_id)
|
29
|
+
delete "clients/#{client_id}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_client_projects(client_id, params={})
|
33
|
+
active = params.has_key?('active') ? "?active=#{params['active']}" : ""
|
34
|
+
get "clients/#{client_id}/projects#{active}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'oj'
|
3
|
+
|
4
|
+
require_relative '../logging'
|
5
|
+
|
6
|
+
module TogglV8
|
7
|
+
module Connection
|
8
|
+
include Logging
|
9
|
+
|
10
|
+
DELAY_SEC = 1
|
11
|
+
MAX_RETRIES = 3
|
12
|
+
|
13
|
+
API_TOKEN = 'api_token'
|
14
|
+
TOGGL_FILE = '.toggl'
|
15
|
+
|
16
|
+
def self.open(username=nil, password=API_TOKEN, url=nil, opts={})
|
17
|
+
raise 'Missing URL' if url.nil?
|
18
|
+
|
19
|
+
Faraday.new(:url => url, :ssl => {:verify => true}) do |faraday|
|
20
|
+
faraday.adapter Faraday.default_adapter
|
21
|
+
faraday.request :url_encoded
|
22
|
+
faraday.response :logger, Logger.new('faraday.log') if opts[:log]
|
23
|
+
faraday.headers = { "Content-Type" => "application/json" }
|
24
|
+
faraday.request :authorization, :basic, username, password
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def requireParams(params, fields=[])
|
29
|
+
raise ArgumentError, 'params is not a Hash' unless params.is_a? Hash
|
30
|
+
return if fields.empty?
|
31
|
+
errors = []
|
32
|
+
for f in fields
|
33
|
+
errors.push("params[#{f}] is required") unless params.has_key?(f)
|
34
|
+
end
|
35
|
+
raise ArgumentError, errors.join(', ') if !errors.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def _call_api(procs)
|
39
|
+
# logger.debug(procs[:debug_output].call)
|
40
|
+
full_resp = nil
|
41
|
+
i = 0
|
42
|
+
loop do
|
43
|
+
i += 1
|
44
|
+
full_resp = procs[:api_call].call
|
45
|
+
# logger.ap(full_resp.env, :debug)
|
46
|
+
break if full_resp.status != 429 || i >= MAX_RETRIES
|
47
|
+
sleep(DELAY_SEC)
|
48
|
+
end
|
49
|
+
|
50
|
+
raise full_resp.headers['warning'] if full_resp.headers['warning'] && !full_resp.headers['warning'].match?(/Deprecation Notice\. Toggl Track API wont be available in this domain after June 2021/)
|
51
|
+
raise "HTTP Status: #{full_resp.status}" unless full_resp.success?
|
52
|
+
return {} if full_resp.body.nil? || full_resp.body == 'null'
|
53
|
+
|
54
|
+
full_resp
|
55
|
+
end
|
56
|
+
|
57
|
+
def get(resource, params={})
|
58
|
+
query_params = params.map { |k,v| "#{k}=#{v}" }.join('&')
|
59
|
+
resource += "?#{query_params}" unless query_params.empty?
|
60
|
+
resource.gsub!('+', '%2B')
|
61
|
+
full_resp = _call_api(debug_output: lambda { "GET #{resource}" },
|
62
|
+
api_call: lambda { self.conn.get(resource) } )
|
63
|
+
return {} if full_resp == {}
|
64
|
+
begin
|
65
|
+
resp = Oj.load(full_resp.body)
|
66
|
+
return resp['data'] if resp.respond_to?(:has_key?) && resp.has_key?('data')
|
67
|
+
return resp
|
68
|
+
rescue Oj::ParseError
|
69
|
+
return full_resp.body
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def post(resource, data='')
|
74
|
+
resource.gsub!('+', '%2B')
|
75
|
+
full_resp = _call_api(debug_output: lambda { "POST #{resource} / #{data}" },
|
76
|
+
api_call: lambda { self.conn.post(resource, Oj.dump(data)) } )
|
77
|
+
return {} if full_resp == {}
|
78
|
+
resp = Oj.load(full_resp.body)
|
79
|
+
resp['data']
|
80
|
+
end
|
81
|
+
|
82
|
+
def put(resource, data='')
|
83
|
+
resource.gsub!('+', '%2B')
|
84
|
+
full_resp = _call_api(debug_output: lambda { "PUT #{resource} / #{data}" },
|
85
|
+
api_call: lambda { self.conn.put(resource, Oj.dump(data)) } )
|
86
|
+
return {} if full_resp == {}
|
87
|
+
resp = Oj.load(full_resp.body)
|
88
|
+
resp['data']
|
89
|
+
end
|
90
|
+
|
91
|
+
def delete(resource)
|
92
|
+
resource.gsub!('+', '%2B')
|
93
|
+
full_resp = _call_api(debug_output: lambda { "DELETE #{resource}" },
|
94
|
+
api_call: lambda { self.conn.delete(resource) } )
|
95
|
+
return {} if full_resp == {}
|
96
|
+
full_resp.body
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|