abt-cli 0.0.11 → 0.0.16

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +1 -7
  3. data/lib/abt.rb +12 -3
  4. data/lib/abt/cli.rb +91 -53
  5. data/lib/abt/cli/arguments_parser.rb +70 -0
  6. data/lib/abt/cli/base_command.rb +61 -0
  7. data/lib/abt/cli/prompt.rb +124 -0
  8. data/lib/abt/docs.rb +24 -18
  9. data/lib/abt/docs/cli.rb +42 -11
  10. data/lib/abt/docs/markdown.rb +36 -10
  11. data/lib/abt/git_config.rb +34 -19
  12. data/lib/abt/helpers.rb +1 -1
  13. data/lib/abt/providers/asana/base_command.rb +24 -13
  14. data/lib/abt/providers/asana/commands/add.rb +75 -0
  15. data/lib/abt/providers/asana/commands/branch_name.rb +44 -0
  16. data/lib/abt/providers/asana/commands/clear.rb +17 -6
  17. data/lib/abt/providers/asana/commands/current.rb +6 -6
  18. data/lib/abt/providers/asana/commands/finalize.rb +4 -4
  19. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +4 -3
  20. data/lib/abt/providers/asana/commands/init.rb +5 -5
  21. data/lib/abt/providers/asana/commands/pick.rb +16 -7
  22. data/lib/abt/providers/asana/commands/projects.rb +3 -3
  23. data/lib/abt/providers/asana/commands/share.rb +8 -8
  24. data/lib/abt/providers/asana/commands/start.rb +15 -9
  25. data/lib/abt/providers/asana/commands/tasks.rb +5 -3
  26. data/lib/abt/providers/asana/configuration.rb +8 -16
  27. data/lib/abt/providers/devops/api.rb +32 -2
  28. data/lib/abt/providers/devops/base_command.rb +32 -16
  29. data/lib/abt/providers/devops/commands/boards.rb +36 -0
  30. data/lib/abt/providers/devops/commands/branch_name.rb +45 -0
  31. data/lib/abt/providers/devops/commands/clear.rb +17 -6
  32. data/lib/abt/providers/devops/commands/current.rb +6 -10
  33. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +5 -3
  34. data/lib/abt/providers/devops/commands/init.rb +5 -5
  35. data/lib/abt/providers/devops/commands/pick.rb +29 -20
  36. data/lib/abt/providers/devops/commands/share.rb +7 -13
  37. data/lib/abt/providers/devops/commands/work-items.rb +46 -0
  38. data/lib/abt/providers/devops/configuration.rb +7 -15
  39. data/lib/abt/providers/git.rb +19 -0
  40. data/lib/abt/providers/git/commands/branch.rb +74 -0
  41. data/lib/abt/providers/harvest/base_command.rb +24 -13
  42. data/lib/abt/providers/harvest/commands/clear.rb +17 -6
  43. data/lib/abt/providers/harvest/commands/current.rb +6 -6
  44. data/lib/abt/providers/harvest/commands/init.rb +5 -5
  45. data/lib/abt/providers/harvest/commands/pick.rb +15 -6
  46. data/lib/abt/providers/harvest/commands/projects.rb +3 -3
  47. data/lib/abt/providers/harvest/commands/share.rb +5 -5
  48. data/lib/abt/providers/harvest/commands/start.rb +6 -44
  49. data/lib/abt/providers/harvest/commands/stop.rb +3 -3
  50. data/lib/abt/providers/harvest/commands/tasks.rb +5 -3
  51. data/lib/abt/providers/harvest/commands/track.rb +50 -13
  52. data/lib/abt/providers/harvest/configuration.rb +7 -13
  53. data/lib/abt/version.rb +1 -1
  54. metadata +12 -7
  55. data/lib/abt/cli/dialogs.rb +0 -86
  56. data/lib/abt/cli/io.rb +0 -23
  57. data/lib/abt/providers/asana/commands/clear_global.rb +0 -24
  58. data/lib/abt/providers/devops/commands/clear_global.rb +0 -24
  59. data/lib/abt/providers/harvest/commands/clear_global.rb +0 -24
@@ -3,24 +3,39 @@
3
3
  module Abt
4
4
  module Providers
5
5
  module Devops
6
- class BaseCommand
7
- attr_reader :arg_str, :organization_name, :project_name, :board_id, :work_item_id, :cli, :config
6
+ class BaseCommand < Abt::Cli::BaseCommand
7
+ attr_reader :organization_name, :project_name, :board_id, :work_item_id, :config
8
8
 
