timesheet-toggl 0.0.2 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/timesheet/toggl/clients.rb +29 -0
- data/lib/timesheet/toggl/parser.rb +129 -0
- data/lib/timesheet/toggl/projects.rb +28 -0
- data/lib/timesheet/toggl/version.rb +2 -2
- data/lib/timesheet/toggl.rb +16 -56
- data/timesheet-toggl-0.0.2.gem +0 -0
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5abbba2899768c7931df772a3807f6e2707861c6
|
4
|
+
data.tar.gz: 1bdf674dc5fa703fd0f0b4fdd441b828dc48ebd6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a870ed228f2c8dda4bc835063d7f4d0ae843f7c2732a213f833df4743da7901700bfd714e28213e345a35ee86fbfbd3fb9489855051bfb4810b30577d890682
|
7
|
+
data.tar.gz: c216d7566349691f264df6427299de3e1137766025552311dc0e2aeec6eec323d715d5bef657c1f6a4da5964163f40e8ee245d23b1c8fd507c522a90e6cdb930
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Timesheet
|
2
|
+
module Clients
|
3
|
+
CLIENTS_URI = 'https://www.toggl.com/api/v8/clients'
|
4
|
+
|
5
|
+
def create_client(name, workspace_id)
|
6
|
+
params = {
|
7
|
+
client: {
|
8
|
+
name: name,
|
9
|
+
wid: workspace_id,
|
10
|
+
}
|
11
|
+
}
|
12
|
+
headers = {}
|
13
|
+
headers['Content-Type']='application/json'
|
14
|
+
headers['X-Requested-With']='XMLHttpRequest'
|
15
|
+
headers['Accept']='application/json'
|
16
|
+
response = Curl::Easy.http_post(CLIENTS_URI, params.to_json) do |request|
|
17
|
+
request.http_auth_types = :basic
|
18
|
+
request.username = config[:api_token]
|
19
|
+
request.password = 'api_token'
|
20
|
+
request.headers = headers
|
21
|
+
end
|
22
|
+
if response.response_code == 200
|
23
|
+
JSON.parse(response.body, symbolize_names: true)[:data][:id]
|
24
|
+
else
|
25
|
+
Rails.logger.error "Client creation failed: #{response.body}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Timesheet
|
2
|
+
# get issue id
|
3
|
+
# issue-related params
|
4
|
+
# get project
|
5
|
+
# split by several issues
|
6
|
+
# if one issue, then all good
|
7
|
+
# if several:
|
8
|
+
# delete time entries from timesheet
|
9
|
+
# delete time entries from kibana
|
10
|
+
# add new time entries to timesheet
|
11
|
+
# @time_part
|
12
|
+
class TogglRecord
|
13
|
+
attr_accessor :record, :config
|
14
|
+
Time.zone = 'UTC' # not to corrupt start_time and end_time
|
15
|
+
|
16
|
+
def initialize(hash, config)
|
17
|
+
@descriptions = parse_description hash[:description]
|
18
|
+
@record = hash
|
19
|
+
@config = config
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse_description(description)
|
23
|
+
result = description.scan(/(#\s?\d+[^#]+)/).flatten
|
24
|
+
result.size < 2 ? [description] : result
|
25
|
+
end
|
26
|
+
|
27
|
+
def push
|
28
|
+
return unless params = descriptions_params
|
29
|
+
if params.size > 1
|
30
|
+
TimeEntry
|
31
|
+
.where(external_id: record[:id], data_source_id: config[:source_id])
|
32
|
+
.each { |x| x.delete_from_kibana; x.delete }
|
33
|
+
params.each { |x| return unless x[:user_id]; TimeEntry.create x }
|
34
|
+
else
|
35
|
+
params = params.first
|
36
|
+
return unless params[:user_id]
|
37
|
+
te = TimeEntry.find_or_create_by(
|
38
|
+
external_id: record[:id], data_source_id: config[:source_id])
|
39
|
+
te.update params
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def descriptions_params
|
44
|
+
return unless params = derive_params
|
45
|
+
time_proc = proc { |x| x.scan(/@\s?(\d+)/).flatten.first.to_i }
|
46
|
+
times = @descriptions.map(&time_proc).reject(&:zero?)
|
47
|
+
one_part = (times.size / @descriptions.size.to_f) * params[:hours] / times.sum
|
48
|
+
@descriptions.map do |x|
|
49
|
+
time = time_proc.call(x)
|
50
|
+
hours = time.zero? ?
|
51
|
+
(params[:hours] / @descriptions.size) :
|
52
|
+
(one_part * time)
|
53
|
+
iid = issue_id(comment: x)
|
54
|
+
params.merge(comment: x, hours: hours).merge(issue_related_params(iid))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def derive_params
|
59
|
+
params = record.reduce({}) do |r, (k, v)|
|
60
|
+
next(r) unless params_map[k]
|
61
|
+
r.merge(params_map[k] => v)
|
62
|
+
end
|
63
|
+
params[:data_source_id] = config[:source_id]
|
64
|
+
params[:spent_on] = record[:start]
|
65
|
+
params[:user_id] = user_id
|
66
|
+
return unless params[:user_id]
|
67
|
+
params[:hours] /= 3_600_000.0 # turn milliseconds into hours
|
68
|
+
if iid = issue_id(params)
|
69
|
+
params.merge!(issue_related_params(iid))
|
70
|
+
end
|
71
|
+
params[:client_id] ||= client_id
|
72
|
+
params
|
73
|
+
end
|
74
|
+
|
75
|
+
def params_map
|
76
|
+
{
|
77
|
+
id: :external_id,
|
78
|
+
project: :project,
|
79
|
+
description: :comment,
|
80
|
+
dur: :hours,
|
81
|
+
start: :start_time,
|
82
|
+
end: :finish_time
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def issue_id(params)
|
87
|
+
return unless config[:redmine_time_entry_class]
|
88
|
+
params[:comment].match(/#\s?(\d+)/).try(:[], 1)
|
89
|
+
end
|
90
|
+
|
91
|
+
def issue_related_params(issue_id)
|
92
|
+
time_entry_class = Kernel.const_get(config[:redmine_time_entry_class])
|
93
|
+
begin
|
94
|
+
project_id = time_entry_class.issue_class.find(issue_id).project_id
|
95
|
+
rescue
|
96
|
+
return {}
|
97
|
+
end
|
98
|
+
project_company = time_entry_class.project_company(project_id)
|
99
|
+
issue_company = time_entry_class.issue_company(issue_id)
|
100
|
+
company = issue_company.empty? ? project_company : issue_company
|
101
|
+
alert = time_entry_class.alert? project_company, issue_company
|
102
|
+
{
|
103
|
+
project: time_entry_class.project(project_id),
|
104
|
+
task: time_entry_class.task(issue_id),
|
105
|
+
client_id: time_entry_class.client_id(company),
|
106
|
+
project_company: project_company,
|
107
|
+
issue_company: issue_company,
|
108
|
+
alert: alert
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def client_id
|
113
|
+
if client = Client.find_by(name: record[:client])
|
114
|
+
client_id = client.id
|
115
|
+
else
|
116
|
+
Rails.logger.error "No client match to toggl client #{record[:client]}"
|
117
|
+
end
|
118
|
+
client_id
|
119
|
+
end
|
120
|
+
|
121
|
+
def user_id
|
122
|
+
data_source_user = DataSourceUser.find_by(
|
123
|
+
data_source_id: config[:source_id], external_user_id: record[:uid])
|
124
|
+
error = "No user match to toggl user #{record[:user]} (id #{record[:uid]})"
|
125
|
+
(Rails.logger.error(error); return) unless data_source_user
|
126
|
+
data_source_user.user_id
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Timesheet
|
2
|
+
module Projects
|
3
|
+
PROJECTS_URI = 'https://www.toggl.com/api/v8/projects'
|
4
|
+
|
5
|
+
def create_project(name, workspace_id, client_id)
|
6
|
+
params = {
|
7
|
+
project: {
|
8
|
+
name: name,
|
9
|
+
wid: workspace_id,
|
10
|
+
cid: client_id
|
11
|
+
}
|
12
|
+
}
|
13
|
+
headers = {}
|
14
|
+
headers['Content-Type']='application/json'
|
15
|
+
headers['X-Requested-With']='XMLHttpRequest'
|
16
|
+
headers['Accept']='application/json'
|
17
|
+
response = Curl::Easy.http_post(PROJECTS_URI, params.to_json) do |request|
|
18
|
+
request.http_auth_types = :basic
|
19
|
+
request.username = config[:api_token]
|
20
|
+
request.password = 'api_token'
|
21
|
+
request.headers = headers
|
22
|
+
end
|
23
|
+
unless response.response_code == 200
|
24
|
+
Rails.logger.error "Project creation failed: #{response.body}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/timesheet/toggl.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
require_relative 'toggl/version'
|
2
|
+
require_relative 'toggl/clients'
|
3
|
+
require_relative 'toggl/projects'
|
4
|
+
require_relative 'toggl/parser'
|
2
5
|
require 'curb'
|
3
6
|
require 'active_support/core_ext/time'
|
4
7
|
require 'active_support/core_ext/date'
|
@@ -13,6 +16,9 @@ module Timesheet
|
|
13
16
|
# Timesheet::Toggl.new(config).sync(from: (Date.today - 1.month), to: Date.today)
|
14
17
|
#
|
15
18
|
class Toggl
|
19
|
+
include Clients
|
20
|
+
include Projects
|
21
|
+
|
16
22
|
attr_accessor :config
|
17
23
|
|
18
24
|
BASE_URI = 'https://toggl.com/reports/api/v2/details'
|
@@ -44,18 +50,20 @@ module Timesheet
|
|
44
50
|
# until=2013-05-20&
|
45
51
|
# user_agent=api_test"
|
46
52
|
#
|
53
|
+
# @return array of time entries' ids
|
54
|
+
#
|
47
55
|
def synchronize(from, to, user_ids = [])
|
48
56
|
params = {
|
49
57
|
workspace_id: config[:workspace_id],
|
50
58
|
since: from.to_s,
|
51
59
|
until: to.to_s,
|
52
|
-
user_agent: 'export_to_timesheet'
|
53
|
-
user_ids: user_ids.join(',')
|
60
|
+
user_agent: 'export_to_timesheet'
|
54
61
|
}
|
62
|
+
params[:user_ids] = user_ids.join(',') unless user_ids.empty?
|
55
63
|
first_page = fetch(params, 1)
|
56
|
-
push(first_page)
|
64
|
+
te_ids = push(first_page)
|
57
65
|
pages = first_page[:total_count] / first_page[:per_page]
|
58
|
-
pages.times { |page| sync(params, page + 2) }
|
66
|
+
pages.times.map { |page| sync(params, page + 2) }.flatten + te_ids
|
59
67
|
end
|
60
68
|
|
61
69
|
# Since toggl has per-page api, we will follow them.
|
@@ -78,7 +86,7 @@ module Timesheet
|
|
78
86
|
request.password = 'api_token'
|
79
87
|
end
|
80
88
|
parsed = JSON.parse(response.body, symbolize_names: true)
|
81
|
-
fail "Request failed: #{parsed}" unless response.response_code == 200
|
89
|
+
fail "Request failed: #{parsed} with params #{params}" unless response.response_code == 200
|
82
90
|
parsed
|
83
91
|
end
|
84
92
|
|
@@ -87,7 +95,7 @@ module Timesheet
|
|
87
95
|
#
|
88
96
|
def push(parsed_response)
|
89
97
|
TimeEntry.transaction do
|
90
|
-
parsed_response[:data].
|
98
|
+
parsed_response[:data].map { |x| push_record x }
|
91
99
|
end
|
92
100
|
end
|
93
101
|
|
@@ -95,56 +103,8 @@ module Timesheet
|
|
95
103
|
# Don't push time entry if no user set.
|
96
104
|
#
|
97
105
|
def push_record(record)
|
98
|
-
params =
|
99
|
-
|
100
|
-
te = TimeEntry.find_or_create_by(
|
101
|
-
external_id: record[:id], data_source_id: config[:source_id])
|
102
|
-
te.update params
|
103
|
-
end
|
104
|
-
|
105
|
-
def derive_params(record)
|
106
|
-
params = record.reduce({}) do |r, (k, v)|
|
107
|
-
next(r) unless params_map[k]
|
108
|
-
r.merge(params_map[k] => v)
|
109
|
-
end
|
110
|
-
params[:data_source_id] = config[:source_id]
|
111
|
-
params[:spent_on] = record[:start].to_date
|
112
|
-
params[:hours] /= 3_600_000.0 # turn milliseconds into hours
|
113
|
-
if config[:redmine_time_entry_class]
|
114
|
-
if issue_id = params[:comment].match(/\#(\d+)/)[1]
|
115
|
-
params[:client_id] = Kernel.const_get(config[:redmine_time_entry_class])
|
116
|
-
.client_id(issue_id)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
unless params[:client_id]
|
120
|
-
if client = Client.find_by(name: record[:client])
|
121
|
-
params[:client_id] = client.id
|
122
|
-
else
|
123
|
-
Rails.logger.error "No client match to toggl client #{record[:client]}"
|
124
|
-
end
|
125
|
-
end
|
126
|
-
data_source_user = DataSourceUser.find_by(
|
127
|
-
data_source_id: config[:source_id], external_user_id: record[:uid])
|
128
|
-
if data_source_user
|
129
|
-
params[:user_id] = data_source_user.user_id
|
130
|
-
else
|
131
|
-
Rails.logger.error "No user match to toggl user
|
132
|
-
#{record[:user]} (id #{record[:uid]})"
|
133
|
-
end
|
134
|
-
params
|
135
|
-
end
|
136
|
-
|
137
|
-
def params_map
|
138
|
-
{
|
139
|
-
id: :external_id,
|
140
|
-
project: :project,
|
141
|
-
description: :comment,
|
142
|
-
dur: :hours,
|
143
|
-
start: :start_time,
|
144
|
-
end: :finish_time
|
145
|
-
}
|
106
|
+
params = TogglRecord.new(record, config).push
|
107
|
+
record[:id]
|
146
108
|
end
|
147
|
-
|
148
|
-
extend self
|
149
109
|
end
|
150
110
|
end
|
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timesheet-toggl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Smagin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -79,7 +79,11 @@ files:
|
|
79
79
|
- README.md
|
80
80
|
- Rakefile
|
81
81
|
- lib/timesheet/toggl.rb
|
82
|
+
- lib/timesheet/toggl/clients.rb
|
83
|
+
- lib/timesheet/toggl/parser.rb
|
84
|
+
- lib/timesheet/toggl/projects.rb
|
82
85
|
- lib/timesheet/toggl/version.rb
|
86
|
+
- timesheet-toggl-0.0.2.gem
|
83
87
|
- timesheet-toggl.gemspec
|
84
88
|
homepage: https://github.com/s-mage/timesheet-toggl
|
85
89
|
licenses:
|
@@ -101,9 +105,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
105
|
version: '0'
|
102
106
|
requirements: []
|
103
107
|
rubyforge_project:
|
104
|
-
rubygems_version: 2.
|
108
|
+
rubygems_version: 2.4.5
|
105
109
|
signing_key:
|
106
110
|
specification_version: 4
|
107
111
|
summary: Toggl integration for timesheet
|
108
112
|
test_files: []
|
109
|
-
has_rdoc:
|