abt-cli 0.0.3 → 0.0.8

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +2 -0
  3. data/lib/abt/cli.rb +16 -7
  4. data/lib/abt/cli/dialogs.rb +18 -2
  5. data/lib/abt/cli/io.rb +8 -6
  6. data/lib/abt/docs.rb +16 -5
  7. data/lib/abt/docs/cli.rb +1 -1
  8. data/lib/abt/docs/markdown.rb +1 -1
  9. data/lib/abt/git_config.rb +55 -49
  10. data/lib/abt/providers/asana/api.rb +1 -1
  11. data/lib/abt/providers/asana/base_command.rb +9 -4
  12. data/lib/abt/providers/asana/commands/current.rb +10 -4
  13. data/lib/abt/providers/asana/commands/finalize.rb +71 -0
  14. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +2 -2
  15. data/lib/abt/providers/asana/commands/init.rb +10 -3
  16. data/lib/abt/providers/asana/commands/{pick_task.rb → pick.rb} +13 -6
  17. data/lib/abt/providers/asana/commands/projects.rb +9 -2
  18. data/lib/abt/providers/asana/commands/share.rb +29 -0
  19. data/lib/abt/providers/asana/commands/start.rb +51 -6
  20. data/lib/abt/providers/asana/commands/tasks.rb +4 -1
  21. data/lib/abt/providers/asana/configuration.rb +54 -34
  22. data/lib/abt/providers/harvest.rb +9 -51
  23. data/lib/abt/providers/harvest/api.rb +62 -0
  24. data/lib/abt/providers/harvest/base_command.rb +12 -16
  25. data/lib/abt/providers/harvest/commands/clear.rb +24 -0
  26. data/lib/abt/providers/harvest/commands/clear_global.rb +24 -0
  27. data/lib/abt/providers/harvest/commands/current.rb +83 -0
  28. data/lib/abt/providers/harvest/commands/init.rb +83 -0
  29. data/lib/abt/providers/harvest/commands/pick.rb +51 -0
  30. data/lib/abt/providers/harvest/commands/projects.rb +40 -0
  31. data/lib/abt/providers/harvest/commands/share.rb +29 -0
  32. data/lib/abt/providers/harvest/commands/start.rb +58 -0
  33. data/lib/abt/providers/harvest/commands/stop.rb +58 -0
  34. data/lib/abt/providers/harvest/commands/tasks.rb +45 -0
  35. data/lib/abt/providers/harvest/commands/track.rb +70 -0
  36. data/lib/abt/providers/harvest/configuration.rb +91 -0
  37. data/lib/abt/version.rb +1 -1
  38. metadata +18 -14
  39. data/lib/abt/harvest_client.rb +0 -58
  40. data/lib/abt/providers/asana/commands/move.rb +0 -56
  41. data/lib/abt/providers/harvest/clear.rb +0 -24
  42. data/lib/abt/providers/harvest/clear_global.rb +0 -24
  43. data/lib/abt/providers/harvest/current.rb +0 -79
  44. data/lib/abt/providers/harvest/init.rb +0 -61
  45. data/lib/abt/providers/harvest/pick_task.rb +0 -45
  46. data/lib/abt/providers/harvest/projects.rb +0 -29
  47. data/lib/abt/providers/harvest/start.rb +0 -58
  48. data/lib/abt/providers/harvest/stop.rb +0 -51
  49. data/lib/abt/providers/harvest/tasks.rb +0 -36
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Harvest
6
+ module Commands
7
+ class Start < BaseCommand
8
+ def self.command
9
+ 'start harvest[:<project-id>/<task-id>]'
10
+ end
11
+
12
+ def self.description
13
+ 'As track, but also lets the user override the current task and triggers `start` commands for other providers ' # rubocop:disable Layout/LineLength
14
+ end
15
+
16
+ def call
17
+ start_output = call_start
18
+ puts start_output
19
+
20
+ use_arg_str(arg_str_from_start_output(start_output))
21
+
22
+ maybe_override_current_task
23
+ rescue Abt::HttpError::HttpError => e
24
+ cli.warn e
25
+ cli.abort 'Unable to start tracker'
26
+ end
27
+
28
+ private
29
+
30
+ def arg_str_from_start_output(output)
31
+ output = output.split(' # ').first
32
+ output.split(':')[1]
33
+ end
34
+
35
+ def call_start
36
+ output = StringIO.new
37
+ Abt::Cli.new(argv: ['track', *cli.args], output: output).perform
38
+
39
+ output_str = output.string.strip
40
+ cli.abort 'No task provided' if output_str.empty?
41
+ output_str
42
+ end
43
+
44
+ def maybe_override_current_task
45
+ return if arg_str.nil?
46
+ return if same_args_as_config?
47
+ return unless config.local_available?
48
+ return unless cli.prompt_boolean 'Set selected task as current?'
49
+
50
+ output = StringIO.new
51
+ Abt::Cli.new(argv: ['current', "harvest:#{project_id}/#{task_id}"],
52
+ output: output).perform
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Harvest
6
+ module Commands
7
+ class Stop < BaseCommand
8
+ def self.command
9
+ 'stop harvest'
10
+ end
11
+
12
+ def self.description
13
+ 'Stop running harvest tracker'
14
+ end
15
+
16
+ def call
17
+ cli.abort 'No running time entry' if time_entry.nil?
18
+
19
+ stop_time_entry
20
+
21
+ cli.warn 'Harvest time entry stopped'
22
+ print_task(project, task)
23
+ rescue Abt::HttpError::HttpError => e
24
+ cli.warn e
25
+ cli.abort 'Unable to stop time entry'
26
+ end
27
+
28
+ private
29
+
30
+ def stop_time_entry
31
+ api.patch("time_entries/#{time_entry['id']}/stop")
32
+ end
33
+
34
+ def project
35
+ time_entry['project']
36
+ end
37
+
38
+ def task
39
+ time_entry['task']
40
+ end
41
+
42
+ def time_entry
43
+ @time_entry ||= begin
44
+ api.get_paged(
45
+ 'time_entries',
46
+ is_running: true,
47
+ user_id: config.user_id
48
+ ).first
49
+ rescue Abt::HttpError::HttpError => e # rubocop:disable Layout/RescueEnsureAlignment
50
+ cli.warn e
51
+ cli.abort 'Unable to fetch running time entry'
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Harvest
6
+ module Commands
7
+ class Tasks < BaseCommand
8
+ def self.command
9
+ 'tasks harvest'
10
+ end
11
+
12
+ def self.description
13
+ 'List available tasks on project - useful for piping into grep etc.'
14
+ end
15
+
16
+ def call
17
+ tasks.each do |task|
18
+ print_task(project, task)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def project
25
+ project_assignment['project']
26
+ end
27
+
28
+ def tasks
29
+ @tasks ||= project_assignment['task_assignments'].map { |ta| ta['task'] }
30
+ end
31
+
32
+ def project_assignment
33
+ @project_assignment ||= begin
34
+ project_assignments.find { |pa| pa['project']['id'].to_s == project_id }
35
+ end
36
+ end
37
+
38
+ def project_assignments
39
+ @project_assignments ||= api.get_paged('users/me/project_assignments')
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Harvest
6
+ module Commands
7
+ class Track < BaseCommand
8
+ def self.command
9
+ 'track harvest[:<project-id>/<task-id>]'
10
+ end
11
+
12
+ def self.description
13
+ 'Start tracker for current or specified task. Add a relevant provider to link the time entry: E.g. `abt start harvest asana`' # rubocop:disable Layout/LineLength
14
+ end
15
+
16
+ def call
17
+ abort 'No current/provided task' if task_id.nil?
18
+ cli.abort('No task selected') if task_id.nil?
19
+
20
+ print_task(created_time_entry['project'], created_time_entry['task'])
21
+
22
+ cli.warn 'Tracker successfully started'
23
+ rescue Abt::HttpError::HttpError => e
24
+ cli.abort 'Invalid task'
25
+ end
26
+
27
+ private
28
+
29
+ def created_time_entry
30
+ @created_time_entry ||= create_time_entry
31
+ end
32
+
33
+ def create_time_entry
34
+ body = {
35
+ project_id: project_id,
36
+ task_id: task_id,
37
+ user_id: config.user_id,
38
+ spent_date: Date.today.iso8601
39
+ }
40
+
41
+ if external_link_data
42
+ body.merge! external_link_data
43
+ else
44
+ cli.warn 'No external link provided'
45
+ body[:notes] ||= cli.prompt('Fill in comment (optional)')
46
+ end
47
+
48
+ api.post('time_entries', Oj.dump(body, mode: :json))
49
+ end
50
+
51
+ def external_link_data
52
+ @external_link_data ||= begin
53
+ arg_strs = cli.args.join(' ')
54
+ lines = `#{$PROGRAM_NAME} harvest-time-entry-data #{arg_strs}`.split("\n")
55
+
56
+ return if lines.empty?
57
+
58
+ # TODO: Make user choose which reference to use by printing the urls
59
+ if lines.length > 1
60
+ cli.abort('Multiple providers had harvest reference data, only one is supported at a time') # rubocop:disable Layout/LineLength
61
+ end
62
+
63
+ Oj.load(lines.first)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Harvest
6
+ class Configuration
7
+ attr_accessor :cli
8
+
9
+ def initialize(cli:)
10
+ @cli = cli
11
+ @git = GitConfig.new(namespace: 'abt.harvest')
12
+ end
13
+
14
+ def local_available?
15
+ GitConfig.local_available?
16
+ end
17
+
18
+ def project_id
19
+ local_available? ? git['projectId'] : nil
20
+ end
21
+
22
+ def task_id
23
+ local_available? ? git['taskId'] : nil
24
+ end
25
+
26
+ def project_id=(value)
27
+ value = value.to_s unless value.nil?
28
+ return if project_id == value
29
+
30
+ clear_local
31
+ git['projectId'] = value
32
+ end
33
+
34
+ def task_id=(value)
35
+ value = value.to_s unless value.nil?
36
+ git['taskId'] = value
37
+ end
38
+
39
+ def clear_local
40
+ cli.abort 'No local configuration was found' unless local_available?
41
+
42
+ git['projectId'] = nil
43
+ git['taskId'] = nil
44
+ end
45
+
46
+ def clear_global
47
+ git.global['userId'] = nil
48
+ git.global['accountId'] = nil
49
+ git.global['accessToken'] = nil
50
+ end
51
+
52
+ def access_token
53
+ return git.global['accessToken'] unless git.global['accessToken'].nil?
54
+
55
+ git.global['accessToken'] = cli.prompt([
56
+ 'Please provide your personal access token for Harvest.',
57
+ 'If you don\'t have one, create one here: https://id.getharvest.com/developers',
58
+ '',
59
+ 'Enter access token'
60
+ ].join("\n"))
61
+ end
62
+
63
+ def account_id
64
+ return git.global['accountId'] unless git.global['accountId'].nil?
65
+
66
+ git.global['accountId'] = cli.prompt([
67
+ 'Please provide harvest account id.',
68
+ 'This information is shown next to your generated access token',
69
+ '',
70
+ 'Enter account id'
71
+ ].join("\n"))
72
+ end
73
+
74
+ def user_id
75
+ return git.global['userId'] unless git.global['userId'].nil?
76
+
77
+ git.global['userId'] = api.get('users/me')['id'].to_s
78
+ end
79
+
80
+ private
81
+
82
+ attr_reader :git
83
+
84
+ def api
85
+ @api ||=
86
+ Abt::Providers::Harvest::Api.new(access_token: access_token, account_id: account_id)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Abt
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.8'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abt-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesper Sørensen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-14 00:00:00.000000000 Z
11
+ date: 2021-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-inflector
@@ -82,7 +82,6 @@ files:
82
82
  - "./lib/abt/docs/cli.rb"
