abt-cli 0.0.2 → 0.0.3

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +3 -1
  3. data/lib/abt.rb +11 -0
  4. data/lib/abt/cli.rb +25 -29
  5. data/lib/abt/cli/dialogs.rb +2 -2
  6. data/lib/abt/cli/io.rb +21 -0
  7. data/lib/abt/{help.rb → docs.rb} +8 -14
  8. data/lib/abt/{help → docs}/cli.rb +3 -3
  9. data/lib/abt/{help → docs}/markdown.rb +3 -3
  10. data/lib/abt/helpers.rb +16 -0
  11. data/lib/abt/providers/asana.rb +9 -50
  12. data/lib/abt/providers/asana/api.rb +57 -0
  13. data/lib/abt/providers/asana/base_command.rb +5 -12
  14. data/lib/abt/providers/asana/commands/clear.rb +24 -0
  15. data/lib/abt/providers/asana/commands/clear_global.rb +24 -0
  16. data/lib/abt/providers/asana/commands/current.rb +71 -0
  17. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +50 -0
  18. data/lib/abt/providers/asana/commands/init.rb +63 -0
  19. data/lib/abt/providers/asana/commands/move.rb +56 -0
  20. data/lib/abt/providers/asana/commands/pick_task.rb +48 -0
  21. data/lib/abt/providers/asana/commands/projects.rb +32 -0
  22. data/lib/abt/providers/asana/commands/start.rb +60 -0
  23. data/lib/abt/providers/asana/commands/tasks.rb +37 -0
  24. data/lib/abt/providers/asana/configuration.rb +105 -0
  25. data/lib/abt/providers/harvest.rb +9 -0
  26. data/lib/abt/version.rb +1 -1
  27. metadata +18 -15
  28. data/lib/abt/asana_client.rb +0 -53
  29. data/lib/abt/providers/asana/clear.rb +0 -24
  30. data/lib/abt/providers/asana/clear_global.rb +0 -24
  31. data/lib/abt/providers/asana/current.rb +0 -69
  32. data/lib/abt/providers/asana/harvest_link_entry_data.rb +0 -48
  33. data/lib/abt/providers/asana/init.rb +0 -62
  34. data/lib/abt/providers/asana/move.rb +0 -54
  35. data/lib/abt/providers/asana/pick_task.rb +0 -46
  36. data/lib/abt/providers/asana/projects.rb +0 -30
  37. data/lib/abt/providers/asana/start.rb +0 -22
  38. data/lib/abt/providers/asana/tasks.rb +0 -35
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Asana
6
+ class Configuration
7
+ attr_accessor :cli
8
+
9
+ def initialize(cli:)
10
+ @cli = cli
11
+ end
12
+
13
+ def project_gid
14
+ Abt::GitConfig.local('abt.asana.projectGid')
15
+ end
16
+
17
+ def task_gid
18
+ Abt::GitConfig.local('abt.asana.taskGid')
19
+ end
20
+
21
+ def workspace_gid
22
+ @workspace_gid ||= begin
23
+ current = Abt::GitConfig.global('abt.asana.workspaceGid')
24
+ if current.nil?
25
+ prompt_workspace['gid']
26
+ else
27
+ current
28
+ end
29
+ end
30
+ end
31
+
32
+ def wip_section_gid
33
+ @wip_section_gid ||= begin
34
+ current = Abt::GitConfig.global('abt.asana.wipSectionGid')
35
+ if current.nil?
36
+ prompt_wip_section['gid']
37
+ else
38
+ current
39
+ end
40
+ end
41
+ end
42
+
43
+ def project_gid=(value)
44
+ return if project_gid == value
45
+
46
+ clear_local
47
+ Abt::GitConfig.local('abt.asana.projectGid', value) unless value.nil?
48
+ end
49
+
50
+ def task_gid=(value)
51
+ if value.nil?
52
+ Abt::GitConfig.unset_local('abt.asana.taskGid')
53
+ elsif task_gid != value
54
+ Abt::GitConfig.local('abt.asana.taskGid', value)
55
+ end
56
+ end
57
+
58
+ def clear_local
59
+ Abt::GitConfig.unset_local('abt.asana.projectGid')
60
+ Abt::GitConfig.unset_local('abt.asana.taskGid')
61
+ Abt::GitConfig.unset_local('abt.asana.wipSectionGid')
62
+ end
63
+
64
+ def clear_global
65
+ Abt::GitConfig.unset_global('abt.asana.workspaceGid')
66
+ Abt::GitConfig.unset_global('abt.asana.accessToken')
67
+ end
68
+
69
+ def access_token
70
+ Abt::GitConfig.prompt_global(
71
+ 'abt.asana.accessToken',
72
+ 'Please enter your personal asana access_token',
73
+ 'Create a personal access token here: https://app.asana.com/0/developer-console'
74
+ )
75
+ end
76
+
77
+ private
78
+
79
+ def prompt_wip_section
80
+ sections = api.get_paged("projects/#{project_gid}/sections")
81
+
82
+ section = cli.prompt_choice('Select WIP (Work In Progress) section', sections)
83
+ Abt::GitConfig.global('abt.asana.wipSectionGid', section['gid'])
84
+ section
85
+ end
86
+
87
+ def prompt_workspace
88
+ workspaces = api.get_paged('workspaces')
89
+ if workspaces.empty?
90
+ cli.abort 'Your asana access token does not have access to any workspaces'
91
+ end
92
+
93
+ # TODO: Handle if there are multiple workspaces
94
+ workspace = workspaces.first
95
+ Abt::GitConfig.global('abt.asana.workspaceGid', workspace['gid'])
96
+ workspace
97
+ end
98
+
99
+ def api
100
+ Abt::Providers::Asana::Api.new(access_token: access_token)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -8,6 +8,15 @@ module Abt
8
8
  module Providers
