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.
- checksums.yaml +4 -4
- data/bin/abt +1 -7
- data/lib/abt.rb +12 -3
- data/lib/abt/cli.rb +91 -53
- data/lib/abt/cli/arguments_parser.rb +70 -0
- data/lib/abt/cli/base_command.rb +61 -0
- data/lib/abt/cli/prompt.rb +124 -0
- data/lib/abt/docs.rb +24 -18
- data/lib/abt/docs/cli.rb +42 -11
- data/lib/abt/docs/markdown.rb +36 -10
- data/lib/abt/git_config.rb +34 -19
- data/lib/abt/helpers.rb +1 -1
- data/lib/abt/providers/asana/base_command.rb +24 -13
- data/lib/abt/providers/asana/commands/add.rb +75 -0
- data/lib/abt/providers/asana/commands/branch_name.rb +44 -0
- data/lib/abt/providers/asana/commands/clear.rb +17 -6
- data/lib/abt/providers/asana/commands/current.rb +6 -6
- data/lib/abt/providers/asana/commands/finalize.rb +4 -4
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +4 -3
- data/lib/abt/providers/asana/commands/init.rb +5 -5
- data/lib/abt/providers/asana/commands/pick.rb +16 -7
- data/lib/abt/providers/asana/commands/projects.rb +3 -3
- data/lib/abt/providers/asana/commands/share.rb +8 -8
- data/lib/abt/providers/asana/commands/start.rb +15 -9
- data/lib/abt/providers/asana/commands/tasks.rb +5 -3
- data/lib/abt/providers/asana/configuration.rb +8 -16
- data/lib/abt/providers/devops/api.rb +32 -2
- data/lib/abt/providers/devops/base_command.rb +32 -16
- data/lib/abt/providers/devops/commands/boards.rb +36 -0
- data/lib/abt/providers/devops/commands/branch_name.rb +45 -0
- data/lib/abt/providers/devops/commands/clear.rb +17 -6
- data/lib/abt/providers/devops/commands/current.rb +6 -10
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +5 -3
- data/lib/abt/providers/devops/commands/init.rb +5 -5
- data/lib/abt/providers/devops/commands/pick.rb +29 -20
- data/lib/abt/providers/devops/commands/share.rb +7 -13
- data/lib/abt/providers/devops/commands/work-items.rb +46 -0
- data/lib/abt/providers/devops/configuration.rb +7 -15
- data/lib/abt/providers/git.rb +19 -0
- data/lib/abt/providers/git/commands/branch.rb +74 -0
- data/lib/abt/providers/harvest/base_command.rb +24 -13
- data/lib/abt/providers/harvest/commands/clear.rb +17 -6
- data/lib/abt/providers/harvest/commands/current.rb +6 -6
- data/lib/abt/providers/harvest/commands/init.rb +5 -5
- data/lib/abt/providers/harvest/commands/pick.rb +15 -6
- data/lib/abt/providers/harvest/commands/projects.rb +3 -3
- data/lib/abt/providers/harvest/commands/share.rb +5 -5
- data/lib/abt/providers/harvest/commands/start.rb +6 -44
- data/lib/abt/providers/harvest/commands/stop.rb +3 -3
- data/lib/abt/providers/harvest/commands/tasks.rb +5 -3
- data/lib/abt/providers/harvest/commands/track.rb +50 -13
- data/lib/abt/providers/harvest/configuration.rb +7 -13
- data/lib/abt/version.rb +1 -1
- metadata +12 -7
- data/lib/abt/cli/dialogs.rb +0 -86
- data/lib/abt/cli/io.rb +0 -23
- data/lib/abt/providers/asana/commands/clear_global.rb +0 -24
- data/lib/abt/providers/devops/commands/clear_global.rb +0 -24
- 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 :
|
6
|
+
class BaseCommand < Abt::Cli::BaseCommand
|
7
|
+
attr_reader :organization_name, :project_name, :board_id, :work_item_id, :config
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
|
9
|
+
def initialize(path:, cli:, **)
|
10
|
+
super
|
11
11
|
|
12
12
|
@config = Configuration.new(cli: cli)
|
13
|
-
@cli = cli
|
14
13
|
|
15
|
-
if
|
16
|
-
|
14
|
+
if path.nil?
|
15
|
+
use_current_path
|
17
16
|
else
|
18
|
-
|
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
|
-
|
57
|
+
path = "#{organization_name}/#{project_name}/#{board['id']}"
|
43
58
|
|
44
|
-
cli.
|
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
|
-
|
64
|
+
path = "#{organization}/#{project}/#{board['id']}/#{work_item['id']}"
|
50
65
|
|
51
|
-
cli.
|
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
|
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
|
63
|
-
args =
|
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.
|
9
|
-
'clear devops'
|
8
|
+
def self.usage
|
9
|
+
'abt clear devops'
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
'Clear DevOps
|
13
|
+
'Clear DevOps configuration'
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
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.
|
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
|
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
|
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.
|
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
|
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.
|
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
|
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.
|
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.
|
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
|
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
|
-
|
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.
|
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.
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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.
|
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
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|