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,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class Clear < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'clear asana'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Clear project/task for current git repository'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
cli.warn 'Clearing Asana project configuration'
|
18
|
+
config.clear_local
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class ClearGlobal < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'clear-global asana'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Clear all global configuration'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
cli.warn 'Clearing Asana project configuration'
|
18
|
+
config.clear_global
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class Current < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'current asana[:<project-gid>[/<task-gid>]]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Get or set project and or task for current git repository'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
if arg_str.nil?
|
18
|
+
show_current_configuration
|
19
|
+
else
|
20
|
+
cli.warn 'Updating configuration'
|
21
|
+
update_configuration
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def show_current_configuration
|
28
|
+
if project_gid.nil?
|
29
|
+
cli.warn 'No project selected'
|
30
|
+
elsif task_gid.nil?
|
31
|
+
print_project(project)
|
32
|
+
else
|
33
|
+
print_task(project, task)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_configuration
|
38
|
+
ensure_project_is_valid!
|
39
|
+
config.project_gid = project_gid
|
40
|
+
|
41
|
+
if task_gid.nil?
|
42
|
+
print_project(project)
|
43
|
+
config.task_gid = nil
|
44
|
+
else
|
45
|
+
ensure_task_is_valid!
|
46
|
+
config.task_gid task_gid
|
47
|
+
|
48
|
+
print_task(project, task)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def ensure_project_is_valid!
|
53
|
+
cli.abort "Invalid project: #{project_gid}" if project.nil?
|
54
|
+
end
|
55
|
+
|
56
|
+
def ensure_task_is_valid!
|
57
|
+
cli.abort "Invalid task: #{task_gid}" if task.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def project
|
61
|
+
@project ||= api.get("projects/#{project_gid}")
|
62
|
+
end
|
63
|
+
|
64
|
+
def task
|
65
|
+
@task ||= api.get("tasks/#{task_gid}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class HarvestTimeEntryData < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'harvest-time-entry-data asana[:<project-gid>/<task-gid>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Print Harvest time entry data for Asana task as json. Used by harvest start script.'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
ensure_current_is_valid!
|
18
|
+
|
19
|
+
body = {
|
20
|
+
notes: task['name'],
|
21
|
+
external_reference: {
|
22
|
+
id: task_gid.to_i,
|
23
|
+
group_id: project_gid.to_i,
|
24
|
+
permalink: task['permalink_url'],
|
25
|
+
service: 'app.asana.com',
|
26
|
+
service_icon_url: 'https://proxy.harvestfiles.com/production_harvestapp_public/uploads/platform_icons/app.asana.com.png'
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
cli.puts Oj.dump(body, mode: :json)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def ensure_current_is_valid!
|
36
|
+
cli.abort "Invalid task gid: #{task_gid}" if task.nil?
|
37
|
+
|
38
|
+
return if task['memberships'].any? { |m| m.dig('project', 'gid') == project_gid }
|
39
|
+
|
40
|
+
cli.abort "Invalid project gid: #{project_gid}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def task
|
44
|
+
@task ||= api.get("tasks/#{task_gid}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class Init < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'init asana'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Pick Asana project for current git repository'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
cli.warn 'Loading projects'
|
18
|
+
|
19
|
+
projects # Load projects up front to make it obvious that searches are instant
|
20
|
+
project = find_search_result
|
21
|
+
|
22
|
+
config.project_gid = project['gid']
|
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
|
+
cli.warn 'No matches'
|
34
|
+
next
|
35
|
+
end
|
36
|
+
|
37
|
+
cli.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
|
+
api.get_paged('projects', workspace: config.workspace_gid, archived: false)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class Move < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'move asana[:<project-gid>/<task-gid>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Move current or specified task to another section (column)'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
print_task(project, task)
|
18
|
+
|
19
|
+
move_task
|
20
|
+
|
21
|
+
cli.warn "Asana task moved to #{section['name']}"
|
22
|
+
rescue Abt::HttpError::HttpError => e
|
23
|
+
cli.warn e
|
24
|
+
cli.abort 'Unable to move asana task'
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def task
|
30
|
+
@task ||= api.get("tasks/#{task_gid}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def move_task
|
34
|
+
body = { data: { task: task_gid } }
|
35
|
+
body_json = Oj.dump(body, mode: :json)
|
36
|
+
api.post("sections/#{section['gid']}/addTask", body_json)
|
37
|
+
end
|
38
|
+
|
39
|
+
def section
|
40
|
+
@section ||= cli.prompt_choice 'Move asana task to?', sections
|
41
|
+
end
|
42
|
+
|
43
|
+
def project
|
44
|
+
@project ||= api.get("projects/#{project_gid}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def sections
|
48
|
+
api.get_paged("projects/#{project_gid}/sections")
|
49
|
+
rescue Abt::HttpError::HttpError
|
50
|
+
[]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class PickTask < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'pick-task asana[:<project-gid>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Pick task for current git repository'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
cli.warn project['name']
|
18
|
+
task = cli.prompt_choice 'Select a task', tasks
|
19
|
+
|
20
|
+
config.project_gid = project_gid # We might have gotten the project ID as an argument
|
21
|
+
config.task_gid = task['gid']
|
22
|
+
|
23
|
+
print_task(project, task)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def project
|
29
|
+
@project ||= api.get("projects/#{project_gid}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def tasks
|
33
|
+
@tasks ||= begin
|
34
|
+
section = cli.prompt_choice 'Which section?', sections
|
35
|
+
api.get_paged('tasks', section: section['gid'])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def sections
|
40
|
+
api.get_paged("projects/#{project_gid}/sections")
|
41
|
+
rescue Abt::HttpError::HttpError
|
42
|
+
[]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class Projects < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'projects asana'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'List all available projects - useful for piping into grep etc.'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
projects.map do |project|
|
18
|
+
print_project(project)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def projects
|
25
|
+
@projects ||=
|
26
|
+
api.get_paged('projects', workspace: config.workspace_gid, archived: false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class Start < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'start asana[:<project-gid>/<task-gid>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Set current task and move it to a section (column) of your choice'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
Current.new(arg_str: arg_str, cli: cli).call unless arg_str.nil?
|
18
|
+
|
19
|
+
if task_already_in_wip_section?
|
20
|
+
cli.warn "Task already in #{current_task_section['name']}"
|
21
|
+
else
|
22
|
+
cli.warn "Moving task to #{wip_section['name']}"
|
23
|
+
move_task
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def task_already_in_wip_section?
|
30
|
+
!task_section_membership.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def current_task_section
|
34
|
+
task_section_membership&.dig('section')
|
35
|
+
end
|
36
|
+
|
37
|
+
def task_section_membership
|
38
|
+
task['memberships'].find do |membership|
|
39
|
+
membership.dig('section', 'gid') == config.wip_section_gid
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def wip_section
|
44
|
+
@wip_section ||= api.get("sections/#{config.wip_section_gid}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def move_task
|
48
|
+
body = { data: { task: task_gid } }
|
49
|
+
body_json = Oj.dump(body, mode: :json)
|
50
|
+
api.post("sections/#{config.wip_section_gid}/addTask", body_json)
|
51
|
+
end
|
52
|
+
|
53
|
+
def task
|
54
|
+
@task ||= api.get("tasks/#{task_gid}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class Tasks < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'tasks asana'
|
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 ||= begin
|
26
|
+
api.get("projects/#{project_gid}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def tasks
|
31
|
+
@tasks ||= api.get_paged('tasks', project: project['gid'])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|