9
- def initialize(arg_str:, cli:)
10
- @arg_str = arg_str
9
+ def initialize(path:, cli:, **)
10
+ super
11
11
 
12
12
  @config = Configuration.new(cli: cli)
13
- @cli = cli
14
13
 
15
- if arg_str.nil?
16
- use_current_args
14
+ if path.nil?
15
+ use_current_path
17
16
  else
18
- use_arg_str(arg_str)
17
+ use_path(path)
19
18
  end
20
19
  end
21
20
 
22
21
  private
23
22
 
23
+ def require_board!
24
+ return if organization_name && project_name && board_id
25
+
26
+ cli.abort 'No current/specified board. Did you initialize DevOps?'
27
+ end
28
+
29
+ def require_work_item!
30
+ unless organization_name && project_name && board_id
31
+ cli.abort 'No current/specified board. Did you initialize DevOps and pick a work item?'
32
+ end
33
+
34
+ return if work_item_id
35
+
36
+ cli.abort 'No current/specified work item. Did you pick a DevOps work item?'
37
+ end
38
+
24
39
  def sanitize_work_item(work_item)
25
40
  return nil if work_item.nil?
26
41
 
@@ -39,28 +54,28 @@ module Abt
39
54
  end
40
55
 
41
56
  def print_board(organization_name, project_name, board)
42
- arg_str = "#{organization_name}/#{project_name}/#{board['id']}"
57
+ path = "#{organization_name}/#{project_name}/#{board['id']}"
43
58
 
44
- cli.print_provider_command('devops', arg_str, board['name'])
59
+ cli.print_scheme_argument('devops', path, board['name'])
45
60
  # cli.warn board['url'] if board.key?('url') && cli.output.isatty # TODO: Web URL
46
61
  end
47
62
 
48
63
  def print_work_item(organization, project, board, work_item)
49
- arg_str = "#{organization}/#{project}/#{board['id']}/#{work_item['id']}"
64
+ path = "#{organization}/#{project}/#{board['id']}/#{work_item['id']}"
50
65
 
51
- cli.print_provider_command('devops', arg_str, work_item['name'])
66
+ cli.print_scheme_argument('devops', path, work_item['name'])
52
67
  cli.warn work_item['url'] if work_item.key?('url') && cli.output.isatty
53
68
  end
54
69
 
55
- def use_current_args
70
+ def use_current_path
56
71
  @organization_name = config.organization_name
57
72
  @project_name = config.project_name
58
73
  @board_id = config.board_id
59
74
  @work_item_id = config.work_item_id
60
75
  end
61
76
 
62
- def use_arg_str(arg_str)
63
- args = arg_str.to_s.split('/')
77
+ def use_path(path)
78
+ args = path.to_s.split('/')
64
79
 
65
80
  if args.length < 3
66
81
  cli.abort 'Argument format is <organization>/<project>/<board-id>[/<work-item-id>]'
@@ -73,7 +88,8 @@ module Abt
73
88
  Abt::Providers::Devops::Api.new(organization_name: organization_name,
74
89
  project_name: project_name,
75
90
  username: config.username_for_organization(organization_name),
76
- access_token: config.access_token_for_organization(organization_name))
91
+ access_token: config.access_token_for_organization(organization_name),
92
+ cli: cli)
77
93
  end
78
94
  end
79
95
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class Boards < BaseCommand
8
+ def self.usage
9
+ 'abt boards devops'
10
+ end
11
+
12
+ def self.description
13
+ 'List all boards - useful for piping into grep etc'
14
+ end
15
+
16
+ def perform
17
+ if organization_name.nil?
18
+ cli.abort 'No organization selected. Did you initialize DevOps?'
19
+ end
20
+ cli.abort 'No project selected. Did you initialize DevOps?' if project_name.nil?
21
+
22
+ boards.map do |board|
23
+ print_board(organization_name, project_name, board)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def boards
30
+ @boards ||= api.get_paged('work/boards')
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class BranchName < BaseCommand
8
+ def self.usage
9
+ 'abt branch-name devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]'
10
+ end
11
+
12
+ def self.description
13
+ 'Suggest a git branch name for the current/specified work-item.'
14
+ end
15
+
16
+ def perform
17
+ require_work_item!
18
+
19
+ cli.puts name
20
+ rescue HttpError::NotFoundError
21
+ args = [organization_name, project_name, board_id, work_item_id].compact
22
+ cli.warn 'Unable to find work item for configuration:'
23
+ cli.abort "devops:#{args.join('/')}"
24
+ end
25
+
26
+ private
27
+
28
+ def name
29
+ str = work_item['id']
30
+ str += '-'
31
+ str += work_item['name'].downcase.gsub(/[^\w]/, '-')
32
+ str.gsub(/-+/, '-')
33
+ end
34
+
35
+ def work_item
36
+ @work_item ||= begin
37
+ work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
38
+ sanitize_work_item(work_item)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -5,17 +5,28 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class Clear < BaseCommand
8
- def self.command
9
- 'clear devops'
8
+ def self.usage
9
+ 'abt clear devops'
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Clear DevOps config for current git repository'
13
+ 'Clear DevOps configuration'
14
14
  end
