abt-cli 0.0.26 → 0.0.27
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/lib/abt/docs.rb +10 -6
- data/lib/abt/providers/asana.rb +1 -0
- data/lib/abt/providers/asana/base_command.rb +33 -3
- data/lib/abt/providers/asana/commands/add.rb +0 -4
- data/lib/abt/providers/asana/commands/branch_name.rb +0 -13
- data/lib/abt/providers/asana/commands/current.rb +0 -18
- data/lib/abt/providers/asana/commands/pick.rb +11 -41
- data/lib/abt/providers/asana/commands/tasks.rb +2 -7
- data/lib/abt/providers/asana/path.rb +1 -1
- data/lib/abt/providers/asana/services/project_picker.rb +54 -0
- data/lib/abt/providers/asana/services/task_picker.rb +83 -0
- data/lib/abt/providers/devops.rb +1 -0
- data/lib/abt/providers/devops/api.rb +10 -0
- data/lib/abt/providers/devops/base_command.rb +34 -14
- data/lib/abt/providers/devops/commands/boards.rb +1 -2
- data/lib/abt/providers/devops/commands/branch_name.rb +10 -16
- data/lib/abt/providers/devops/commands/current.rb +0 -19
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +10 -16
- data/lib/abt/providers/devops/commands/pick.rb +14 -53
- data/lib/abt/providers/devops/commands/work_items.rb +3 -6
- data/lib/abt/providers/devops/path.rb +2 -2
- data/lib/abt/providers/devops/services/board_picker.rb +54 -0
- data/lib/abt/providers/devops/services/project_picker.rb +79 -0
- data/lib/abt/providers/devops/services/work_item_picker.rb +93 -0
- data/lib/abt/providers/harvest.rb +1 -0
- data/lib/abt/providers/harvest/base_command.rb +45 -3
- data/lib/abt/providers/harvest/commands/current.rb +0 -28
- data/lib/abt/providers/harvest/commands/pick.rb +12 -27
- data/lib/abt/providers/harvest/commands/projects.rb +0 -5
- data/lib/abt/providers/harvest/commands/tasks.rb +1 -16
- data/lib/abt/providers/harvest/services/project_picker.rb +53 -0
- data/lib/abt/providers/harvest/services/task_picker.rb +50 -0
- data/lib/abt/version.rb +1 -1
- metadata +9 -5
- data/lib/abt/providers/asana/commands/init.rb +0 -42
- data/lib/abt/providers/devops/commands/init.rb +0 -79
- data/lib/abt/providers/harvest/commands/init.rb +0 -53
@@ -14,8 +14,7 @@ module Abt
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
17
|
-
|
18
|
-
abort("No project selected. Did you initialize DevOps?") if project_name.nil?
|
17
|
+
prompt_project! unless project_name
|
19
18
|
|
20
19
|
boards.map do |board|
|
21
20
|
print_board(organization_name, project_name, board)
|
@@ -16,15 +16,16 @@ module Abt
|
|
16
16
|
def perform
|
17
17
|
require_work_item!
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
if work_item
|
20
|
+
puts name
|
21
|
+
else
|
22
|
+
args = [organization_name, project_name, board_id, work_item_id].compact
|
23
|
+
|
24
|
+
abort(<<~TXT)
|
25
|
+
Unable to find work item for configuration:
|
26
|
+
devops:#{args.join('/')}
|
27
|
+
TXT
|
28
|
+
end
|
28
29
|
end
|
29
30
|
|
30
31
|
private
|
@@ -35,13 +36,6 @@ module Abt
|
|
35
36
|
str += work_item["name"].downcase.gsub(/[^\w]/, "-")
|
36
37
|
str.squeeze("-").gsub(/(^-|-$)/, "")
|
37
38
|
end
|
38
|
-
|
39
|
-
def work_item
|
40
|
-
@work_item ||= begin
|
41
|
-
work_item = api.get_paged("wit/workitems", ids: work_item_id)[0]
|
42
|
-
sanitize_work_item(work_item)
|
43
|
-
end
|
44
|
-
end
|
45
39
|
end
|
46
40
|
end
|
47
41
|
end
|
@@ -42,25 +42,6 @@ module Abt
|
|
42
42
|
end
|
43
43
|
abort("No such work item: ##{work_item_id}") if work_item_id && work_item.nil?
|
44
44
|
end
|
45
|
-
|
46
|
-
def board
|
47
|
-
@board ||= begin
|
48
|
-
warn("Fetching board...")
|
49
|
-
api.get("work/boards/#{board_id}")
|
50
|
-
rescue HttpError::NotFoundError
|
51
|
-
nil
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def work_item
|
56
|
-
@work_item ||= begin
|
57
|
-
warn("Fetching work item...")
|
58
|
-
work_item = api.get_paged("wit/workitems", ids: work_item_id)[0]
|
59
|
-
sanitize_work_item(work_item)
|
60
|
-
rescue HttpError::NotFoundError
|
61
|
-
nil
|
62
|
-
end
|
63
|
-
end
|
64
45
|
end
|
65
46
|
end
|
66
47
|
end
|
@@ -16,15 +16,16 @@ module Abt
|
|
16
16
|
def perform
|
17
17
|
require_work_item!
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
if work_item
|
20
|
+
puts Oj.dump(body, mode: :json)
|
21
|
+
else
|
22
|
+
args = [organization_name, project_name, board_id, work_item_id].compact
|
23
|
+
|
24
|
+
abort(<<~TXT)
|
25
|
+
Unable to find work item for configuration:
|
26
|
+
devops:#{args.join('/')}
|
27
|
+
TXT
|
28
|
+
end
|
28
29
|
end
|
29
30
|
|
30
31
|
private
|
@@ -49,13 +50,6 @@ module Abt
|
|
49
50
|
work_item["name"]
|
50
51
|
].join(" ")
|
51
52
|
end
|
52
|
-
|
53
|
-
def work_item
|
54
|
-
@work_item ||= begin
|
55
|
-
work_item = api.get_paged("wit/workitems", ids: work_item_id)[0]
|
56
|
-
sanitize_work_item(work_item)
|
57
|
-
end
|
58
|
-
end
|
59
53
|
end
|
60
54
|
end
|
61
55
|
end
|
@@ -15,26 +15,33 @@ module Abt
|
|
15
15
|
|
16
16
|
def self.flags
|
17
17
|
[
|
18
|
-
["-d", "--dry-run", "Keep existing configuration"]
|
18
|
+
["-d", "--dry-run", "Keep existing configuration"],
|
19
|
+
["-c", "--clean", "Don't reuse project/board configuration"]
|
19
20
|
]
|
20
21
|
end
|
21
22
|
|
22
23
|
def perform
|
23
|
-
|
24
|
-
require_board!
|
24
|
+
pick!
|
25
25
|
|
26
|
-
warn("#{project_name} - #{board['name']}")
|
27
|
-
|
28
|
-
work_item = select_work_item
|
29
26
|
print_work_item(organization_name, project_name, board, work_item)
|
30
27
|
|
31
28
|
return if flags[:"dry-run"]
|
32
29
|
|
33
|
-
|
30
|
+
if config.local_available?
|
31
|
+
update_config(work_item)
|
32
|
+
else
|
33
|
+
warn("No local configuration to update - will function as dry run")
|
34
|
+
end
|
34
35
|
end
|
35
36
|
|
36
37
|
private
|
37
38
|
|
39
|
+
def pick!
|
40
|
+
prompt_project! if project_name.nil? || flags[:clean]
|
41
|
+
prompt_board! if board_id.nil? || flags[:clean]
|
42
|
+
prompt_work_item!
|
43
|
+
end
|
44
|
+
|
38
45
|
def update_config(work_item)
|
39
46
|
config.path = Path.from_ids(
|
40
47
|
organization_name: organization_name,
|
@@ -43,52 +50,6 @@ module Abt
|
|
43
50
|
work_item_id: work_item["id"]
|
44
51
|
)
|
45
52
|
end
|
46
|
-
|
47
|
-
def select_work_item
|
48
|
-
column = cli.prompt.choice("Which column?", columns)
|
49
|
-
warn("Fetching work items...")
|
50
|
-
work_items = work_items_in_column(column)
|
51
|
-
|
52
|
-
if work_items.length.zero?
|
53
|
-
warn("Section is empty")
|
54
|
-
select_work_item
|
55
|
-
else
|
56
|
-
prompt_work_item(work_items) || select_work_item
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def prompt_work_item(work_items)
|
61
|
-
options = work_items.map do |work_item|
|
62
|
-
{
|
63
|
-
"id" => work_item["id"],
|
64
|
-
"name" => "##{work_item['id']} #{work_item['name']}"
|
65
|
-
}
|
66
|
-
end
|
67
|
-
|
68
|
-
choice = cli.prompt.choice("Select a work item", options, nil_option: true)
|
69
|
-
choice && work_items.find { |work_item| work_item["id"] == choice["id"] }
|
70
|
-
end
|
71
|
-
|
72
|
-
def work_items_in_column(column)
|
73
|
-
work_items = api.work_item_query(
|
74
|
-
<<~WIQL
|
75
|
-
SELECT [System.Id]
|
76
|
-
FROM WorkItems
|
77
|
-
WHERE [System.BoardColumn] = '#{column['name']}'
|
78
|
-
ORDER BY [Microsoft.VSTS.Common.BacklogPriority] ASC
|
79
|
-
WIQL
|
80
|
-
)
|
81
|
-
|
82
|
-
work_items.map { |work_item| sanitize_work_item(work_item) }
|
83
|
-
end
|
84
|
-
|
85
|
-
def columns
|
86
|
-
board["columns"]
|
87
|
-
end
|
88
|
-
|
89
|
-
def board
|
90
|
-
@board ||= api.get("work/boards/#{board_id}")
|
91
|
-
end
|
92
53
|
end
|
93
54
|
end
|
94
55
|
end
|
@@ -14,7 +14,8 @@ module Abt
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
17
|
-
|
17
|
+
prompt_project! unless project_name
|
18
|
+
prompt_board! unless board_id
|
18
19
|
|
19
20
|
work_items.each do |work_item|
|
20
21
|
print_work_item(organization_name, project_name, board, work_item)
|
@@ -32,13 +33,9 @@ module Abt
|
|
32
33
|
FROM WorkItems
|
33
34
|
ORDER BY [System.Title] ASC
|
34
35
|
WIQL
|
35
|
-
).map { |work_item| sanitize_work_item(work_item) }
|
36
|
+
).map { |work_item| api.sanitize_work_item(work_item) }
|
36
37
|
end
|
37
38
|
end
|
38
|
-
|
39
|
-
def board
|
40
|
-
@board ||= api.get("work/boards/#{board_id}")
|
41
|
-
end
|
42
39
|
end
|
43
40
|
end
|
44
41
|
end
|
@@ -13,9 +13,9 @@ module Abt
|
|
13
13
|
%r{^(#{ORGANIZATION_NAME_REGEX}/#{PROJECT_NAME_REGEX}(/#{BOARD_ID_REGEX}(/#{WORK_ITEM_ID_REGEX})?)?)?}.freeze
|
14
14
|
|
15
15
|
def self.from_ids(organization_name: nil, project_name: nil, board_id: nil, work_item_id: nil)
|
16
|
-
return new unless organization_name && project_name
|
16
|
+
return new unless organization_name && project_name
|
17
17
|
|
18
|
-
new([organization_name, project_name, board_id, *work_item_id].join("/"))
|
18
|
+
new([organization_name, project_name, *board_id, *work_item_id].join("/"))
|
19
19
|
end
|
20
20
|
|
21
21
|
def initialize(path = "")
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
module Services
|
7
|
+
class BoardPicker
|
8
|
+
class Result
|
9
|
+
attr_reader :board, :path
|
10
|
+
|
11
|
+
def initialize(board:, path:)
|
12
|
+
@board = board
|
13
|
+
@path = path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.call(**args)
|
18
|
+
new(**args).call
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :cli, :config, :path
|
22
|
+
|
23
|
+
def initialize(cli:, path:, config:)
|
24
|
+
@cli = cli
|
25
|
+
@config = config
|
26
|
+
@path = path
|
27
|
+
end
|
28
|
+
|
29
|
+
def call
|
30
|
+
board = cli.prompt.choice("Select a project work board", boards)
|
31
|
+
|
32
|
+
path_with_board = Path.new([path, board["id"]].join("/"))
|
33
|
+
|
34
|
+
Result.new(board: board, path: path_with_board)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def boards
|
40
|
+
@boards ||= api.get_paged("work/boards")
|
41
|
+
end
|
42
|
+
|
43
|
+
def api
|
44
|
+
Abt::Providers::Devops::Api.new(organization_name: path.organization_name,
|
45
|
+
project_name: path.project_name,
|
46
|
+
username: config.username_for_organization(path.organization_name),
|
47
|
+
access_token: config.access_token_for_organization(path.organization_name),
|
48
|
+
cli: cli)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
module Services
|
7
|
+
class ProjectPicker
|
8
|
+
class Result
|
9
|
+
attr_reader :board, :path
|
10
|
+
|
11
|
+
def initialize(path:)
|
12
|
+
@path = path
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
AZURE_DEV_URL_REGEX = %r{^https://dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)}.freeze
|
17
|
+
VS_URL_REGEX = %r{^https://(?<organization>[^.]+)\.visualstudio\.com/(?<project>[^/]+)}.freeze
|
18
|
+
|
19
|
+
extend Forwardable
|
20
|
+
|
21
|
+
def self.call(**args)
|
22
|
+
new(**args).call
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :cli
|
26
|
+
|
27
|
+
def initialize(cli:)
|
28
|
+
@cli = cli
|
29
|
+
end
|
30
|
+
|
31
|
+
def call
|
32
|
+
Result.new(
|
33
|
+
path: Path.from_ids(organization_name: organization_name, project_name: project_name)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def project_name
|
40
|
+
@project_name ||= begin
|
41
|
+
project_url_match && project_url_match[:project]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def organization_name
|
46
|
+
@organization_name ||= begin
|
47
|
+
project_url_match && project_url_match[:organization]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def project_url_match
|
52
|
+
AZURE_DEV_URL_REGEX.match(project_url) || VS_URL_REGEX.match(project_url)
|
53
|
+
end
|
54
|
+
|
55
|
+
def project_url
|
56
|
+
@project_url ||= begin
|
57
|
+
loop do
|
58
|
+
url = prompt_url
|
59
|
+
|
60
|
+
break url if AZURE_DEV_URL_REGEX =~ url || VS_URL_REGEX =~ url
|
61
|
+
|
62
|
+
cli.warn("Invalid URL")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def prompt_url
|
68
|
+
cli.prompt.text(<<~TXT)
|
69
|
+
Please provide the URL for the devops project
|
70
|
+
For instance https://{organization}.visualstudio.com/{project} or https://dev.azure.com/{organization}/{project}
|
71
|
+
|
72
|
+
Enter URL
|
73
|
+
TXT
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
module Services
|
7
|
+
class WorkItemPicker
|
8
|
+
class Result
|
9
|
+
attr_reader :work_item, :path
|
10
|
+
|
11
|
+
def initialize(work_item:, path:)
|
12
|
+
@work_item = work_item
|
13
|
+
@path = path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.call(**args)
|
18
|
+
new(**args).call
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :cli, :config, :path, :board
|
22
|
+
|
23
|
+
def initialize(cli:, path:, config:, board:)
|
24
|
+
@cli = cli
|
25
|
+
@config = config
|
26
|
+
@path = path
|
27
|
+
@board = board
|
28
|
+
end
|
29
|
+
|
30
|
+
def call
|
31
|
+
work_item = select_work_item
|
32
|
+
|
33
|
+
path_with_work_item = Path.new([path, work_item["id"]].join("/"))
|
34
|
+
|
35
|
+
Result.new(work_item: work_item, path: path_with_work_item)
|
36
|
+
end
|
37
|
+
|
38
|
+
def select_work_item
|
39
|
+
column = cli.prompt.choice("Which column in #{board['name']}?", columns)
|
40
|
+
cli.warn("Fetching work items...")
|
41
|
+
work_items = work_items_in_column(column)
|
42
|
+
|
43
|
+
if work_items.length.zero?
|
44
|
+
cli.warn("Section is empty")
|
45
|
+
select_work_item
|
46
|
+
else
|
47
|
+
prompt_work_item(work_items) || select_work_item
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def prompt_work_item(work_items)
|
52
|
+
options = work_items.map do |work_item|
|
53
|
+
{
|
54
|
+
"id" => work_item["id"],
|
55
|
+
"name" => "##{work_item['id']} #{work_item['name']}"
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
choice = cli.prompt.choice("Select a work item", options, nil_option: true)
|
60
|
+
choice && work_items.find { |work_item| work_item["id"] == choice["id"] }
|
61
|
+
end
|
62
|
+
|
63
|
+
def work_items_in_column(column)
|
64
|
+
work_items = api.work_item_query(
|
65
|
+
<<~WIQL
|
66
|
+
SELECT [System.Id]
|
67
|
+
FROM WorkItems
|
68
|
+
WHERE [System.BoardColumn] = '#{column['name']}'
|
69
|
+
ORDER BY [Microsoft.VSTS.Common.BacklogPriority] ASC
|
70
|
+
WIQL
|
71
|
+
)
|
72
|
+
|
73
|
+
work_items.map { |work_item| api.sanitize_work_item(work_item) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def columns
|
77
|
+
board["columns"] || api.get("work/boards/#{path.board_id}")["columns"]
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def api
|
83
|
+
Abt::Providers::Devops::Api.new(organization_name: path.organization_name,
|
84
|
+
project_name: path.project_name,
|
85
|
+
username: config.username_for_organization(path.organization_name),
|
86
|
+
access_token: config.access_token_for_organization(path.organization_name),
|
87
|
+
cli: cli)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|