sfctl 0.0.1
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 +7 -0
- data/.buildkite/hooks/pre-command +5 -0
- data/.buildkite/pipeline.yml +9 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +76 -0
- data/.tool-versions +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +40 -0
- data/Gemfile.lock +186 -0
- data/README.md +276 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/sfctl +18 -0
- data/lib/sfctl.rb +5 -0
- data/lib/sfctl/cli.rb +44 -0
- data/lib/sfctl/command.rb +166 -0
- data/lib/sfctl/commands/account.rb +34 -0
- data/lib/sfctl/commands/account/assignments.rb +52 -0
- data/lib/sfctl/commands/account/info.rb +40 -0
- data/lib/sfctl/commands/auth.rb +35 -0
- data/lib/sfctl/commands/auth/bye.rb +26 -0
- data/lib/sfctl/commands/auth/init.rb +53 -0
- data/lib/sfctl/commands/time.rb +52 -0
- data/lib/sfctl/commands/time/connections.rb +33 -0
- data/lib/sfctl/commands/time/connections/add.rb +84 -0
- data/lib/sfctl/commands/time/connections/get.rb +53 -0
- data/lib/sfctl/commands/time/init.rb +23 -0
- data/lib/sfctl/commands/time/providers.rb +44 -0
- data/lib/sfctl/commands/time/providers/get.rb +41 -0
- data/lib/sfctl/commands/time/providers/set.rb +58 -0
- data/lib/sfctl/commands/time/providers/unset.rb +43 -0
- data/lib/sfctl/commands/time/sync.rb +215 -0
- data/lib/sfctl/starfish.rb +51 -0
- data/lib/sfctl/templates/.gitkeep +1 -0
- data/lib/sfctl/toggl.rb +27 -0
- data/lib/sfctl/version.rb +3 -0
- data/scripts/test.sh +8 -0
- data/sfctl.gemspec +28 -0
- metadata +84 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
require_relative '../../starfish'
|
5
|
+
require 'pastel'
|
6
|
+
require 'tty-spinner'
|
7
|
+
|
8
|
+
module Sfctl
|
9
|
+
module Commands
|
10
|
+
class Auth
|
11
|
+
class Init < Sfctl::Command
|
12
|
+
def initialize(access_token, options)
|
13
|
+
@access_token = access_token
|
14
|
+
@options = options
|
15
|
+
|
16
|
+
@pastel = Pastel.new(enabled: !@options['no-color'])
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute(output: $stdout)
|
20
|
+
spinner = ::TTY::Spinner.new('[:spinner] Checking token ...')
|
21
|
+
spinner.auto_spin
|
22
|
+
token_valid? ? update_config!(spinner, output) : render_error(spinner, output)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def token_valid?
|
28
|
+
Starfish.check_authorization(@options['starfish-host'], @access_token)
|
29
|
+
end
|
30
|
+
|
31
|
+
def token_accepted_message
|
32
|
+
@pastel.green('Credentials are accepted.')
|
33
|
+
end
|
34
|
+
|
35
|
+
def wrong_token_message
|
36
|
+
@pastel.red('Token is not accepted, please make sure you copy and paste it correctly.')
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_config!(spinner, output)
|
40
|
+
config.set :access_token, value: @access_token
|
41
|
+
save_config!
|
42
|
+
spinner.success
|
43
|
+
output.puts token_accepted_message
|
44
|
+
end
|
45
|
+
|
46
|
+
def render_error(spinner, output)
|
47
|
+
spinner.error
|
48
|
+
output.puts wrong_token_message
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Sfctl
|
4
|
+
module Commands
|
5
|
+
class Time < Thor
|
6
|
+
namespace :time
|
7
|
+
|
8
|
+
desc 'sync', 'Synchronize data with providers.'
|
9
|
+
method_option :help, aliases: '-h', type: :boolean,
|
10
|
+
desc: 'Display usage information'
|
11
|
+
method_option :dry_run, aliases: '-dry-run', type: :boolean, default: false,
|
12
|
+
desc: 'Check the data first respectively prevent data from being overwritten'
|
13
|
+
method_option :touchy, aliases: '-touchy', type: :boolean, default: false,
|
14
|
+
desc: 'The synchronizsation will be skipped if there is preexisting data.'
|
15
|
+
long_desc <<~HEREDOC
|
16
|
+
It will gets for each assignment the next reporting segment from starfish.team
|
17
|
+
and loads the corresponding time reports from the provider.
|
18
|
+
HEREDOC
|
19
|
+
def sync(*)
|
20
|
+
if options[:help]
|
21
|
+
invoke :help, ['sync']
|
22
|
+
else
|
23
|
+
require_relative 'time/sync'
|
24
|
+
Sfctl::Commands::Time::Sync.new(options).execute
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'init',
|
29
|
+
'You can use the following command to create a .sflink file that will store your project configuration.'
|
30
|
+
long_desc <<~HEREDOC
|
31
|
+
You can use the following command to create a .sflink file that will store your project configuration.\n
|
32
|
+
Although sensitive data is stored in the main .sfctl directory
|
33
|
+
we'd like to recommend to not add the .sflink file to your version control system.
|
34
|
+
HEREDOC
|
35
|
+
method_option :help, aliases: '-h', type: :boolean
|
36
|
+
def init(*)
|
37
|
+
if options[:help]
|
38
|
+
invoke :help, ['init']
|
39
|
+
else
|
40
|
+
require_relative 'time/init'
|
41
|
+
Sfctl::Commands::Time::Init.new(options).execute
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
require_relative 'time/providers'
|
46
|
+
register Sfctl::Commands::Time::Providers, 'providers', 'providers [SUBCOMMAND]', 'Manage providers.'
|
47
|
+
|
48
|
+
require_relative 'time/connections'
|
49
|
+
register Sfctl::Commands::Time::Connections, 'connections', 'connections [SUBCOMMAND]', 'Manage connections.'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Sfctl
|
4
|
+
module Commands
|
5
|
+
class Time
|
6
|
+
class Connections < Thor
|
7
|
+
namespace :connections
|
8
|
+
|
9
|
+
desc 'add', 'This command will add a connection between a provider and an assignment.'
|
10
|
+
method_option :help, aliases: '-h', type: :boolean
|
11
|
+
def add(*)
|
12
|
+
if options[:help]
|
13
|
+
invoke :help, ['add']
|
14
|
+
else
|
15
|
+
require_relative 'connections/add'
|
16
|
+
Sfctl::Commands::Time::Connections::Add.new(options).execute
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'get', 'List all known connections in that project.'
|
21
|
+
method_option :help, aliases: '-h', type: :boolean
|
22
|
+
def get(*)
|
23
|
+
if options[:help]
|
24
|
+
invoke :help, ['get']
|
25
|
+
else
|
26
|
+
require_relative 'connections/get'
|
27
|
+
Sfctl::Commands::Time::Connections::Get.new(options).execute
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'pastel'
|
2
|
+
require 'tty-prompt'
|
3
|
+
require_relative '../../../command'
|
4
|
+
require_relative '../../../starfish'
|
5
|
+
|
6
|
+
module Sfctl
|
7
|
+
module Commands
|
8
|
+
class Time
|
9
|
+
class Connections
|
10
|
+
class Add < Sfctl::Command
|
11
|
+
def initialize(options)
|
12
|
+
@options = options
|
13
|
+
@pastel = Pastel.new(enabled: !@options['no-color'])
|
14
|
+
@prompt = ::TTY::Prompt.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute(output: $stdout) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
18
|
+
return if !config_present?(output) || !link_config_present?(output)
|
19
|
+
|
20
|
+
ltoken = access_token
|
21
|
+
config.delete(:access_token)
|
22
|
+
success, data = Starfish.account_assignments(@options['starfish-host'], @options['all'], ltoken)
|
23
|
+
unless success
|
24
|
+
output.puts @pastel.red('Something went wrong. Unable to fetch assignments')
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
assignments = filter_assignments(data['assignments'])
|
29
|
+
if assignments.length.zero?
|
30
|
+
output.puts @pastel.yellow('All assignments already added.')
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
provider = @prompt.select('Select provider:', PROVIDERS_LIST)
|
35
|
+
|
36
|
+
assignment_name = select_assignment(assignments)
|
37
|
+
|
38
|
+
case provider
|
39
|
+
when TOGGL_PROVIDER
|
40
|
+
setup_toggl_connection!(assignment_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
save_link_config!
|
44
|
+
|
45
|
+
output.puts @pastel.green('Connection successfully added.')
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def select_assignment(assignments)
|
51
|
+
@prompt.select('Select assignment:') do |menu|
|
52
|
+
assignments.each.with_index do |asmnt, i|
|
53
|
+
menu.choice name: "#{i + 1}. #{asmnt['name']} / #{asmnt['service']}", value: asmnt['name']
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def filter_assignments(list)
|
59
|
+
return list if config.fetch(:connections).nil?
|
60
|
+
|
61
|
+
added_assignments_name = config.fetch(:connections).keys
|
62
|
+
list.delete_if { |h| added_assignments_name.include?(h['name']) }
|
63
|
+
list
|
64
|
+
end
|
65
|
+
|
66
|
+
def setup_toggl_connection!(assignment_name)
|
67
|
+
workspace_id = @prompt.ask('Workspace ID (required):', required: true)
|
68
|
+
project_ids = @prompt.ask('Project IDs (required / comma separated):', required: true)
|
69
|
+
task_ids = @prompt.ask('Task IDs (optional / comma separated):') || ''
|
70
|
+
billable = @prompt.select('Billable? (required)', %w[yes no both])
|
71
|
+
rounding = @prompt.select('Rounding? (required)', %w[on off])
|
72
|
+
|
73
|
+
config.set("connections.#{assignment_name}.provider", value: TOGGL_PROVIDER)
|
74
|
+
config.set("connections.#{assignment_name}.workspace_id", value: workspace_id)
|
75
|
+
config.set("connections.#{assignment_name}.project_ids", value: project_ids)
|
76
|
+
config.set("connections.#{assignment_name}.task_ids", value: task_ids)
|
77
|
+
config.set("connections.#{assignment_name}.billable", value: billable)
|
78
|
+
config.set("connections.#{assignment_name}.rounding", value: rounding)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'pastel'
|
2
|
+
require_relative '../../../command'
|
3
|
+
|
4
|
+
module Sfctl
|
5
|
+
module Commands
|
6
|
+
class Time
|
7
|
+
class Connections
|
8
|
+
class Get < Sfctl::Command
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
@pastel = Pastel.new(enabled: !@options['no-color'])
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(output: $stdout)
|
15
|
+
read_link_config
|
16
|
+
|
17
|
+
if config.fetch(:connections).nil?
|
18
|
+
output.puts @pastel.yellow('You have no connections. Please add them before continue.')
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
print_connections(output)
|
23
|
+
rescue TTY::Config::ReadError
|
24
|
+
error_message = 'Please initialize time before continue and ensure that your account authenticated.'
|
25
|
+
output.puts @pastel.yellow(error_message)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def print_connections(output)
|
31
|
+
config.fetch(:connections).each_key do |assignment_name|
|
32
|
+
case config.fetch(:connections, assignment_name, :provider)
|
33
|
+
when TOGGL_PROVIDER
|
34
|
+
print_toggl_connection!(output, assignment_name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def print_toggl_connection!(output, assignment_name)
|
40
|
+
output.puts "Connection: #{assignment_name}"
|
41
|
+
output.puts " provider: #{TOGGL_PROVIDER}"
|
42
|
+
output.puts " workspace_id: #{config.fetch(:connections, assignment_name, :workspace_id)}"
|
43
|
+
output.puts " project_ids: #{config.fetch(:connections, assignment_name, :project_ids)}"
|
44
|
+
output.puts " task_ids: #{config.fetch(:connections, assignment_name, :task_ids)}"
|
45
|
+
output.puts " billable: #{config.fetch(:connections, assignment_name, :billable)}"
|
46
|
+
output.puts " rounding: #{config.fetch(:connections, assignment_name, :rounding)}"
|
47
|
+
output.puts
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative '../../command'
|
2
|
+
|
3
|
+
module Sfctl
|
4
|
+
module Commands
|
5
|
+
class Time
|
6
|
+
class Init < Sfctl::Command
|
7
|
+
def initialize(options)
|
8
|
+
@options = options
|
9
|
+
|
10
|
+
@pastel = Pastel.new(enabled: !@options['no-color'])
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(output: $stdout)
|
14
|
+
read_link_config
|
15
|
+
output.puts @pastel.yellow('.sflink is already created.')
|
16
|
+
rescue ::TTY::Config::ReadError
|
17
|
+
save_link_config!
|
18
|
+
output.puts @pastel.green('.sflink successfully created.')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Sfctl
|
4
|
+
module Commands
|
5
|
+
class Time
|
6
|
+
class Providers < Thor
|
7
|
+
namespace :providers
|
8
|
+
|
9
|
+
desc 'set', 'Set the configuration required for the provider to authenticate a call to their API.'
|
10
|
+
method_option :help, aliases: '-h', type: :boolean
|
11
|
+
def set(*)
|
12
|
+
if options[:help]
|
13
|
+
invoke :help, ['set']
|
14
|
+
else
|
15
|
+
require_relative 'providers/set'
|
16
|
+
Sfctl::Commands::Time::Providers::Set.new(options).execute
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'unset', 'Unset the configuration of a provider.'
|
21
|
+
method_option :help, aliases: '-h', type: :boolean
|
22
|
+
def unset(*)
|
23
|
+
if options[:help]
|
24
|
+
invoke :help, ['unset']
|
25
|
+
else
|
26
|
+
require_relative 'providers/unset'
|
27
|
+
Sfctl::Commands::Time::Providers::Unset.new(options).execute
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'get', 'Read which providers are configured on your system.'
|
32
|
+
method_option :help, aliases: '-h', type: :boolean
|
33
|
+
def get(*)
|
34
|
+
if options[:help]
|
35
|
+
invoke :help, ['get']
|
36
|
+
else
|
37
|
+
require_relative 'providers/get'
|
38
|
+
Sfctl::Commands::Time::Providers::Get.new(options).execute
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'pastel'
|
2
|
+
require_relative '../../../command'
|
3
|
+
|
4
|
+
module Sfctl
|
5
|
+
module Commands
|
6
|
+
class Time
|
7
|
+
class Providers
|
8
|
+
class Get < Sfctl::Command
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
@pastel = Pastel.new(enabled: !@options['no-color'])
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(output: $stdout)
|
15
|
+
read_link_config
|
16
|
+
|
17
|
+
PROVIDERS_LIST.each do |provider|
|
18
|
+
read(provider, output)
|
19
|
+
end
|
20
|
+
rescue TTY::Config::ReadError
|
21
|
+
output.puts @pastel.yellow('Please initialize time before continue.')
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def read(provider, output)
|
27
|
+
info = config.fetch("providers.#{provider}")
|
28
|
+
if info.nil?
|
29
|
+
output.puts @pastel.yellow("Provider #{provider} is not set.")
|
30
|
+
else
|
31
|
+
output.puts "Provider: #{@pastel.cyan(provider)}"
|
32
|
+
info.each_key do |k|
|
33
|
+
output.puts " #{k.upcase}: #{info[k]}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'pastel'
|
2
|
+
require 'tty-prompt'
|
3
|
+
require_relative '../../../command'
|
4
|
+
|
5
|
+
module Sfctl
|
6
|
+
module Commands
|
7
|
+
class Time
|
8
|
+
class Providers
|
9
|
+
class Set < Sfctl::Command
|
10
|
+
def initialize(options)
|
11
|
+
@options = options
|
12
|
+
@pastel = Pastel.new(enabled: !@options['no-color'])
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(output: $stdout)
|
16
|
+
read_link_config
|
17
|
+
|
18
|
+
prompt = ::TTY::Prompt.new
|
19
|
+
provider = prompt.select('Setting up:', PROVIDERS_LIST)
|
20
|
+
|
21
|
+
!ask_for_replace(output, prompt) && return unless config.fetch("providers.#{TOGGL_PROVIDER}").nil?
|
22
|
+
|
23
|
+
case provider
|
24
|
+
when TOGGL_PROVIDER
|
25
|
+
setup_toggl_provider!(output, prompt)
|
26
|
+
end
|
27
|
+
rescue TTY::Config::ReadError
|
28
|
+
output.puts @pastel.yellow('Please initialize time before continue.')
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def ask_for_replace(output, prompt)
|
34
|
+
output.puts @pastel.yellow('You already have a configuration for this provider.')
|
35
|
+
prompt.yes?('Do you want to replace it?')
|
36
|
+
end
|
37
|
+
|
38
|
+
def save_toggl_config!(output, access_token)
|
39
|
+
config.set("providers.#{TOGGL_PROVIDER}.access_token", value: access_token)
|
40
|
+
save_link_config!
|
41
|
+
output.puts @pastel.green('Everything saved.')
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_toggl_provider!(output, prompt)
|
45
|
+
output.puts
|
46
|
+
access_token = prompt.ask("Your access token at [#{@pastel.green(TOGGL_PROVIDER)}]:", required: true)
|
47
|
+
is_correct = prompt.yes?('Is that information correct?')
|
48
|
+
if is_correct
|
49
|
+
save_toggl_config!(output, access_token)
|
50
|
+
else
|
51
|
+
setup_toggl_provider!(output, prompt)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|