15
15
 
16
- def call
17
- cli.warn 'Clearing configuration'
18
- config.clear_local
16
+ def self.flags
17
+ [
18
+ ['-g', '--global', 'Clear global instead of local DevOp configuration (credentials etc.)'],
19
+ ['-a', '--all', 'Clear all DevOp configuration']
20
+ ]
21
+ end
22
+
23
+ def perform
24
+ if flags[:global] && flags[:all]
25
+ abort('Flags --global and --all cannot be used at the same time')
26
+ end
27
+
28
+ config.clear_local unless flags[:global]
29
+ config.clear_global if flags[:global] || flags[:all]
19
30
  end
20
31
  end
21
32
  end
@@ -5,15 +5,17 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class Current < BaseCommand
8
- def self.command
9
- 'current devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
8
+ def self.usage
9
+ 'abt current devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Get or set DevOps configuration for current git repository'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
+ require_board!
18
+
17
19
  if same_args_as_config? || !config.local_available?
18
20
  show_current_configuration
19
21
  else
@@ -25,13 +27,7 @@ module Abt
25
27
  private
26
28
 
27
29
  def show_current_configuration
28
- if organization_name.nil?
29
- cli.warn 'No organization selected'
30
- elsif project_name.nil?
31
- cli.warn 'No project selected'
32
- elsif board_id.nil?
33
- cli.warn 'No board selected'
34
- elsif work_item_id.nil?
30
+ if work_item_id.nil?
35
31
  print_board(organization_name, project_name, board)
36
32
  else
37
33
  print_work_item(organization_name, project_name, board, work_item)