83
83
  - "./lib/abt/docs/markdown.rb"
84
84
  - "./lib/abt/git_config.rb"
85
- - "./lib/abt/harvest_client.rb"
86
85
  - "./lib/abt/helpers.rb"
87
86
  - "./lib/abt/http_error.rb"
88
87
  - "./lib/abt/providers.rb"
@@ -92,25 +91,30 @@ files:
92
91
  - "./lib/abt/providers/asana/commands/clear.rb"
93
92
  - "./lib/abt/providers/asana/commands/clear_global.rb"
94
93
  - "./lib/abt/providers/asana/commands/current.rb"
94
+ - "./lib/abt/providers/asana/commands/finalize.rb"
95
95
  - "./lib/abt/providers/asana/commands/harvest_time_entry_data.rb"
96
96
  - "./lib/abt/providers/asana/commands/init.rb"
97
- - "./lib/abt/providers/asana/commands/move.rb"
98
- - "./lib/abt/providers/asana/commands/pick_task.rb"
97
+ - "./lib/abt/providers/asana/commands/pick.rb"
99
98
  - "./lib/abt/providers/asana/commands/projects.rb"
99
+ - "./lib/abt/providers/asana/commands/share.rb"
100
100
  - "./lib/abt/providers/asana/commands/start.rb"
