timesheet-toggl 0.0.2 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7144dce52c6d9a109b3e9d095540359aeecd6cae
4
- data.tar.gz: 4e37f892a05b06dd46798c96eaf7cc22db88f6f8
3
+ metadata.gz: 5abbba2899768c7931df772a3807f6e2707861c6
4
+ data.tar.gz: 1bdf674dc5fa703fd0f0b4fdd441b828dc48ebd6
5
5
  SHA512:
6
- metadata.gz: c9ba21d9910947f6c2278e83a302b0f77f67fbf158d125fdf2ef4502a96e887ab6139fa0634d411efc3ee53d889b137f38d4cd6192e806b6e688875902434e72
7
- data.tar.gz: 133104f772bcf93c091ffa1da79cfc4ceecd8fbb99ad8f2dca488ae8abc74a63587d279b454660a9ad83de0ff7a37262a8befa980949f89ef480030113853002
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
@@ -1,5 +1,5 @@
1
1
  module Timesheet
2
- module Toggl
3
- VERSION = "0.0.2"
2
+ class Toggl
3
+ VERSION = "0.0.5"
4
4
  end
5
5
  end
@@ -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].each { |x| push_record x }
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 = derive_params(record)
99
- return unless params[:user_id]
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.2
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: 2014-10-20 00:00:00.000000000 Z
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.2.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: