toggl_cache 0.1.1 → 0.2.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 +4 -4
- data/Gemfile +1 -0
- data/HISTORY.md +27 -0
- data/README.md +9 -3
- data/VERSION +1 -1
- data/lib/toggl_api/reports_client.rb +9 -9
- data/lib/toggl_cache/data/report_repository.rb +42 -14
- data/lib/toggl_cache.rb +140 -22
- data/lib/toggl_cli.rb +3 -5
- data/scripts/sync.rb +8 -2
- data/scripts/sync_check_and_fix.rb +19 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/toggl_api/reports_client_spec.rb +21 -17
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5c97e1c35e17dd0e4e74f0194eefb1b9d100199
|
4
|
+
data.tar.gz: c4b6bfe066da7c539256d39dc98c00539795e7ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4f4ce467a89a23278e731de36b6a2e0df90d6164eb742a87e496f9c42667c232d7764df84b0a0c2b90a0ad0780d640fd65a7715c098ca70f29a04864fa34f41
|
7
|
+
data.tar.gz: 167afe2ed80546b63b0007e69dab6bd1d5ce2fd7a49deff03cf975a4578bbe16194c0cfcaf9e1d5b88812cc1ad9714230f5368a9b7ad6930c01fdb387c1f02f1
|
data/Gemfile
CHANGED
data/HISTORY.md
CHANGED
@@ -1,5 +1,32 @@
|
|
1
1
|
# History
|
2
2
|
|
3
|
+
## 2017-03-25 - 0.2.0
|
4
|
+
|
5
|
+
Synchronizing deleted records and old changes.
|
6
|
+
|
7
|
+
Approach #1: calculate aggregates over periods and perform a full sync over the period if there is a diff.
|
8
|
+
|
9
|
+
- Pros: more efficient, smarter, also provides a synchronization correctness control.
|
10
|
+
- Cons: harder to implement.
|
11
|
+
|
12
|
+
Approach #2: brute-force, i.e. simply clear the content for a given period and perform a full sync.
|
13
|
+
|
14
|
+
- Pros: simpler to implement.
|
15
|
+
- Cons: synchronization is currently to long.
|
16
|
+
|
17
|
+
Trying approach #2. Optimizing synchronization:
|
18
|
+
|
19
|
+
- [X] processing fetched records once a page has been fetched, not at the end.
|
20
|
+
- [ ] perform requests in parallel.
|
21
|
+
|
22
|
+
The approach #2 is not applicable. Reviewing Toggl API's documentation, the rate limit is about 1 request per second, so parallelizing does not seem to be an option.
|
23
|
+
|
24
|
+
NB: performing a full synchronization leading to about 40K reports takes about 0.65 hour.
|
25
|
+
|
26
|
+
The approach #1 has been implemented. The algorithm first checks each full year. If a difference is detected, it checks each month of the year. For each month with a difference, a sync is done by clearing the cache for the month and fetching the reports.
|
27
|
+
|
28
|
+
The implementation has been done in `TogglCache.sync_check_and_fix`.
|
29
|
+
|
3
30
|
## 2017-03-18 - 0.1.1
|
4
31
|
|
5
32
|
- Changed default period of import to one week from now, instead of one day.
|
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# TogglCache
|
2
2
|
|
3
|
-
Fetches reports data from Toggl API and caches them in a
|
3
|
+
Fetches reports data from Toggl API and caches them in a PostgreSQL database.
|
4
4
|
|
5
5
|
This allows you to build applications performing complex operations on a large number of Toggl content without the Toggl API latency. You may also use it to backup your precious data!
|
6
6
|
|
7
7
|
[](https://travis-ci.org/rchampourlier/toggl_cache)
|
8
|
-
[](https://codeclimate.com/repos/58d7ff3b88ccb7027b000baa/feed)
|
9
|
+
[](https://codeclimate.com/repos/58d7ff3b88ccb7027b000baa/coverage)
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
|
@@ -39,6 +39,12 @@ client = TogglCache::Client.new(
|
|
39
39
|
TogglCache.sync_reports(client, 'TOGGL-WORKSPACE-ID')
|
40
40
|
```
|
41
41
|
|
42
|
+
## The CLI
|
43
|
+
|
44
|
+
```
|
45
|
+
ruby lib/toggl_cli.rb [batch|help]
|
46
|
+
```
|
47
|
+
|
42
48
|
## Contributing
|
43
49
|
|
44
50
|
1. Fork it ( https://github.com/rchampourlier/toggl_cache/fork )
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -10,28 +10,26 @@ module TogglAPI
|
|
10
10
|
API_URL = "https://toggl.com/reports/api/v2"
|
11
11
|
|
12
12
|
# @param params [Hash]: Toggl API params
|
13
|
-
# -
|
14
|
-
# -
|
13
|
+
# - since
|
14
|
+
# - until
|
15
15
|
# - workspace_id
|
16
16
|
# - ... more params available, see Toggl API documentation for details
|
17
17
|
def fetch_reports(params)
|
18
|
+
raise "Must give a block" unless block_given?
|
18
19
|
page = 1
|
19
|
-
|
20
|
+
current_total = 0
|
20
21
|
loop do
|
21
22
|
results_raw = fetch_reports_details_raw(
|
22
23
|
params.merge(page: page)
|
23
24
|
)
|
24
|
-
|
25
|
+
yield(results_raw["data"])
|
25
26
|
|
26
|
-
|
27
|
-
break if
|
27
|
+
current_total += results_raw["data"].count
|
28
|
+
break if current_total == results_raw["total_count"]
|
28
29
|
page += 1
|
29
30
|
end
|
30
|
-
all_results
|
31
31
|
end
|
32
32
|
|
33
|
-
private
|
34
|
-
|
35
33
|
def fetch_reports_details_raw(params)
|
36
34
|
fetch_reports_raw(api_url(:details), params)
|
37
35
|
end
|
@@ -40,6 +38,8 @@ module TogglAPI
|
|
40
38
|
fetch_reports_raw(api_url(:summary), params)
|
41
39
|
end
|
42
40
|
|
41
|
+
private
|
42
|
+
|
43
43
|
# @param url [String]
|
44
44
|
# @param params [Hash]: Toggl API params
|
45
45
|
def fetch_reports_raw(url, params)
|
@@ -5,8 +5,10 @@ require "active_support/inflector"
|
|
5
5
|
module TogglCache
|
6
6
|
module Data
|
7
7
|
|
8
|
-
#
|
9
|
-
#
|
8
|
+
# Repository for Toggl reports.
|
9
|
+
#
|
10
|
+
# TODO: should be used through instances
|
11
|
+
# TODO: #table should be private
|
10
12
|
class ReportRepository
|
11
13
|
|
12
14
|
MAPPED_REPORT_ATTRIBUTES = %w(
|
@@ -22,7 +24,7 @@ module TogglCache
|
|
22
24
|
# It inserts a new issue row with the specified data.
|
23
25
|
# If the issue already exists (unicity key is `id`)
|
24
26
|
# the row is updated instead.
|
25
|
-
def
|
27
|
+
def create_or_update(report)
|
26
28
|
id = report["id"].to_s
|
27
29
|
if exist_with_id?(id)
|
28
30
|
update_where({ id: id }, row(report: report))
|
@@ -31,39 +33,65 @@ module TogglCache
|
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
34
|
-
def
|
36
|
+
def find_by_id(id)
|
35
37
|
table.where(id: id).first
|
36
38
|
end
|
37
39
|
|
38
|
-
def
|
40
|
+
def exist_with_id?(id)
|
39
41
|
table.where(id: id).count != 0
|
40
42
|
end
|
41
43
|
|
42
|
-
def
|
44
|
+
def delete_where(where_data)
|
43
45
|
table.where(where_data).delete
|
44
46
|
end
|
45
47
|
|
46
|
-
def
|
48
|
+
def update_where(where_data, values)
|
47
49
|
table.where(where_data).update(values)
|
48
50
|
end
|
49
51
|
|
50
|
-
def
|
52
|
+
def first(by: :start)
|
53
|
+
table.order(by).first
|
54
|
+
end
|
55
|
+
|
56
|
+
def first_where(where_data)
|
51
57
|
table.where(where_data).first
|
52
58
|
end
|
53
59
|
|
54
|
-
def
|
60
|
+
def index
|
55
61
|
table.entries
|
56
62
|
end
|
57
63
|
|
58
|
-
def
|
64
|
+
def count
|
59
65
|
table.count
|
60
66
|
end
|
61
67
|
|
62
|
-
|
68
|
+
# Returns reports whose `start` time is within the specified range.
|
69
|
+
#
|
70
|
+
# @param since: [Time]
|
71
|
+
# @param until: [Time]
|
72
|
+
def starting(time_since:, time_until:)
|
73
|
+
table.where("start >= ? AND start <= ?", time_since, time_until).entries
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete_starting(time_since:, time_until:)
|
77
|
+
table.where("start >= ? AND start <= ?", time_since, time_until).delete
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param pid [Integer]
|
81
|
+
# @param tid [Integer] optional
|
82
|
+
def where(project_id:, task_id: nil)
|
83
|
+
where_criteria = { pid: project_id }
|
84
|
+
where_criteria[:tid] = tid if task_id
|
85
|
+
table.where(where_criteria).entries
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def table
|
63
91
|
DB[:toggl_cache_reports]
|
64
92
|
end
|
65
93
|
|
66
|
-
def
|
94
|
+
def row(report:, insert_created_at: false, insert_updated_at: true)
|
67
95
|
new_report = map_report_attributes(report: report)
|
68
96
|
new_report = add_timestamps(
|
69
97
|
report: new_report,
|
@@ -73,7 +101,7 @@ module TogglCache
|
|
73
101
|
new_report
|
74
102
|
end
|
75
103
|
|
76
|
-
def
|
104
|
+
def map_report_attributes(report:)
|
77
105
|
new_report = report.select { |k, _| MAPPED_REPORT_ATTRIBUTES.include?(k) }
|
78
106
|
new_report = new_report.merge(
|
79
107
|
duration: report["dur"] / 1_000,
|
@@ -85,7 +113,7 @@ module TogglCache
|
|
85
113
|
new_report
|
86
114
|
end
|
87
115
|
|
88
|
-
def
|
116
|
+
def add_timestamps(report:, insert_created_at:, insert_updated_at:)
|
89
117
|
new_report = {}.merge(report)
|
90
118
|
new_report["created_at"] = Time.now if insert_created_at
|
91
119
|
new_report["updated_at"] = Time.now if insert_updated_at
|
data/lib/toggl_cache.rb
CHANGED
@@ -20,17 +20,130 @@ module TogglCache
|
|
20
20
|
# The fetched reports either update the already
|
21
21
|
# existing ones, or create new ones.
|
22
22
|
#
|
23
|
-
# @param client [TogglAPI::Client] a configured client
|
24
|
-
# @param workspace_id [String] Toggl workspace ID (mandatory)
|
25
23
|
# @param date_since [Date] Date since when to fetch
|
26
24
|
# the reports.
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
# @param date_until [Date] Date until when to fetch. Defaults to `Time.now`.
|
26
|
+
# @param client [TogglAPI::Client] a configured client
|
27
|
+
def self.sync_reports(
|
28
|
+
date_since: default_date_since,
|
29
|
+
date_until: Time.now,
|
30
|
+
logger: default_logger,
|
31
|
+
client: default_client
|
32
|
+
)
|
33
|
+
logger.info "Syncing reports from #{date_since} to #{date_until}."
|
34
|
+
clear_cache(
|
35
|
+
time_since: Time.parse("#{date_since} 00:00:00Z"),
|
36
|
+
time_until: Time.parse("#{date_until} 23:59:59Z")
|
37
|
+
)
|
38
|
+
fetch_reports(
|
30
39
|
client: client,
|
31
|
-
date_since: date_since
|
40
|
+
date_since: date_since,
|
41
|
+
date_until: date_until
|
42
|
+
) do |reports|
|
43
|
+
process_reports(reports)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Performs a full synchronization check, from the time of the first
|
48
|
+
# report in the cache to now. Proceeds by comparing reports total
|
49
|
+
# duration from Toggl (using the Reports API) and the total contained
|
50
|
+
# in the cache. If a difference is detected, proceeds monthly and
|
51
|
+
# clear and reconstructs the cache for the invovled month.
|
52
|
+
#
|
53
|
+
# TODO: enable detecting a change in project/task level aggregates.
|
54
|
+
def self.sync_check_and_fix(logger: default_logger)
|
55
|
+
reports = TogglCache::Data::ReportRepository.new
|
56
|
+
first_report = reports.first
|
57
|
+
|
58
|
+
year_start = first_report[:start].year
|
59
|
+
year_end = Time.now.year
|
60
|
+
month_start = first_report[:start].month
|
61
|
+
month_end = Time.now.month
|
62
|
+
|
63
|
+
(year_start..year_end).each do |year|
|
64
|
+
year_toggl = TogglCache.toggl_total(year: year)
|
65
|
+
year_cache = TogglCache.cache_total(year: year)
|
66
|
+
if year_toggl == year_cache
|
67
|
+
logger.info "Checked total for #{year}: ✅ (#{year_toggl})"
|
68
|
+
next
|
69
|
+
end
|
70
|
+
logger.info "Checked total for #{year}: ❌ (Toggl: #{year_toggl}, cache: #{year_cache})"
|
71
|
+
(1..12).each do |month|
|
72
|
+
next if year == year_start && month < month_start
|
73
|
+
next if year == year_end && month > month_end
|
74
|
+
month_toggl = TogglCache.toggl_total(year: year, month: month)
|
75
|
+
month_cache = TogglCache.cache_total(year: year, month: month)
|
76
|
+
if month_toggl == month_cache
|
77
|
+
logger.info "Checked total for #{year}/#{month}: ✅ (#{month_toggl})"
|
78
|
+
else
|
79
|
+
logger.info "Checked total for #{year}/#{month}: ❌ (Toggl: #{month_toggl}, cache: #{month_cache})"
|
80
|
+
TogglCache.clear_cache(year: year, month: month)
|
81
|
+
TogglCache.sync_reports_for_month(year: year, month: month)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# An easy-to-use method to sync reports for a given month. Simply
|
88
|
+
# performs a call to `sync_reports`.
|
89
|
+
#
|
90
|
+
# @param year [Integer]
|
91
|
+
# @param month [Integer
|
92
|
+
def self.sync_reports_for_month(year:, month:)
|
93
|
+
date_since = Date.civil(year, month, 1)
|
94
|
+
date_until = Date.civil(year, month, -1)
|
95
|
+
sync_reports(
|
96
|
+
date_since: date_since,
|
97
|
+
date_until: date_until
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Remove TogglCache's reports between the specified dates.
|
102
|
+
def self.clear_cache(time_since:, time_until:)
|
103
|
+
logger.info "Clearing cache from #{date_since} to #{date_until}."
|
104
|
+
reports = Data::ReportRepository.new
|
105
|
+
reports.delete_starting(
|
106
|
+
time_since: time_since,
|
107
|
+
time_until: time_until
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Remove TogglCache's reports for the specified month.
|
112
|
+
def self.clear_cache_for_month(year:, month:, logger: default_logger)
|
113
|
+
date_since = Date.civil(year, month, 1)
|
114
|
+
date_until = Date.civil(year, month, -1)
|
115
|
+
clear_cache(
|
116
|
+
time_since: Time.parse("#{date_since} 00:00:00Z"),
|
117
|
+
time_until: Time.parse("#{date_until} 23:59:59Z")
|
32
118
|
)
|
33
|
-
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns the total duration from Toggl (using Reports API)
|
122
|
+
# for the specified year or month.
|
123
|
+
def self.toggl_total(year:, month: nil)
|
124
|
+
reports_client = TogglAPI::ReportsClient.new
|
125
|
+
date_since = month ? Date.civil(year, month, 1) : Date.civil(year, 1, 1)
|
126
|
+
date_until = month ? Date.civil(year, month, -1) : Date.civil(year, 12, -1)
|
127
|
+
total_grand = reports_client.fetch_reports_summary_raw(
|
128
|
+
since: date_since.to_s,
|
129
|
+
until: date_until.to_s,
|
130
|
+
workspace_id: ENV["TOGGL_WORKSPACE_ID"]
|
131
|
+
)["total_grand"]
|
132
|
+
total_grand ? total_grand / 3600 / 1000 : 0
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns the total duration in TogglCache reports
|
136
|
+
# for the specified year or month.
|
137
|
+
def self.cache_total(year:, month: nil)
|
138
|
+
reports = TogglCache::Data::ReportRepository.new
|
139
|
+
date_since = month ? Date.civil(year, month, 1).to_s : Date.civil(year, 1, 1)
|
140
|
+
date_until = month ? Date.civil(year, month, -1).to_s : Date.civil(year, 12, -1)
|
141
|
+
time_since = Time.parse("#{date_since} 00:00:00Z")
|
142
|
+
time_until = Time.parse("#{date_until} 23:59:59Z")
|
143
|
+
reports.starting(
|
144
|
+
time_since: time_since,
|
145
|
+
time_until: time_until
|
146
|
+
).inject(0) { |sum, r| sum + r[:duration] } / 3600
|
34
147
|
end
|
35
148
|
|
36
149
|
# Fetch from Toggl
|
@@ -47,32 +160,37 @@ module TogglCache
|
|
47
160
|
client: default_client,
|
48
161
|
workspace_id: default_workspace_id,
|
49
162
|
date_since:,
|
50
|
-
date_until: Time.now
|
163
|
+
date_until: Time.now,
|
164
|
+
&block
|
51
165
|
)
|
166
|
+
raise "You must give a block to process fetched records" unless block_given?
|
52
167
|
if date_since && date_until.year > date_since.year
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
168
|
+
[
|
169
|
+
[date_since, Date.new(date_since.year, 12, 31)],
|
170
|
+
[Date.new(date_since.year + 1, 1, 1), date_until]
|
171
|
+
].each do |dates|
|
172
|
+
fetch_reports(
|
173
|
+
client: client,
|
174
|
+
workspace_id: workspace_id,
|
175
|
+
date_since: dates.first,
|
176
|
+
date_until: dates.last,
|
177
|
+
&block
|
178
|
+
)
|
179
|
+
end
|
64
180
|
else
|
65
181
|
options = {
|
66
182
|
workspace_id: workspace_id, until: date_until.strftime("%Y-%m-%d")
|
67
183
|
}
|
68
184
|
options[:since] = date_since.strftime("%Y-%m-%d") unless date_since.nil?
|
69
|
-
client.fetch_reports(options)
|
185
|
+
client.fetch_reports(options, &block)
|
70
186
|
end
|
71
187
|
end
|
72
188
|
|
73
|
-
def self.process_reports(reports)
|
189
|
+
def self.process_reports(reports, logger: default_logger)
|
190
|
+
logger.debug "Processing #{reports.count} Toggl reports"
|
191
|
+
repository = Data::ReportRepository.new
|
74
192
|
reports.each do |report|
|
75
|
-
|
193
|
+
repository.create_or_update(report)
|
76
194
|
end
|
77
195
|
end
|
78
196
|
|
data/lib/toggl_cli.rb
CHANGED
@@ -3,7 +3,6 @@ require "thor"
|
|
3
3
|
require "tty-pager"
|
4
4
|
require "tty-progressbar"
|
5
5
|
require "tty-spinner"
|
6
|
-
# require "tty-table"
|
7
6
|
require File.expand_path("../../config/boot", __FILE__)
|
8
7
|
require "toggl_api/client"
|
9
8
|
require "toggl_cache/data/report_repository"
|
@@ -48,7 +47,7 @@ class TogglCLI < Thor
|
|
48
47
|
say("MOVING!!!")
|
49
48
|
end
|
50
49
|
end
|
51
|
-
|
50
|
+
TogglCache::Data::ReportRepository.new.create_or_update(report)
|
52
51
|
end
|
53
52
|
|
54
53
|
private
|
@@ -112,10 +111,9 @@ class TogglCLI < Thor
|
|
112
111
|
end
|
113
112
|
|
114
113
|
def find_reports(project_id, task_id)
|
114
|
+
reports = TogglCache::Data::ReportRepository.new
|
115
115
|
with_spinner("Fetching matching reports...") do
|
116
|
-
|
117
|
-
where_criteria[:tid] = task_id if task_id
|
118
|
-
TogglCache::Data::ReportRepository.table.where(where_criteria).entries
|
116
|
+
reports.where(project_id: project_id&.to_i, task_id: task_id&.to_i)
|
119
117
|
end
|
120
118
|
end
|
121
119
|
|
data/scripts/sync.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Usage:
|
4
4
|
#
|
5
|
-
# - Syncs reports since
|
5
|
+
# - Syncs reports since 31 days::
|
6
6
|
# ruby scripts/sync.rb
|
7
7
|
#
|
8
8
|
# - Sync reports since the specified date (use Ruby-parseable
|
@@ -16,5 +16,11 @@ require 'bundler/setup'
|
|
16
16
|
$LOAD_PATH.unshift File.expand_path('../..', __FILE__)
|
17
17
|
require 'config/boot'
|
18
18
|
|
19
|
-
date_since =
|
19
|
+
date_since = (
|
20
|
+
if ARGV[0].nil? || ARGV[0].empty?
|
21
|
+
Time.now - 31 * 24 * 3600
|
22
|
+
else
|
23
|
+
Time.parse(ARGV[0])
|
24
|
+
end
|
25
|
+
)
|
20
26
|
TogglCache.sync_reports(date_since: date_since)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Check that reports stored in TogglCache match report summaries
|
5
|
+
# provided by toggl Reports API.
|
6
|
+
#
|
7
|
+
# Usage:
|
8
|
+
#
|
9
|
+
# ruby scripts/sync_check_and_fix.rb
|
10
|
+
|
11
|
+
# Load dependencies
|
12
|
+
require "rubygems"
|
13
|
+
require "bundler/setup"
|
14
|
+
require "logger"
|
15
|
+
|
16
|
+
$LOAD_PATH.unshift File.expand_path("../..", __FILE__)
|
17
|
+
require "config/boot"
|
18
|
+
|
19
|
+
TogglCache.sync_check_and_fix
|
data/spec/spec_helper.rb
CHANGED
@@ -4,8 +4,13 @@ require "toggl_api/reports_client"
|
|
4
4
|
|
5
5
|
describe TogglAPI::ReportsClient do
|
6
6
|
let(:options) { {} }
|
7
|
-
let(:expected_api_call_url) { "https://
|
8
|
-
|
7
|
+
let(:expected_api_call_url) { "https://toggl.com/reports/api/v2/details?page=1&user_agent=TogglAPI" }
|
8
|
+
let(:expected_headers) do
|
9
|
+
{
|
10
|
+
"Authorization" => "Basic #{["toggl_api_token:api_token"].pack('m').delete("\r\n")}",
|
11
|
+
"Content-Type" => "application/json"
|
12
|
+
}
|
13
|
+
end
|
9
14
|
let(:api_response_status) { 200 }
|
10
15
|
let(:api_response_body) do
|
11
16
|
{
|
@@ -20,24 +25,25 @@ describe TogglAPI::ReportsClient do
|
|
20
25
|
|
21
26
|
before do
|
22
27
|
stub_request(:get, expected_api_call_url)
|
23
|
-
.with(headers:
|
28
|
+
.with(headers: expected_headers)
|
24
29
|
.to_return(status: api_response_status, body: api_response_body, headers: {})
|
25
30
|
end
|
26
31
|
|
27
|
-
describe "#fetch_reports(params)" do
|
32
|
+
describe "#fetch_reports(params, &block)" do
|
28
33
|
|
29
34
|
context "only 1 page" do
|
30
35
|
let(:total_count) { 50 }
|
31
36
|
|
32
37
|
it "fetches the data only once" do
|
33
|
-
described_class.new.fetch_reports(options)
|
38
|
+
described_class.new.fetch_reports(options) {}
|
34
39
|
# It will fail if it fetches several times because
|
35
40
|
# the second request, with page=2, is not mocked.
|
36
41
|
end
|
37
42
|
|
38
|
-
it "
|
39
|
-
results = described_class.new.fetch_reports(options)
|
40
|
-
|
43
|
+
it "passes the received data to the specified block" do
|
44
|
+
results = described_class.new.fetch_reports(options) do |reports|
|
45
|
+
expect(reports.count).to eq(50)
|
46
|
+
end
|
41
47
|
end
|
42
48
|
end
|
43
49
|
|
@@ -53,18 +59,16 @@ describe TogglAPI::ReportsClient do
|
|
53
59
|
data: data_page2
|
54
60
|
}.to_json
|
55
61
|
stub_request(:get, expected_api_call_url.gsub('page=1', 'page=2'))
|
56
|
-
.with(headers:
|
62
|
+
.with(headers: expected_headers)
|
57
63
|
.to_return(status: api_response_status, body: api_response_body_page2, headers: {})
|
58
64
|
end
|
59
65
|
|
60
66
|
it "fetches the additional pages" do
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
results = described_class.new.fetch_reports(options)
|
67
|
-
expect(results.inject(0) { |s, i| s + i["id"] }).to eq(50 * 1 + 10 * 2)
|
67
|
+
total_count = 0
|
68
|
+
results = described_class.new.fetch_reports(options) do |reports|
|
69
|
+
total_count += reports.count
|
70
|
+
end
|
71
|
+
expect(total_count).to eq(60)
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
@@ -72,7 +76,7 @@ describe TogglAPI::ReportsClient do
|
|
72
76
|
let(:api_response_status) { 500 }
|
73
77
|
|
74
78
|
it "raises a TogglAPI::Error" do
|
75
|
-
expect { described_class.new.fetch_reports(options) }
|
79
|
+
expect { described_class.new.fetch_reports(options) {} }
|
76
80
|
.to raise_error(TogglAPI::Error)
|
77
81
|
end
|
78
82
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toggl_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Romain Champourlier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: i18n
|
@@ -204,6 +204,7 @@ files:
|
|
204
204
|
- lib/toggl_cache/version.rb
|
205
205
|
- lib/toggl_cli.rb
|
206
206
|
- scripts/sync.rb
|
207
|
+
- scripts/sync_check_and_fix.rb
|
207
208
|
- spec/spec_helper.rb
|
208
209
|
- spec/unit/toggl_api/reports_client_spec.rb
|
209
210
|
- toggl_cache.gemspec
|