101
101
  - "./lib/abt/providers/asana/commands/tasks.rb"
102
102
  - "./lib/abt/providers/asana/configuration.rb"
103
103
  - "./lib/abt/providers/harvest.rb"
104
+ - "./lib/abt/providers/harvest/api.rb"
104
105
  - "./lib/abt/providers/harvest/base_command.rb"
105
- - "./lib/abt/providers/harvest/clear.rb"
106
- - "./lib/abt/providers/harvest/clear_global.rb"
107
- - "./lib/abt/providers/harvest/current.rb"
108
- - "./lib/abt/providers/harvest/init.rb"
109
- - "./lib/abt/providers/harvest/pick_task.rb"
110
- - "./lib/abt/providers/harvest/projects.rb"
111
- - "./lib/abt/providers/harvest/start.rb"
112
- - "./lib/abt/providers/harvest/stop.rb"
113
- - "./lib/abt/providers/harvest/tasks.rb"
106
+ - "./lib/abt/providers/harvest/commands/clear.rb"
107
+ - "./lib/abt/providers/harvest/commands/clear_global.rb"
108
+ - "./lib/abt/providers/harvest/commands/current.rb"
109
+ - "./lib/abt/providers/harvest/commands/init.rb"
110
+ - "./lib/abt/providers/harvest/commands/pick.rb"
111
+ - "./lib/abt/providers/harvest/commands/projects.rb"
112
+ - "./lib/abt/providers/harvest/commands/share.rb"
113
+ - "./lib/abt/providers/harvest/commands/start.rb"
114
+ - "./lib/abt/providers/harvest/commands/stop.rb"
115
+ - "./lib/abt/providers/harvest/commands/tasks.rb"
116
+ - "./lib/abt/providers/harvest/commands/track.rb"
117
+ - "./lib/abt/providers/harvest/configuration.rb"
114
118
  - "./lib/abt/version.rb"