@@ -5,15 +5,17 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class HarvestTimeEntryData < BaseCommand
8
- def self.command
9
- 'harvest-time-entry-data devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]'
8
+ def self.usage
9
+ 'abt harvest-time-entry-data devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Print Harvest time entry data for DevOps work item as json. Used by harvest start script.'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
+ require_work_item!
18
+
17
19
  body = {
18
20
  notes: notes,
19
21
  external_reference: {
@@ -8,21 +8,21 @@ module Abt
8
8
  AZURE_DEV_URL_REGEX = %r{^https://dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)}.freeze
9
9
  VS_URL_REGEX = %r{^https://(?<organization>[^.]+)\.visualstudio\.com/(?<project>[^/]+)}.freeze
10
10
 
11
- def self.command
12
- 'init devops'
11
+ def self.usage
12
+ 'abt init devops'
13
13
  end
14
14
 
15
15
  def self.description
16
16
  'Pick DevOps board for current git repository'
17
17
  end
18
18
 
19
- def call
19
+ def perform
20
20
  cli.abort 'Must be run inside a git repository' unless config.local_available?
21
21
 
22
22
  @organization_name = config.organization_name = organization_name_from_url
23
23
  @project_name = config.project_name = project_name_from_url
24
24
 
25
- board = cli.prompt_choice 'Select a project work board', boards
25
+ board = cli.prompt.choice 'Select a project work board', boards
26
26
 
27
27
  config.board_id = board['id']
28
28
 
@@ -52,7 +52,7 @@ module Abt
52
52
  def project_url
53
53
  @project_url ||= begin
54
54
  loop do
55
- url = cli.prompt([
55
+ url = cli.prompt.text([
56
56
  'Please provide the URL for the devops project',
57
57
  'For instance https://{organization}.visualstudio.com/{project} or https://dev.azure.com/{organization}/{project}',
58
58
  '',
@@ -5,35 +5,46 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class Pick < BaseCommand
8
- def self.command
9
- 'pick devops[:<organization-name>/<project-name>/<board-id>]'
8
+ def self.usage
9
+ 'abt pick devops[:<organization-name>/<project-name>/<board-id>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Pick work item for current git repository'
14
14
  end
15
15
 
16
- def call
16
+ def self.flags
17
+ [
18
+ ['-d', '--dry-run', 'Keep existing configuration']
19
+ ]
20
+ end
21
+
22
+ def perform
17
23
  cli.abort 'Must be run inside a git repository' unless config.local_available?
24
+ require_board!
18
25
 
19
26
  cli.warn "#{project_name} - #{board['name']}"
20
27
 
21
28
  work_item = select_work_item
29
+ print_work_item(organization_name, project_name, board, work_item)
30
+
31
+ return if flags[:"dry-run"]
32
+
33
+ update_config!(work_item)
34
+ end
22
35
 
23
- # We might have gotten org, project, board as arg str
36
+ private
37
+
38
+ def update_config!(work_item)
24
39
  config.organization_name = organization_name
25
40
  config.project_name = project_name
26
41
  config.board_id = board_id
27
42
  config.work_item_id = work_item['id']
28
-
29
- print_work_item(organization_name, project_name, board, work_item)
30
43
  end
31
44
 
32
- private
33
-
34
45
  def select_work_item
35
46
  loop do
36
- column = cli.prompt_choice 'Which column?', columns
47
+ column = cli.prompt.choice 'Which column?', columns
37
48
  cli.warn 'Fetching work items...'
38
49
  work_items = work_items_in_column(column)
39
50
 
@@ -42,22 +53,20 @@ module Abt
42
53
  next
43
54
  end
44
55
 
45
- work_item = cli.prompt_choice 'Select a work item', work_items, true
56
+ work_item = cli.prompt.choice 'Select a work item', work_items, true
46
57
  return work_item if work_item
47
58
  end
48
59
  end
49
60
 
50
61
  def work_items_in_column(column)
51
- wiql = <<~WIQL
52
- SELECT [System.Id]
53
- FROM WorkItems
54
- WHERE [System.BoardColumn] = '#{column['name']}'
55
- ORDER BY [Microsoft.VSTS.Common.BacklogPriority] ASC
56
- WIQL
57
-
58
- response = api.post('wit/wiql', Oj.dump({ query: wiql }, mode: :json))
59
- ids = response['workItems'].map { |work_item| work_item['id'] }
60
- work_items = api.get_paged('wit/workitems', ids: ids.join(','))
62
+ work_items = api.work_item_query(
63
+ <<~WIQL
64
+ SELECT [System.Id]
65
+ FROM WorkItems
66
+ WHERE [System.BoardColumn] = '#{column['name']}'
67
+ ORDER BY [Microsoft.VSTS.Common.BacklogPriority] ASC
68
+ WIQL
69
+ )
61
70
 
62
71
  work_items.map { |work_item| sanitize_work_item(work_item) }
63
72
  end
@@ -5,25 +5,19 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class Share < BaseCommand
8
- def self.command
9
- 'share devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
8
+ def self.usage
9
+ 'abt share devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Print DevOps config string'
14
14
  end
15
15
 
16
- def call
17
- if organization_name.nil?
18
- cli.warn 'No organization selected'
19
- elsif project_name.nil?
20
- cli.warn 'No project selected'
21
- elsif board_id.nil?
22
- cli.warn 'No board selected'
23
- else
24
- args = [organization_name, project_name, board_id, work_item_id].compact
25
- cli.print_provider_command('devops', args.join('/'))
26
- end
16
+ def perform
17
+ require_work_item!
18
+
19
+ args = [organization_name, project_name, board_id, work_item_id].compact
20
+ cli.print_scheme_argument('devops', args.join('/'))
27
21
  end
28
22
  end
29
23
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class WorkItems < BaseCommand
8
+ def self.usage
9
+ 'abt work-items devops'
10
+ end
11
+
12
+ def self.description
13
+ 'List all work items on board - useful for piping into grep etc.'
14
+ end
15
+
16
+ def perform
17
+ require_board!
18
+
19
+ work_items.each do |work_item|
20
+ print_work_item(organization_name, project_name, board, work_item)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def work_items
27
+ @work_items ||= begin
28
+ cli.warn 'Fetching work items...'
29
+ api.work_item_query(
30
+ <<~WIQL
31
+ SELECT [System.Id]
32
+ FROM WorkItems
33
+ ORDER BY [System.Title] ASC
34
+ WIQL
35
+ ).map { |work_item| sanitize_work_item(work_item) }
36
+ end
37
+ end
38
+
39
+ def board
40
+ @board ||= api.get("work/boards/#{board_id}")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end