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.
- checksums.yaml +4 -4
- data/bin/abt +3 -1
- data/lib/abt.rb +11 -0
- data/lib/abt/cli.rb +25 -29
- data/lib/abt/cli/dialogs.rb +2 -2
- data/lib/abt/cli/io.rb +21 -0
- data/lib/abt/{help.rb → docs.rb} +8 -14
- data/lib/abt/{help → docs}/cli.rb +3 -3
- data/lib/abt/{help → docs}/markdown.rb +3 -3
- data/lib/abt/helpers.rb +16 -0
- data/lib/abt/providers/asana.rb +9 -50
- data/lib/abt/providers/asana/api.rb +57 -0
- data/lib/abt/providers/asana/base_command.rb +5 -12
- data/lib/abt/providers/asana/commands/clear.rb +24 -0
- data/lib/abt/providers/asana/commands/clear_global.rb +24 -0
- data/lib/abt/providers/asana/commands/current.rb +71 -0
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +50 -0
- data/lib/abt/providers/asana/commands/init.rb +63 -0
- data/lib/abt/providers/asana/commands/move.rb +56 -0
- data/lib/abt/providers/asana/commands/pick_task.rb +48 -0
- data/lib/abt/providers/asana/commands/projects.rb +32 -0
- data/lib/abt/providers/asana/commands/start.rb +60 -0
- data/lib/abt/providers/asana/commands/tasks.rb +37 -0
- data/lib/abt/providers/asana/configuration.rb +105 -0
- data/lib/abt/providers/harvest.rb +9 -0
- data/lib/abt/version.rb +1 -1
- metadata +18 -15
- data/lib/abt/asana_client.rb +0 -53
- data/lib/abt/providers/asana/clear.rb +0 -24
- data/lib/abt/providers/asana/clear_global.rb +0 -24
- data/lib/abt/providers/asana/current.rb +0 -69
- data/lib/abt/providers/asana/harvest_link_entry_data.rb +0 -48
- data/lib/abt/providers/asana/init.rb +0 -62
- data/lib/abt/providers/asana/move.rb +0 -54
- data/lib/abt/providers/asana/pick_task.rb +0 -46
- data/lib/abt/providers/asana/projects.rb +0 -30
- data/lib/abt/providers/asana/start.rb +0 -22
- 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',
|
data/lib/abt/version.rb
CHANGED
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.
|
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/
|
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/
|
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"
|
data/lib/abt/asana_client.rb
DELETED
@@ -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
|