115
119
  - bin/abt
116
120
  homepage: https://github.com/abtion/abt
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- class HarvestClient
5
- API_ENDPOINT = 'https://api.harvestapp.com/v2'
6
- VERBS = %i[get post patch].freeze
7
-
8
- attr_reader :access_token, :account_id
9
-
10
- def initialize(access_token:, account_id:)
11
- @access_token = access_token
12
- @account_id = account_id
13
- end
14
-
15
- VERBS.each do |verb|
16
- define_method(verb) do |*args|
17
- request(verb, *args)
18
- end
19
- end
20
-
21
- def get_paged(path, query = {})
22
- result_key = path.split('?').first.split('/').last
23
-
24
- page = 1
25
- records = []
26
-
27
- loop do
28
- result = get(path, query.merge(page: page))
29
- records += result[result_key]
30
- break if result['total_pages'] == page
31
-
32
- page += 1
33
- end
34
-
35
- records
36
- end
37
-
38
- def request(*args)
39
- response = connection.public_send(*args)
40
-
41
- if response.success?
42
- Oj.load(response.body)
43
- else
44
- error_class = Abt::HttpError.error_class_for_status(response.status)
45
- encoded_response_body = response.body.force_encoding('utf-8')
46
- raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
47
- end
48
- end
49
-
50
- def connection
51
- @connection ||= Faraday.new(API_ENDPOINT) do |connection|
52
- connection.headers['Authorization'] = "Bearer #{access_token}"
53
- connection.headers['Harvest-Account-Id'] = account_id
54
- connection.headers['Content-Type'] = 'application/json'
55
- end
56
- end
57
- end
58
- end