togglv8-tfl 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.
@@ -0,0 +1,55 @@
1
+ require_relative 'clients'
2
+ require_relative 'dashboard'
3
+ require_relative 'project_users'
4
+ require_relative 'projects'
5
+ require_relative 'tags'
6
+ require_relative 'tasks'
7
+ require_relative 'time_entries'
8
+ require_relative 'users'
9
+ require_relative 'version'
10
+ require_relative 'workspaces'
11
+
12
+ module TogglV8
13
+ TOGGL_API_URL = 'https://www.toggl.com/api/'
14
+
15
+ class API
16
+ include TogglV8::Connection
17
+
18
+ TOGGL_API_V8_URL = TOGGL_API_URL + 'v8/'
19
+
20
+ attr_reader :conn
21
+
22
+ def initialize(username=nil, password=API_TOKEN, opts={})
23
+ debug(false)
24
+ if username.nil? && password == API_TOKEN
25
+ toggl_api_file = File.join(Dir.home, TOGGL_FILE)
26
+ # logger.debug("toggl_api_file = #{toggl_api_file}")
27
+ if File.exist?(toggl_api_file) then
28
+ username = IO.read(toggl_api_file)
29
+ else
30
+ raise "Expecting one of:\n" +
31
+ " 1) api_token in file #{toggl_api_file}, or\n" +
32
+ " 2) parameter: (api_token), or\n" +
33
+ " 3) parameters: (username, password).\n" +
34
+ "\n\tSee https://github.com/kanet77/togglv8#togglv8api" +
35
+ "\n\tand https://github.com/toggl/toggl_api_docs/blob/master/chapters/authentication.md"
36
+ end
37
+ end
38
+
39
+ @conn = TogglV8::Connection.open(username, password,
40
+ TOGGL_API_V8_URL, opts)
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
@@ -0,0 +1,74 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Users
7
+ #
8
+ # api_token : (string)
9
+ # default_wid : default workspace id (integer)
10
+ # email : (string)
11
+ # jquery_timeofday_format : (string)
12
+ # jquery_date_format : (string)
13
+ # timeofday_format : (string)
14
+ # date_format : (string)
15
+ # store_start_and_stop_time : whether start and stop time are saved on time entry (boolean)
16
+ # beginning_of_week : (integer, Sunday=0)
17
+ # language : user's language (string)
18
+ # image_url : url with the user's profile picture(string)
19
+ # sidebar_piechart : should a piechart be shown on the sidebar (boolean)
20
+ # at : timestamp of last changes
21
+ # new_blog_post : an object with toggl blog post title and link
22
+
23
+ def me(all=nil)
24
+ # NOTE: response['since'] is discarded because it is outside response['data']
25
+ # (See TogglV8::API#get in lib/togglv8.rb)
26
+ get "me%s" % [all.nil? ? "" : "?with_related_data=#{all}"]
27
+ end
28
+
29
+ def my_clients(user=nil)
30
+ user = me(all=true) if user.nil?
31
+ user['clients'] || {}
32
+ end
33
+
34
+ def my_projects(user=nil)
35
+ user = me(all=true) if user.nil?
36
+ return {} unless user['projects']
37
+ projects = user['projects']
38
+ projects.delete_if { |p| p['server_deleted_at'] }
39
+ end
40
+
41
+ def my_deleted_projects(user=nil)
42
+ user = me(all=true) if user.nil?
43
+ return {} unless user['projects']
44
+ projects = user['projects']
45
+ projects.keep_if { |p| p['server_deleted_at'] }
46
+ end
47
+
48
+ def my_tags(user=nil)
49
+ user = me(all=true) if user.nil?
50
+ user['tags'] || {}
51
+ end
52
+
53
+ def my_tasks(user=nil)
54
+ user = me(all=true) if user.nil?
55
+ user['tasks'] || {}
56
+ end
57
+
58
+ def my_time_entries(user=nil)
59
+ user = me(all=true) if user.nil?
60
+ user['time_entries'] || {}
61
+ end
62
+
63
+ def my_workspaces(user=nil)
64
+ user = me(all=true) if user.nil?
65
+ user['workspaces'] || {}
66
+ end
67
+
68
+ def create_user(params)
69
+ params['created_with'] = TogglV8::NAME unless params.has_key?('created_with')
70
+ requireParams(params, ['email', 'password', 'timezone', 'created_with'])
71
+ post "signups", { 'user' => params }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,4 @@
1
+ module TogglV8
2
+ # :section:
3
+ VERSION = "0.0.1"
4
+ end
@@ -0,0 +1,47 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Workspaces
7
+ #
8
+ # name : (string, required)
9
+ # premium : If it's a pro workspace or not.
10
+ # Shows if someone is paying for the workspace or not (boolean, not required)
11
+ # at : timestamp that is sent in the response, indicates the time item was last updated
12
+
13
+ def workspaces
14
+ get "workspaces"
15
+ end
16
+
17
+ def clients(workspace_id=nil)
18
+ if workspace_id.nil?
19
+ get "clients"
20
+ else
21
+ get "workspaces/#{workspace_id}/clients"
22
+ end
23
+ end
24
+
25
+ def projects(workspace_id, params={})
26
+ active = params.has_key?(:active) ? "?active=#{params[:active]}" : ""
27
+ get "workspaces/#{workspace_id}/projects#{active}"
28
+ end
29
+
30
+ def users(workspace_id)
31
+ get "workspaces/#{workspace_id}/users"
32
+ end
33
+
34
+ def tasks(workspace_id, params={})
35
+ active = params.has_key?(:active) ? "?active=#{params[:active]}" : ""
36
+ get "workspaces/#{workspace_id}/tasks#{active}"
37
+ end
38
+
39
+ def tags(workspace_id)
40
+ get "workspaces/#{workspace_id}/tags"
41
+ end
42
+
43
+ def leave_workspace(workspace_id)
44
+ delete "workspaces/#{workspace_id}/leave"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,254 @@
1
+ require 'fileutils'
2
+
3
+ describe 'ReportsV2' do
4
+ it 'initializes with api_token' do
5
+ reports = TogglV8::ReportsV2.new(api_token: Testing::API_TOKEN)
6
+ env = reports.env
7
+ expect(env).to_not be nil
8
+ expect(env['user']['api_token']).to eq Testing::API_TOKEN
9
+ end
10
+
11
+ it 'does not initialize with bogus api_token' do
12
+ reports = TogglV8::ReportsV2.new(api_token: '4880nqor1orr9n241sn08070q33oq49s')
13
+ expect { reports.env }.to raise_error(RuntimeError, "HTTP Status: 401")
14
+ end
15
+
16
+ context '.toggl file' do
17
+ before :each do
18
+ @tmp_home = mktemp_dir
19
+ @original_home = Dir.home
20
+ ENV['HOME'] = @tmp_home
21
+ end
22
+
23
+ after :each do
24
+ FileUtils.rm_rf(@tmp_home)
25
+ ENV['HOME'] = @original_home
26
+ end
27
+
28
+ it 'initializes with .toggl file' do
29
+ toggl_file = File.join(@tmp_home, '.toggl')
30
+ File.open(toggl_file, 'w') { |file| file.write(Testing::API_TOKEN) }
31
+
32
+ reports = TogglV8::ReportsV2.new
33
+ env = reports.env
34
+ expect(env).to_not be nil
35
+ expect(env['user']['api_token']).to eq Testing::API_TOKEN
36
+ end
37
+
38
+ it 'initializes with custom toggl file' do
39
+ toggl_file = File.join(@tmp_home, 'my_toggl')
40
+ File.open(toggl_file, 'w') { |file| file.write(Testing::API_TOKEN) }
41
+
42
+ reports = TogglV8::ReportsV2.new(toggl_api_file: toggl_file)
43
+ env = reports.env
44
+ expect(env).to_not be nil
45
+ expect(env['user']['api_token']).to eq Testing::API_TOKEN
46
+ end
47
+
48
+ it 'raises error if .toggl file is missing' do
49
+ expect{ reports = TogglV8::ReportsV2.new }.to raise_error(RuntimeError)
50
+ end
51
+ end
52
+
53
+ context 'handles errors' do
54
+ before :all do
55
+ @reports = TogglV8::ReportsV2.new(api_token: Testing::API_TOKEN)
56
+ @reports.workspace_id = @workspace_id
57
+ end
58
+
59
+ it 'surfaces a Warning HTTP header in case of 400 error' do
60
+ # https://github.com/toggl/toggl_api_docs/blob/master/reports.md#failed-requests
61
+ expect { @reports.error400 }.to raise_error(RuntimeError,
62
+ "This URL is intended only for testing")
63
+ end
64
+
65
+ it 'retries a request up to 3 times if a 429 is received' do
66
+ expect(@reports.conn).to receive(:get).exactly(3).times.and_return(
67
+ MockResponse.new(429, {}, 'body'))
68
+ expect { @reports.env }.to raise_error(RuntimeError, "HTTP Status: 429")
69
+ end
70
+
71
+ it 'retries a request after 429' do
72
+ expect(@reports.conn).to receive(:get).twice.and_return(
73
+ MockResponse.new(429, {}, 'body'),
74
+ MockResponse.new(200, {}, 'rev1.2.3'))
75
+ expect(@reports.revision).to eq('rev1.2.3')
76
+ end
77
+ end
78
+
79
+ context 'miscellaneous' do
80
+ it 'env returns environment' do
81
+ reports = TogglV8::ReportsV2.new(api_token: Testing::API_TOKEN)
82
+ reports.workspace_id = @workspace_id
83
+ env = reports.env
84
+ expect(env['workspace']).to_not be nil
85
+ expect(env['user']).to_not be nil
86
+ expect(env['user']['id']).to eq Testing::USER_ID
87
+ end
88
+
89
+ it 'index returns endpoints' do
90
+ reports = TogglV8::ReportsV2.new(api_token: Testing::API_TOKEN)
91
+ reports.workspace_id = @workspace_id
92
+ index = reports.index
93
+ expect(index['Welcome to reports api V2. VALID requests are:']).to_not be nil
94
+ end
95
+
96
+ it 'revision has not changed' do
97
+ reports = TogglV8::ReportsV2.new(api_token: Testing::API_TOKEN)
98
+ reports.workspace_id = @workspace_id
99
+ expect(reports.revision).to eq "0.0.38\n-8a007ca"
100
+ end
101
+ end
102
+
103
+ context 'project', :pro_account do
104
+ before :all do
105
+ @toggl = TogglV8::API.new(Testing::API_TOKEN)
106
+ @project_name = "Project #{Time.now.iso8601}"
107
+ @project = @toggl.create_project({
108
+ 'name' => @project_name,
109
+ 'wid' => @workspace_id
110
+ })
111
+ end
112
+
113
+ after :all do
114
+ @toggl.delete_project(@project['id'])
115
+ end
116
+
117
+ it 'dashboard' do
118
+ reports = TogglV8::ReportsV2.new(api_token: Testing::API_TOKEN)
119
+ reports.workspace_id = @toggl.workspaces.first['id']
120
+ project_dashboard = reports.project(@project['id'])
121
+
122
+ expect(project_dashboard['name']).to eq @project_name
123
+ end
124
+ end
125
+
126
+ context 'blank reports' do
127
+ before :all do
128
+ @toggl = TogglV8::API.new(Testing::API_TOKEN)
129
+ @workspaces = @toggl.workspaces
130
+ @workspace_id = @workspaces.first['id']
131
+ @reports = TogglV8::ReportsV2.new(api_token: Testing::API_TOKEN)
132
+ @reports.workspace_id = @workspace_id
133
+ end
134
+
135
+ it 'summary' do
136
+ expect(@reports.summary).to eq []
137
+ end
138
+
139
+ it 'weekly' do
140
+ expect(@reports.weekly).to eq []
141
+ end
142
+
143
+ it 'details' do
144
+ expect(@reports.details).to eq []
145
+ end
146
+ end
147
+
148
+ context 'reports' do
149
+ before :all do
150
+ @toggl = TogglV8::API.new(Testing::API_TOKEN)
151
+ @workspaces = @toggl.workspaces
152
+ @workspace_id = @workspaces.first['id']
153
+ time_entry_info = {
154
+ 'wid' => @workspace_id,
155
+ 'start' => @toggl.iso8601(DateTime.now),
156
+ 'duration' => 77
157
+ }
158
+
159
+ @time_entry = @toggl.create_time_entry(time_entry_info)
160
+
161
+ @reports = TogglV8::ReportsV2.new(api_token: Testing::API_TOKEN)
162
+ @reports.workspace_id = @workspace_id
163
+
164
+ @tmp_home = mktemp_dir
165
+ @original_home = Dir.home
166
+ ENV['HOME'] = @tmp_home
167
+ end
168
+
169
+ after :all do
170
+ @toggl.delete_time_entry(@time_entry['id'])
171
+
172
+ FileUtils.rm_rf(@tmp_home)
173
+ ENV['HOME'] = @original_home
174
+ end
175
+
176
+ context 'JSON reports' do
177
+ it 'summary' do
178
+ summary = @reports.summary
179
+ expect(summary.length).to eq 1
180
+ expect(summary.first['time']).to eq 77000
181
+ expect(summary.first['items'].length).to eq 1
182
+ expect(summary.first['items'].first['time']).to eq 77000
183
+ end
184
+
185
+ it 'weekly' do
186
+ weekly = @reports.weekly
187
+ expect(weekly.length).to eq 1
188
+ expect(weekly.first['details'].first['title']['user']).to eq Testing::USERNAME
189
+ expect(weekly.first['totals'][7]).to eq 77000
190
+ end
191
+
192
+ it 'details' do
193
+ details = @reports.details
194
+ expect(details.length).to eq 1
195
+ expect(details.first['user']).to eq Testing::USERNAME
196
+ expect(details.first['dur']).to eq 77000
197
+ end
198
+ end
199
+
200
+ context 'CSV reports' do
201
+ it 'summary' do
202
+ filename = File.join(@tmp_home, 'summary.csv')
203
+ summary = @reports.write_summary(filename)
204
+ expect(file_contains(filename, /00:01:17/))
205
+ end
206
+
207
+ it 'weekly' do
208
+ filename = File.join(@tmp_home, 'weekly.csv')
209
+ weekly = @reports.write_weekly(filename)
210
+ expect(file_contains(filename, /00:01:17/))
211
+ end
212
+
213
+ it 'details' do
214
+ filename = File.join(@tmp_home, 'details.csv')
215
+ details = @reports.write_details(filename)
216
+ expect(file_contains(filename, /00:01:17/))
217
+ end
218
+ end
219
+
220
+ context 'PDF reports' do
221
+ it 'summary' do
222
+ filename = File.join(@tmp_home, 'summary.pdf')
223
+ summary = @reports.write_summary(filename)
224
+ expect(file_is_pdf(filename))
225
+ end
226
+
227
+ it 'weekly' do
228
+ filename = File.join(@tmp_home, 'weekly.pdf')
229
+ weekly = @reports.write_weekly(filename)
230
+ expect(file_is_pdf(filename))
231
+ end
232
+
233
+ it 'details' do
234
+ filename = File.join(@tmp_home, 'details.pdf')
235
+ details = @reports.write_details(filename)
236
+ expect(file_is_pdf(filename))
237
+ end
238
+ end
239
+
240
+ context 'XLS reports' do
241
+ it 'summary' do
242
+ filename = File.join(@tmp_home, 'summary.xls')
243
+ summary = @reports.write_summary(filename)
244
+ expect(file_is_xls(filename))
245
+ end
246
+
247
+ it 'details' do
248
+ filename = File.join(@tmp_home, 'details.xls')
249
+ details = @reports.write_details(filename)
250
+ expect(file_is_xls(filename))
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,145 @@
1
+ describe 'Clients' do
2
+ before :all do
3
+ @toggl = TogglV8::API.new(Testing::API_TOKEN)
4
+ @workspaces = @toggl.workspaces
5
+ @workspace_id = @workspaces.first['id']
6
+ end
7
+
8
+ it 'gets {} if there are no clients' do
9
+ client = @toggl.clients
10
+ expect(client).to be_empty
11
+ end
12
+
13
+ it 'gets {} if there are no workspace clients' do
14
+ client = @toggl.clients(@workspace_id)
15
+ expect(client).to be_empty
16
+ end
17
+
18
+ context 'new client' do
19
+ before :all do
20
+ @client = @toggl.create_client({ 'name' => 'new client +1', 'wid' => @workspace_id })
21
+ client_ids = @toggl.my_clients.map { |c| c['id'] }
22
+ expect(client_ids).to eq [ @client['id'] ]
23
+ end
24
+
25
+ after :all do
26
+ TogglV8SpecHelper.delete_all_clients(@toggl)
27
+ clients = @toggl.my_clients
28
+ expect(clients).to be_empty
29
+ end
30
+
31
+ it 'gets a client' do
32
+ client_ids = @toggl.clients.map { |c| c['id'] }
33
+ expect(client_ids).to eq [ @client['id'] ]
34
+ end
35
+
36
+ it 'gets a workspace client' do
37
+ client_ids = @toggl.clients(@workspace_id).map { |c| c['id'] }
38
+ expect(client_ids).to eq [ @client['id'] ]
39
+ end
40
+
41
+ context 'multiple clients' do
42
+ before :all do
43
+ @client2 = @toggl.create_client({ 'name' => 'new client 2', 'wid' => @workspace_id })
44
+ end
45
+
46
+ after :all do
47
+ @toggl.delete_client(@client2['id'])
48
+ end
49
+
50
+ it 'gets clients' do
51
+ client_ids = @toggl.clients.map { |c| c['id'] }
52
+ expect(client_ids).to match_array [ @client['id'], @client2['id'] ]
53
+ end
54
+
55
+ it 'gets workspace clients' do
56
+ client_ids = @toggl.clients(@workspace_id).map { |c| c['id'] }
57
+ expect(client_ids).to match_array [ @client['id'], @client2['id'] ]
58
+ end
59
+ end
60
+
61
+ it 'creates a client' do
62
+ expect(@client).to_not be nil
63
+ expect(@client['name']).to eq 'new client +1'
64
+ expect(@client['notes']).to eq nil
65
+ expect(@client['wid']).to eq @workspace_id
66
+ end
67
+
68
+ it 'gets client data' do
69
+ client = @toggl.get_client(@client['id'])
70
+ expect(client).to_not be nil
71
+ expect(client['name']).to eq @client['name']
72
+ expect(client['wid']).to eq @client['wid']
73
+ expect(client['notes']).to eq @client['notes']
74
+ expect(client['at']).to_not be nil
75
+ end
76
+
77
+ context 'client projects' do
78
+ it 'gets {} if there are no client projects' do
79
+ projects = @toggl.get_client_projects(@client['id'])
80
+ expect(projects).to be_empty
81
+ end
82
+
83
+ context 'new client projects' do
84
+ before :all do
85
+ @project = @toggl.create_project({ 'name' => 'project', 'wid' => @workspace_id, 'cid' => @client['id'] })
86
+ end
87
+
88
+ after :all do
89
+ TogglV8SpecHelper.delete_all_projects(@toggl)
90
+ end
91
+
92
+ it 'gets a client project' do
93
+ projects = @toggl.get_client_projects(@client['id'])
94
+ project_ids = projects.map { |p| p['id'] }
95
+ expect(project_ids).to eq [ @project['id'] ]
96
+ end
97
+
98
+ it 'gets multiple client projects' do
99
+ project2 = @toggl.create_project({ 'name' => 'project2', 'wid' => @workspace_id, 'cid' => @client['id'] })
100
+
101
+ projects = @toggl.get_client_projects(@client['id'])
102
+ project_ids = projects.map { |p| p['id'] }
103
+ expect(project_ids).to match_array [ @project['id'], project2['id'] ]
104
+
105
+ @toggl.delete_project(project2['id'])
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ context 'updated client' do
112
+ before :each do
113
+ @client = @toggl.create_client({ 'name' => 'client to update', 'wid' => @workspace_id })
114
+ end
115
+
116
+ after :each do
117
+ @toggl.delete_client(@client['id'])
118
+ end
119
+
120
+ it 'updates client data' do
121
+ new_values = {
122
+ 'name' => 'CLIENT-NEW',
123
+ 'notes' => 'NOTES-NEW',
124
+ }
125
+
126
+ client = @toggl.update_client(@client['id'], new_values)
127
+ expect(client).to include(new_values)
128
+ end
129
+
130
+ # :nocov:
131
+ # It appears hourly rate is no longer tied to a client despite the docs:
132
+ # https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#clients
133
+ xit 'updates Pro client data', :pro_account do
134
+ new_values = {
135
+ 'hrate' => '7.77',
136
+ 'cur' => 'USD',
137
+ }
138
+ client = @toggl.update_client(@client['id'], new_values)
139
+
140
+ client = @toggl.get_client(@client['id'])
141
+ expect(client).to include(new_values)
142
+ end
143
+ # :nocov:
144
+ end
145
+ end