9
9
  class Harvest
10
10
  class << self
11
+ def command_names
12
+ constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
13
+ end
14
+
15
+ def command_class(name)
16
+ const_name = Helpers.command_to_const(name)
17
+ const_get(const_name) if const_defined?(const_name)
18
+ end
19
+
11
20
  def user_id
12
21
  Abt::GitConfig.prompt_global(
13
22
  'abt.harvest.userId',
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Abt
4
- VERSION = '0.0.2'
4
+ VERSION = '0.0.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abt-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesper Sørensen
@@ -75,28 +75,31 @@ extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
77
  - "./lib/abt.rb"
78
- - "./lib/abt/asana_client.rb"
79
78
  - "./lib/abt/cli.rb"
80
79
  - "./lib/abt/cli/dialogs.rb"
80
+ - "./lib/abt/cli/io.rb"
81
+ - "./lib/abt/docs.rb"
82
+ - "./lib/abt/docs/cli.rb"
83
+ - "./lib/abt/docs/markdown.rb"
81
84
  - "./lib/abt/git_config.rb"
82
85
  - "./lib/abt/harvest_client.rb"
83
- - "./lib/abt/help.rb"
84
- - "./lib/abt/help/cli.rb"
85
- - "./lib/abt/help/markdown.rb"
86
+ - "./lib/abt/helpers.rb"
86
87
  - "./lib/abt/http_error.rb"
87
88
  - "./lib/abt/providers.rb"
88
89
  - "./lib/abt/providers/asana.rb"
90
+ - "./lib/abt/providers/asana/api.rb"
89
91
  - "./lib/abt/providers/asana/base_command.rb"
90
- - "./lib/abt/providers/asana/clear.rb"
91
- - "./lib/abt/providers/asana/clear_global.rb"
92
- - "./lib/abt/providers/asana/current.rb"
93
- - "./lib/abt/providers/asana/harvest_link_entry_data.rb"
94
- - "./lib/abt/providers/asana/init.rb"
95
- - "./lib/abt/providers/asana/move.rb"
96
- - "./lib/abt/providers/asana/pick_task.rb"
97
- - "./lib/abt/providers/asana/projects.rb"
98
- - "./lib/abt/providers/asana/start.rb"
99
- - "./lib/abt/providers/asana/tasks.rb"
92
+ - "./lib/abt/providers/asana/commands/clear.rb"
93
+ - "./lib/abt/providers/asana/commands/clear_global.rb"
94
+ - "./lib/abt/providers/asana/commands/current.rb"
95
+ - "./lib/abt/providers/asana/commands/harvest_time_entry_data.rb"
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"
99
+ - "./lib/abt/providers/asana/commands/projects.rb"
100
+ - "./lib/abt/providers/asana/commands/start.rb"
101
+ - "./lib/abt/providers/asana/commands/tasks.rb"
102
+ - "./lib/abt/providers/asana/configuration.rb"
100
103
  - "./lib/abt/providers/harvest.rb"
101
104
  - "./lib/abt/providers/harvest/base_command.rb"
102
105
  - "./lib/abt/providers/harvest/clear.rb"
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- class AsanaClient
5
- API_ENDPOINT = 'https://app.asana.com/api/1.0'
6
- VERBS = %i[get post patch].freeze
7
-
8
- attr_reader :access_token
9
-
10
- def initialize(access_token:)
11
- @access_token = access_token
12
- end
13
-
14
- VERBS.each do |verb|
15
- define_method(verb) do |*args|
16
- request(verb, *args)['data']
17
- end
18
- end
19
-
20
- def get_paged(path, query = {})
21
- records = []
22
-
23
- loop do
24
- result = request(:get, path, query.merge(limit: 100))
25
- records += result['data']
26
- break if result['next_page'].nil?
27
-
28
- path = result['next_page']['path'][1..-1]
29
- end
30
-
31
- records
32
- end
33
-
34
- def request(*args)
35
- response = connection.public_send(*args)
36
-
37
- if response.success?
38
- Oj.load(response.body)
39
- else
40
- error_class = Abt::HttpError.error_class_for_status(response.status)
41
- encoded_response_body = response.body.force_encoding('utf-8')
42
- raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
43
- end
44
- end
45
-
46
- def connection
47
- @connection ||= Faraday.new(API_ENDPOINT) do |connection|
48
- connection.headers['Authorization'] = "Bearer #{access_token}"
49
- connection.headers['Content-Type'] = 'application/json'
50
- end
51
- end
52
- end
53
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class Clear
7
- def self.command
8
- 'clear asana'
9
- end
10
-
11
- def self.description
12
- 'Clear project/task for current git repository'
13
- end
14
-
15
- def initialize(**); end
16
-
17
- def call
18
- warn 'Clearing Asana project configuration'
19
- Asana.clear
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class ClearGlobal
7
- def self.command
8
- 'clear-global asana'
9
- end
10
-
11
- def self.description
12
- 'Clear all global configuration'
13
- end
14
-
15
- def initialize(**); end
16
-
17
- def call
18
- warn 'Clearing Asana project configuration'
19
- Asana.clear_global
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class Current < BaseCommand
7
- def self.command
8
- 'current asana[:<project-gid>[/<task-gid>]]'
9
- end
10
-
11
- def self.description
12
- 'Get or set project and or task for current git repository'
13
- end
14
-
15
- def call
16
- if arg_str.nil?
17
- show_current_configuration
18
- else
19
- warn 'Updating configuration'
20
- update_configuration
21
- end
22
- end
23
-
24
- private
25
-
26
- def show_current_configuration
27
- if project_gid.nil?
28
- warn 'No project selected'
29
- elsif task_gid.nil?
30
- print_project(project)
31
- else
32
- print_task(project, task)
33
- end
34
- end
35
-
36
- def update_configuration
37
- ensure_project_is_valid!
38
- remember_project_gid(project_gid)
39
-
40
- if task_gid.nil?
41
- print_project(project)
42
- remember_task_gid(nil)
43
- else
44
- ensure_task_is_valid!
45
- remember_task_gid(task_gid)
46
-
47
- print_task(project, task)
48
- end
49
- end
50
-
51
- def ensure_project_is_valid!
52
- abort "Invalid project: #{project_gid}" if project.nil?
53
- end
54
-
55
- def ensure_task_is_valid!
56
- abort "Invalid task: #{task_gid}" if task.nil?
57
- end
58
-
59
- def project
60
- @project ||= Asana.client.get("projects/#{project_gid}")
61
- end
62
-
63
- def task
64
- @task ||= Asana.client.get("tasks/#{task_gid}")
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class HarvestTimeEntryData < BaseCommand
7
- def self.command
8
- 'harvest-time-entry-data asana[:<project-gid>/<task-gid>]'
9
- end
10
-
11
- def self.description
12
- 'Print Harvest time entry data for Asana task as json. Used by harvest start script.'
13
- end
14
-
15
- def call # rubocop:disable Metrics/MethodLength
16
- ensure_current_is_valid!
17
-
18
- body = {
19
- notes: task['name'],
20
- external_reference: {
21
- id: task_gid.to_i,
22
- group_id: project_gid.to_i,
23
- permalink: task['permalink_url'],
24
- service: 'app.asana.com',
25
- service_icon_url: 'https://proxy.harvestfiles.com/production_harvestapp_public/uploads/platform_icons/app.asana.com.png'
26
- }
27
- }
28
-
29
- puts Oj.dump(body, mode: :json)
30
- end
31
-
32
- private
33
-
34
- def ensure_current_is_valid!
35
- abort "Invalid task gid: #{task_gid}" if task.nil?
36
-
37
- return if task['memberships'].any? { |m| m.dig('project', 'gid') == project_gid }
38
-
39
- abort "Invalid project gid: #{project_gid}"
40
- end
41
-
42
- def task
43
- @task ||= Asana.client.get("tasks/#{task_gid}")
44
- end
45
- end
46
- end
47
- end
48
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class Init < BaseCommand
7
- def self.command
8
- 'init asana'
9
- end
10
-
11
- def self.description
12
- 'Pick Asana project for current git repository'
13
- end
14
-
15
- def call
16
- warn 'Loading projects'
17
-
18
- projects # Load projects up front to make it obvious that searches are instant
19
- project = find_search_result
20
-
21
- remember_project_gid(project['gid'])
22
- remember_task_gid(nil)
23
-
24
- print_project(project)
25
- end
26
-
27
- private
28
-
29
- def find_search_result
30
- loop do
31
- matches = matches_for_string cli.prompt('Enter search')
32
- if matches.empty?
33
- warn 'No matches'
34
- next
35
- end
36
-
37
- warn 'Showing the 10 first matches' if matches.size > 10
38
- choice = cli.prompt_choice 'Select a project', matches[0...10], true
39
- break choice unless choice.nil?
40
- end
41
- end
42
-
43
- def matches_for_string(string)
44
- search_string = sanitize_string(string)
45
-
46
- projects.select do |project|
47
- sanitize_string(project['name']).include?(search_string)
48
- end
49
- end
50
-
51
- def sanitize_string(string)
52
- string.downcase.gsub(/[^\w]/, '')
53
- end
54
-
55
- def projects
56
- @projects ||=
57
- Asana.client.get_paged('projects', workspace: Asana.workspace_gid, archived: false)
58
- end
59
- end
60
- end
61
- end
62
- end