sfctl 0.0.1 → 0.0.2
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/.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:
|