sfctl 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -0
- data/README.md +4 -8
- data/lib/sfctl/command.rb +1 -1
- data/lib/sfctl/commands/auth.rb +2 -2
- data/lib/sfctl/commands/auth/init.rb +8 -10
- data/lib/sfctl/commands/time/connections/add.rb +90 -21
- data/lib/sfctl/commands/time/connections/get.rb +11 -10
- data/lib/sfctl/commands/time/providers/get.rb +1 -3
- data/lib/sfctl/commands/time/providers/set.rb +2 -4
- data/lib/sfctl/commands/time/providers/unset.rb +2 -4
- data/lib/sfctl/commands/time/sync.rb +54 -31
- data/lib/sfctl/toggl.rb +28 -6
- data/lib/sfctl/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87640e37bddf52afff13b5ac880334429e8357308c4ca069df10cb06d5627463
|
4
|
+
data.tar.gz: 7431952f8f7d62c6ce5676276f6dc50218389c6d3433e8886e08351f0670dee6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d4518ccdd8dfb1595954e19169402db62511a6be21b3011c082f1bf67661fa3156c44aedd80afdc79ab2fe5a5d991068127b51af02080eef644478f64c93cd2
|
7
|
+
data.tar.gz: 4ec95937e28e42fb82fc289851eadb46d475c2aa59fc9d6819f744e9df6a0ecf266596882cd70828d7aec4e67385008c7117e8552aee8f78811c98a37e5c018c
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -21,13 +21,11 @@ Flags:
|
|
21
21
|
|
22
22
|
## Installing `sfctl`
|
23
23
|
|
24
|
-
### Using
|
24
|
+
### Using Ruby Gems (Preferred)
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
More to come.
|
26
|
+
```
|
27
|
+
gem install sfctl
|
28
|
+
```
|
31
29
|
|
32
30
|
## Authentication
|
33
31
|
|
@@ -109,8 +107,6 @@ We will provide a couple of integrations but also open up for plugins later on t
|
|
109
107
|
As of today we support:
|
110
108
|
|
111
109
|
- Toggl
|
112
|
-
- Harvest
|
113
|
-
- Clockify
|
114
110
|
|
115
111
|
Another advantage of this approach is, that you think of any automation ⚒ you like to support your processes.
|
116
112
|
|
data/lib/sfctl/command.rb
CHANGED
@@ -10,7 +10,7 @@ module Sfctl
|
|
10
10
|
CONFIG_FILENAME = '.sfctl'
|
11
11
|
CONFIG_PATH = "#{Dir.home}/#{CONFIG_FILENAME}"
|
12
12
|
LINK_CONFIG_FILENAME = '.sflink'
|
13
|
-
LINK_CONFIG_PATH = "#{Dir.
|
13
|
+
LINK_CONFIG_PATH = "#{Dir.pwd}/#{LINK_CONFIG_FILENAME}"
|
14
14
|
|
15
15
|
TOGGL_PROVIDER = 'toggl'
|
16
16
|
PROVIDERS_LIST = [
|
data/lib/sfctl/commands/auth.rb
CHANGED
@@ -22,12 +22,12 @@ module Sfctl
|
|
22
22
|
which can be created on the profile page of your account.
|
23
23
|
HEREDOC
|
24
24
|
method_option :help, aliases: '-h', type: :boolean
|
25
|
-
def init(
|
25
|
+
def init(*)
|
26
26
|
if options[:help]
|
27
27
|
invoke :help, ['init']
|
28
28
|
else
|
29
29
|
require_relative 'auth/init'
|
30
|
-
Sfctl::Commands::Auth::Init.new(
|
30
|
+
Sfctl::Commands::Auth::Init.new(options).execute
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -1,31 +1,29 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
require_relative '../../command'
|
4
2
|
require_relative '../../starfish'
|
5
3
|
require 'pastel'
|
4
|
+
require 'tty-prompt'
|
6
5
|
require 'tty-spinner'
|
7
6
|
|
8
7
|
module Sfctl
|
9
8
|
module Commands
|
10
9
|
class Auth
|
11
10
|
class Init < Sfctl::Command
|
12
|
-
def initialize(
|
13
|
-
@access_token = access_token
|
11
|
+
def initialize(options)
|
14
12
|
@options = options
|
15
|
-
|
16
13
|
@pastel = Pastel.new(enabled: !@options['no-color'])
|
17
14
|
end
|
18
15
|
|
19
16
|
def execute(output: $stdout)
|
17
|
+
access_token = ::TTY::Prompt.new.ask("Access token(#{@options['starfish-host']}):", required: true)
|
20
18
|
spinner = ::TTY::Spinner.new('[:spinner] Checking token ...')
|
21
19
|
spinner.auto_spin
|
22
|
-
token_valid? ? update_config!(spinner, output) : render_error(spinner, output)
|
20
|
+
token_valid?(access_token) ? update_config!(spinner, output, access_token) : render_error(spinner, output)
|
23
21
|
end
|
24
22
|
|
25
23
|
private
|
26
24
|
|
27
|
-
def token_valid?
|
28
|
-
Starfish.check_authorization(@options['starfish-host'],
|
25
|
+
def token_valid?(access_token)
|
26
|
+
Starfish.check_authorization(@options['starfish-host'], access_token)
|
29
27
|
end
|
30
28
|
|
31
29
|
def token_accepted_message
|
@@ -36,8 +34,8 @@ module Sfctl
|
|
36
34
|
@pastel.red('Token is not accepted, please make sure you copy and paste it correctly.')
|
37
35
|
end
|
38
36
|
|
39
|
-
def update_config!(spinner, output)
|
40
|
-
config.set :access_token, value:
|
37
|
+
def update_config!(spinner, output, access_token)
|
38
|
+
config.set :access_token, value: access_token
|
41
39
|
save_config!
|
42
40
|
spinner.success
|
43
41
|
output.puts token_accepted_message
|
@@ -1,7 +1,10 @@
|
|
1
|
+
require 'date'
|
1
2
|
require 'pastel'
|
3
|
+
require 'tty-spinner'
|
2
4
|
require 'tty-prompt'
|
3
5
|
require_relative '../../../command'
|
4
6
|
require_relative '../../../starfish'
|
7
|
+
require_relative '../../../toggl'
|
5
8
|
|
6
9
|
module Sfctl
|
7
10
|
module Commands
|
@@ -11,10 +14,10 @@ module Sfctl
|
|
11
14
|
def initialize(options)
|
12
15
|
@options = options
|
13
16
|
@pastel = Pastel.new(enabled: !@options['no-color'])
|
14
|
-
@prompt = ::TTY::Prompt.new
|
17
|
+
@prompt = ::TTY::Prompt.new(help_color: :cyan)
|
15
18
|
end
|
16
19
|
|
17
|
-
def execute(output: $stdout) # rubocop:disable Metrics/
|
20
|
+
def execute(output: $stdout) # rubocop:disable Metrics/AbcSize
|
18
21
|
return if !config_present?(output) || !link_config_present?(output)
|
19
22
|
|
20
23
|
ltoken = access_token
|
@@ -33,24 +36,33 @@ module Sfctl
|
|
33
36
|
|
34
37
|
provider = @prompt.select('Select provider:', PROVIDERS_LIST)
|
35
38
|
|
36
|
-
|
39
|
+
assignment_obj = select_assignment(assignments)
|
37
40
|
|
38
41
|
case provider
|
39
42
|
when TOGGL_PROVIDER
|
40
|
-
setup_toggl_connection!(
|
43
|
+
setup_toggl_connection!(output, assignment_obj)
|
41
44
|
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
42
48
|
|
49
|
+
def clear_conf_and_print_success!(output)
|
50
|
+
delete_providers_from_link_config!
|
43
51
|
save_link_config!
|
44
52
|
|
45
53
|
output.puts @pastel.green('Connection successfully added.')
|
46
54
|
end
|
47
55
|
|
48
|
-
|
56
|
+
def delete_providers_from_link_config!
|
57
|
+
config.set(:providers, value: '')
|
58
|
+
config.delete(:providers)
|
59
|
+
end
|
49
60
|
|
50
61
|
def select_assignment(assignments)
|
51
62
|
@prompt.select('Select assignment:') do |menu|
|
52
63
|
assignments.each.with_index do |asmnt, i|
|
53
|
-
menu.choice name: "#{i + 1}. #{asmnt['name']} / #{asmnt['service']}",
|
64
|
+
menu.choice name: "#{i + 1}. #{asmnt['name']} / #{asmnt['service']}",
|
65
|
+
value: { 'id' => asmnt['id'], 'name' => asmnt['name'], 'service' => asmnt['service'] }
|
54
66
|
end
|
55
67
|
end
|
56
68
|
end
|
@@ -58,24 +70,81 @@ module Sfctl
|
|
58
70
|
def filter_assignments(list)
|
59
71
|
return list if config.fetch(:connections).nil?
|
60
72
|
|
61
|
-
|
62
|
-
list.delete_if { |h|
|
73
|
+
added_assignments_ids = config.fetch(:connections).keys
|
74
|
+
list.delete_if { |h| added_assignments_ids.include?(h['id'].to_s) }
|
63
75
|
list
|
64
76
|
end
|
65
77
|
|
66
|
-
def setup_toggl_connection!(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
78
|
+
def setup_toggl_connection!(output, assignment_obj) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
79
|
+
spinner = ::TTY::Spinner.new('[:spinner] Loading ...')
|
80
|
+
|
81
|
+
assignment_id = assignment_obj['id']
|
82
|
+
toggl_token = read_link_config['providers'][TOGGL_PROVIDER]['access_token']
|
83
|
+
|
84
|
+
spinner.auto_spin
|
85
|
+
_success, workspaces = Toggl.workspaces(toggl_token)
|
86
|
+
spinner.pause
|
87
|
+
output.puts
|
88
|
+
workspace = @prompt.select('Please select Workspace:') do |menu|
|
89
|
+
workspaces.each do |w|
|
90
|
+
menu.choice name: w['name'], value: w
|
91
|
+
end
|
92
|
+
end
|
93
|
+
workspace_id = workspace['id']
|
94
|
+
|
95
|
+
spinner.resume
|
96
|
+
_success, projects = Toggl.workspace_projects(toggl_token, workspace_id)
|
97
|
+
|
98
|
+
if projects.nil? || projects.empty?
|
99
|
+
spinner.stop
|
100
|
+
error_message = "There is no projects. Please visit #{TOGGL_PROVIDER} and create them before continue."
|
101
|
+
output.puts @pastel.red(error_message)
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
spinner.pause
|
106
|
+
output.puts
|
107
|
+
project_ids = @prompt.multi_select('Please select Projects:', min: 1) do |menu|
|
108
|
+
projects.each do |project|
|
109
|
+
menu.choice project['name'], project['id']
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
spinner.resume
|
114
|
+
tasks_objs = []
|
115
|
+
project_ids.each do |pj_id|
|
116
|
+
_success, tasks = Toggl.project_tasks(toggl_token, pj_id)
|
117
|
+
tasks_objs << tasks
|
118
|
+
end
|
119
|
+
tasks_objs.flatten!
|
120
|
+
tasks_objs.compact!
|
121
|
+
output.puts
|
122
|
+
spinner.success
|
123
|
+
tasks_ids = []
|
124
|
+
if tasks_objs.length.positive?
|
125
|
+
tasks_ids = @prompt.multi_select('Please select Tasks(by last 3 months):') do |menu|
|
126
|
+
tasks_objs.each do |to|
|
127
|
+
menu.choice to['name'], to['id']
|
128
|
+
end
|
129
|
+
end
|
130
|
+
else
|
131
|
+
output.puts @pastel.yellow("You don't have tasks. Continue...")
|
132
|
+
end
|
133
|
+
|
134
|
+
billable = @prompt.select('Billable?', %w[yes no both])
|
135
|
+
|
136
|
+
rounding = @prompt.select('Rounding?', %w[on off])
|
137
|
+
|
138
|
+
config.set("connections.#{assignment_id}.name", value: assignment_obj['name'])
|
139
|
+
config.set("connections.#{assignment_id}.service", value: assignment_obj['service'])
|
140
|
+
config.set("connections.#{assignment_id}.provider", value: TOGGL_PROVIDER)
|
141
|
+
config.set("connections.#{assignment_id}.workspace_id", value: workspace_id.to_s)
|
142
|
+
config.set("connections.#{assignment_id}.project_ids", value: project_ids.join(', '))
|
143
|
+
config.set("connections.#{assignment_id}.task_ids", value: tasks_ids.join(', '))
|
144
|
+
config.set("connections.#{assignment_id}.billable", value: billable)
|
145
|
+
config.set("connections.#{assignment_id}.rounding", value: rounding)
|
146
|
+
|
147
|
+
clear_conf_and_print_success!(output)
|
79
148
|
end
|
80
149
|
end
|
81
150
|
end
|
@@ -28,22 +28,23 @@ module Sfctl
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def print_connections(output)
|
31
|
-
config.fetch(:connections).each_key do |
|
32
|
-
case config.fetch(:connections,
|
31
|
+
config.fetch(:connections).each_key do |assignment_id|
|
32
|
+
case config.fetch(:connections, assignment_id, :provider)
|
33
33
|
when TOGGL_PROVIDER
|
34
|
-
print_toggl_connection!(output,
|
34
|
+
print_toggl_connection!(output, assignment_id)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def print_toggl_connection!(output,
|
40
|
-
output.puts "Connection: #{
|
39
|
+
def print_toggl_connection!(output, assignment_id) # rubocop:disable Metrics/AbcSize
|
40
|
+
output.puts "Connection: #{config.fetch(:connections, assignment_id, :name)}"
|
41
|
+
output.puts " service: #{config.fetch(:connections, assignment_id, :service)}"
|
41
42
|
output.puts " provider: #{TOGGL_PROVIDER}"
|
42
|
-
output.puts " workspace_id: #{config.fetch(:connections,
|
43
|
-
output.puts " project_ids: #{config.fetch(:connections,
|
44
|
-
output.puts " task_ids: #{config.fetch(:connections,
|
45
|
-
output.puts " billable: #{config.fetch(:connections,
|
46
|
-
output.puts " rounding: #{config.fetch(:connections,
|
43
|
+
output.puts " workspace_id: #{config.fetch(:connections, assignment_id, :workspace_id)}"
|
44
|
+
output.puts " project_ids: #{config.fetch(:connections, assignment_id, :project_ids)}"
|
45
|
+
output.puts " task_ids: #{config.fetch(:connections, assignment_id, :task_ids)}"
|
46
|
+
output.puts " billable: #{config.fetch(:connections, assignment_id, :billable)}"
|
47
|
+
output.puts " rounding: #{config.fetch(:connections, assignment_id, :rounding)}"
|
47
48
|
output.puts
|
48
49
|
end
|
49
50
|
end
|
@@ -12,13 +12,11 @@ module Sfctl
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def execute(output: $stdout)
|
15
|
-
|
15
|
+
return unless config_present?(output)
|
16
16
|
|
17
17
|
PROVIDERS_LIST.each do |provider|
|
18
18
|
read(provider, output)
|
19
19
|
end
|
20
|
-
rescue TTY::Config::ReadError
|
21
|
-
output.puts @pastel.yellow('Please initialize time before continue.')
|
22
20
|
end
|
23
21
|
|
24
22
|
private
|
@@ -13,7 +13,7 @@ module Sfctl
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def execute(output: $stdout)
|
16
|
-
|
16
|
+
return unless config_present?(output)
|
17
17
|
|
18
18
|
prompt = ::TTY::Prompt.new
|
19
19
|
provider = prompt.select('Setting up:', PROVIDERS_LIST)
|
@@ -24,8 +24,6 @@ module Sfctl
|
|
24
24
|
when TOGGL_PROVIDER
|
25
25
|
setup_toggl_provider!(output, prompt)
|
26
26
|
end
|
27
|
-
rescue TTY::Config::ReadError
|
28
|
-
output.puts @pastel.yellow('Please initialize time before continue.')
|
29
27
|
end
|
30
28
|
|
31
29
|
private
|
@@ -37,7 +35,7 @@ module Sfctl
|
|
37
35
|
|
38
36
|
def save_toggl_config!(output, access_token)
|
39
37
|
config.set("providers.#{TOGGL_PROVIDER}.access_token", value: access_token)
|
40
|
-
|
38
|
+
save_config!
|
41
39
|
output.puts @pastel.green('Everything saved.')
|
42
40
|
end
|
43
41
|
|
@@ -13,7 +13,7 @@ module Sfctl
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def execute(output: $stdout)
|
16
|
-
|
16
|
+
return unless config_present?(output)
|
17
17
|
|
18
18
|
prompt = ::TTY::Prompt.new
|
19
19
|
provider = prompt.select('Unsetting:', PROVIDERS_LIST)
|
@@ -23,8 +23,6 @@ module Sfctl
|
|
23
23
|
elsif prompt.yes?('Do you want to remove the delete the configuration?')
|
24
24
|
remove_provider!(provider, output)
|
25
25
|
end
|
26
|
-
rescue TTY::Config::ReadError
|
27
|
-
output.puts @pastel.yellow('Please initialize time before continue.')
|
28
26
|
end
|
29
27
|
|
30
28
|
private
|
@@ -33,7 +31,7 @@ module Sfctl
|
|
33
31
|
providers = config.fetch(:providers)
|
34
32
|
providers.delete(provider)
|
35
33
|
config.set(:providers, value: providers)
|
36
|
-
|
34
|
+
save_config!
|
37
35
|
output.puts @pastel.green("Configuration for provider [#{provider}] was successfully deleted.")
|
38
36
|
end
|
39
37
|
end
|
@@ -10,7 +10,7 @@ require_relative '../../toggl'
|
|
10
10
|
module Sfctl
|
11
11
|
module Commands
|
12
12
|
class Time
|
13
|
-
class Sync < Sfctl::Command
|
13
|
+
class Sync < Sfctl::Command
|
14
14
|
def initialize(options)
|
15
15
|
@options = options
|
16
16
|
@pastel = Pastel.new(enabled: !@options['no-color'])
|
@@ -20,31 +20,44 @@ module Sfctl
|
|
20
20
|
def execute(output: $stdout)
|
21
21
|
return if !config_present?(output) || !link_config_present?(output)
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
output.puts @pastel.red('Something went wrong. Unable to fetch assignments')
|
23
|
+
if read_link_config['connections'].length.zero?
|
24
|
+
output.puts @pastel.red('Please add a connection before continue.')
|
26
25
|
return
|
27
26
|
end
|
28
27
|
|
29
|
-
sync_assignments(output, assignments_to_sync
|
28
|
+
sync_assignments(output, assignments_to_sync)
|
30
29
|
rescue ThreadError, JSON::ParserError
|
30
|
+
output.puts
|
31
31
|
output.puts @pastel.red('Something went wrong.')
|
32
32
|
end
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
-
def
|
37
|
-
|
36
|
+
def assignments_from_connections
|
37
|
+
read_link_config['connections'].map do |con|
|
38
|
+
id = con[0]
|
39
|
+
asmnt = con[1]
|
40
|
+
{
|
41
|
+
'id' => id,
|
42
|
+
'name' => asmnt['name'],
|
43
|
+
'service' => asmnt['service'] || '-'
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def assignments_to_sync
|
49
|
+
assignments = assignments_from_connections
|
50
|
+
assignment_id = select_assignment(assignments)
|
38
51
|
|
39
|
-
return assignments if
|
52
|
+
return assignments if assignment_id == 'all'
|
40
53
|
|
41
|
-
assignments.select { |a| a['
|
54
|
+
assignments.select { |a| a['id'].to_s == assignment_id.to_s }.to_a
|
42
55
|
end
|
43
56
|
|
44
57
|
def select_assignment(assignments)
|
45
58
|
@prompt.select('Which assignment do you want to sync?') do |menu|
|
46
59
|
assignments.each do |asmnt|
|
47
|
-
menu.choice name: "#{asmnt['name']} / #{asmnt['service']}", value: asmnt['
|
60
|
+
menu.choice name: "#{asmnt['name']} / #{asmnt['service']}", value: asmnt['id'].to_s
|
48
61
|
end
|
49
62
|
menu.choice name: 'All', value: 'all'
|
50
63
|
end
|
@@ -52,15 +65,15 @@ module Sfctl
|
|
52
65
|
|
53
66
|
def sync_assignments(output, list)
|
54
67
|
list.each do |assignment|
|
55
|
-
|
56
|
-
connection = read_link_config['connections'].select { |c| c ==
|
68
|
+
assignment_id = assignment['id'].to_s
|
69
|
+
connection = read_link_config['connections'].select { |c| c == assignment_id }
|
57
70
|
|
58
71
|
if connection.empty?
|
59
|
-
output.puts @pastel.red("Unable to find a connection for assignment \"#{
|
72
|
+
output.puts @pastel.red("Unable to find a connection for assignment \"#{assignment['name']}\"")
|
60
73
|
next
|
61
74
|
end
|
62
75
|
|
63
|
-
sync(output, assignment, connection[
|
76
|
+
sync(output, assignment, connection[assignment_id])
|
64
77
|
end
|
65
78
|
end
|
66
79
|
|
@@ -133,56 +146,66 @@ module Sfctl
|
|
133
146
|
output.puts
|
134
147
|
output.puts
|
135
148
|
|
136
|
-
time_entries
|
149
|
+
time_entries['data']
|
137
150
|
end
|
138
151
|
|
139
152
|
def time_entries_table_rows(time_entries)
|
140
|
-
rows = time_entries.map do |te|
|
153
|
+
rows = time_entries['data'].sort_by { |te| te['start'] }.map do |te|
|
141
154
|
[
|
142
155
|
Date.parse(te['start']).to_s,
|
143
156
|
te['description'],
|
144
|
-
"#{humanize_duration(te['
|
157
|
+
"#{humanize_duration(te['dur'])}h"
|
145
158
|
]
|
146
159
|
end
|
147
|
-
rows.push(['Total:', '', "#{humanize_duration(time_entries
|
160
|
+
rows.push(['Total:', '', "#{humanize_duration(time_entries['total_grand'])}h"])
|
148
161
|
rows
|
149
162
|
end
|
150
163
|
|
151
164
|
def get_toggle_time_entries(next_report, connection)
|
152
|
-
_success,
|
165
|
+
_success, data = Toggl.time_entries(
|
153
166
|
read_link_config['providers'][TOGGL_PROVIDER]['access_token'], time_entries_params(next_report, connection)
|
154
167
|
)
|
155
|
-
unless connection['task_ids'].empty?
|
156
|
-
time_entries.delete_if { |te| !connection['task_ids'].include?(te['id'].to_s) }
|
157
|
-
end
|
158
168
|
|
159
|
-
|
169
|
+
data
|
160
170
|
end
|
161
171
|
|
162
172
|
def time_entries_params(next_report, connection)
|
163
173
|
start_date = Date.parse("#{next_report['year']}-#{next_report['month']}-01")
|
164
174
|
end_date = start_date.next_month.prev_day
|
165
|
-
{
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
175
|
+
params = {
|
176
|
+
workspace_id: connection['workspace_id'],
|
177
|
+
project_ids: connection['project_ids'],
|
178
|
+
billable: connection['billable'],
|
179
|
+
rounding: connection['rounding'],
|
180
|
+
since: start_date.to_s,
|
181
|
+
until: end_date.to_s
|
170
182
|
}
|
183
|
+
params[:task_ids] = connection['task_ids'] if connection['task_ids'].length.positive?
|
184
|
+
params
|
171
185
|
end
|
172
186
|
|
173
|
-
def humanize_duration(
|
187
|
+
def humanize_duration(milliseconds)
|
188
|
+
return '0' if milliseconds.nil?
|
189
|
+
|
190
|
+
seconds = milliseconds / 1000
|
174
191
|
minutes = seconds / 60
|
175
192
|
int = (minutes / 60).ceil
|
176
193
|
dec = minutes % 60
|
177
194
|
amount = (dec * 100) / 60
|
178
|
-
amount = dec.zero?
|
195
|
+
amount = if dec.zero?
|
196
|
+
''
|
197
|
+
elsif amount.to_s.length == 1
|
198
|
+
".0#{amount}"
|
199
|
+
else
|
200
|
+
".#{amount}"
|
201
|
+
end
|
179
202
|
"#{int}#{amount}"
|
180
203
|
end
|
181
204
|
|
182
205
|
def assignment_items(time_entries)
|
183
206
|
time_entries.map do |te|
|
184
207
|
{
|
185
|
-
time: humanize_duration(te['
|
208
|
+
time: humanize_duration(te['dur']).to_f,
|
186
209
|
date: Date.parse(te['start']).to_s,
|
187
210
|
comment: te['description']
|
188
211
|
}
|
data/lib/sfctl/toggl.rb
CHANGED
@@ -3,13 +3,16 @@ require 'json'
|
|
3
3
|
|
4
4
|
module Sfctl
|
5
5
|
module Toggl
|
6
|
-
|
6
|
+
DEFAULT_API_PATH = 'api/v8/'.freeze
|
7
|
+
REPORTS_API_PATH = 'reports/api/v2/'.freeze
|
8
|
+
|
9
|
+
def self.conn(token, api = 'default')
|
7
10
|
raise 'Please set toggl provider before continue.' if token.nil?
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
}
|
12
|
-
Faraday.new(url: "https://#{token}:api_token@www.toggl.com
|
12
|
+
api_path = api == 'reports' ? REPORTS_API_PATH : DEFAULT_API_PATH
|
13
|
+
|
14
|
+
headers = { 'Content-Type' => 'application/json' }
|
15
|
+
Faraday.new(url: "https://#{token}:api_token@www.toggl.com/#{api_path}", headers: headers) do |builder|
|
13
16
|
builder.request :retry
|
14
17
|
builder.adapter :net_http
|
15
18
|
end
|
@@ -19,8 +22,27 @@ module Sfctl
|
|
19
22
|
[response.status == 200, JSON.parse(response.body)]
|
20
23
|
end
|
21
24
|
|
25
|
+
def self.workspaces(token)
|
26
|
+
response = conn(token).get('workspaces')
|
27
|
+
parsed_response(response)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.workspace_projects(token, workspace_id)
|
31
|
+
response = conn(token).get("workspaces/#{workspace_id}/projects")
|
32
|
+
parsed_response(response)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.project_tasks(token, project_id)
|
36
|
+
response = conn(token).get("workspaces/#{project_id}/tasks")
|
37
|
+
|
38
|
+
return [] if response.body.length.zero?
|
39
|
+
|
40
|
+
parsed_response(response)
|
41
|
+
end
|
42
|
+
|
22
43
|
def self.time_entries(token, params)
|
23
|
-
|
44
|
+
params[:user_agent] = 'api_test'
|
45
|
+
response = conn(token, 'reports').get('details', params)
|
24
46
|
parsed_response(response)
|
25
47
|
end
|
26
48
|
end
|
data/lib/sfctl/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sfctl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Serhii Rudik
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-04-
|
12
|
+
date: 2020-04-15 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description:
|
15
15
|
email:
|