abt-cli 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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,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