abt-cli 0.0.10 → 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/abt/git_config.rb +13 -0
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +2 -4
- data/lib/abt/providers/asana/commands/init.rb +5 -0
- data/lib/abt/providers/asana/configuration.rb +4 -2
- data/lib/abt/providers/devops.rb +19 -0
- data/lib/abt/providers/devops/api.rb +65 -0
- data/lib/abt/providers/devops/base_command.rb +81 -0
- data/lib/abt/providers/devops/commands/clear.rb +24 -0
- data/lib/abt/providers/devops/commands/clear_global.rb +24 -0
- data/lib/abt/providers/devops/commands/current.rb +97 -0
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +51 -0
- data/lib/abt/providers/devops/commands/init.rb +72 -0
- data/lib/abt/providers/devops/commands/pick.rb +76 -0
- data/lib/abt/providers/devops/commands/share.rb +32 -0
- data/lib/abt/providers/devops/configuration.rb +110 -0
- data/lib/abt/providers/harvest/configuration.rb +4 -3
- data/lib/abt/version.rb +1 -1
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cc34f7579a887a49ae83db56c8b3808a0e00b09c55631f9b4224228477a0762
|
4
|
+
data.tar.gz: 9c26a06c3af53ebe55ca5e53f93b5d6d73854962daebef31d131000657be1c63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4fe10297a130b747d93dc1398bdf404996d5c07987680deac59a5365d6893ab3d0466ebd764ef087f5eb2409b99a7733dc55a869432d81286f3460ec510d247d
|
7
|
+
data.tar.gz: 3684a1d3113f9df06637a6c7294f334b48e2c8cd5e54d4334c1ec1884571519f6e5aedeec327d8292f5364d1251453d6a7a8131074f91b2f94935b7a3586ddb9
|
data/lib/abt/git_config.rb
CHANGED
@@ -32,6 +32,19 @@ module Abt
|
|
32
32
|
set(key, value)
|
33
33
|
end
|
34
34
|
|
35
|
+
def full_keys
|
36
|
+
if scope == 'local' && !self.class.local_available?
|
37
|
+
raise StandardError, 'Local configuration is not available outside a git repository'
|
38
|
+
end
|
39
|
+
|
40
|
+
`git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
|
41
|
+
end
|
42
|
+
|
43
|
+
def keys
|
44
|
+
offset = namespace.length + 1
|
45
|
+
full_keys.map { |key| key[offset..-1] }
|
46
|
+
end
|
47
|
+
|
35
48
|
def local
|
36
49
|
@local ||= begin
|
37
50
|
if scope == 'local'
|
@@ -13,7 +13,7 @@ module Abt
|
|
13
13
|
'Print Harvest time entry data for Asana task as json. Used by harvest start script.'
|
14
14
|
end
|
15
15
|
|
16
|
-
def call
|
16
|
+
def call
|
17
17
|
ensure_current_is_valid!
|
18
18
|
|
19
19
|
body = {
|
@@ -21,9 +21,7 @@ module Abt
|
|
21
21
|
external_reference: {
|
22
22
|
id: task_gid.to_i,
|
23
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'
|
24
|
+
permalink: task['permalink_url']
|
27
25
|
}
|
28
26
|
}
|
29
27
|
|
@@ -13,6 +13,11 @@ module Abt
|
|
13
13
|
'Pick Asana project for current git repository'
|
14
14
|
end
|
15
15
|
|
16
|
+
def initialize(cli:, **)
|
17
|
+
@config = Configuration.new(cli: cli)
|
18
|
+
@cli = cli
|
19
|
+
end
|
20
|
+
|
16
21
|
def call
|
17
22
|
cli.abort 'Must be run inside a git repository' unless config.local_available?
|
18
23
|
|
@@ -67,8 +67,10 @@ module Abt
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def clear_global
|
70
|
-
git.global
|
71
|
-
|
70
|
+
git.global.keys.each do |key|
|
71
|
+
cli.puts 'Deleting configuration: ' + key
|
72
|
+
git.global[key] = nil
|
73
|
+
end
|
72
74
|
end
|
73
75
|
|
74
76
|
def access_token
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Dir.glob("#{File.expand_path(__dir__)}/devops/*.rb").sort.each { |file| require file }
|
4
|
+
Dir.glob("#{File.expand_path(__dir__)}/devops/commands/*.rb").sort.each { |file| require file }
|
5
|
+
|
6
|
+
module Abt
|
7
|
+
module Providers
|
8
|
+
module Devops
|
9
|
+
def self.command_names
|
10
|
+
Commands.constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.command_class(name)
|
14
|
+
const_name = Helpers.command_to_const(name)
|
15
|
+
Commands.const_get(const_name) if Commands.const_defined?(const_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
class Api
|
7
|
+
VERBS = %i[get post put].freeze
|
8
|
+
|
9
|
+
attr_reader :organization_name, :project_name, :username, :access_token
|
10
|
+
|
11
|
+
def initialize(organization_name:, project_name:, username:, access_token:)
|
12
|
+
@organization_name = organization_name
|
13
|
+
@project_name = project_name
|
14
|
+
@username = username
|
15
|
+
@access_token = access_token
|
16
|
+
end
|
17
|
+
|
18
|
+
VERBS.each do |verb|
|
19
|
+
define_method(verb) do |*args|
|
20
|
+
request(verb, *args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_paged(path, query = {})
|
25
|
+
result = request(:get, path, query)
|
26
|
+
result['value']
|
27
|
+
|
28
|
+
# TODO: Loop if necessary
|
29
|
+
end
|
30
|
+
|
31
|
+
def request(*args)
|
32
|
+
response = connection.public_send(*args)
|
33
|
+
|
34
|
+
if response.success?
|
35
|
+
Oj.load(response.body)
|
36
|
+
else
|
37
|
+
error_class = Abt::HttpError.error_class_for_status(response.status)
|
38
|
+
encoded_response_body = response.body.force_encoding('utf-8')
|
39
|
+
raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def base_url
|
44
|
+
"https://#{organization_name}.visualstudio.com/#{project_name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def api_endpoint
|
48
|
+
"#{base_url}/_apis"
|
49
|
+
end
|
50
|
+
|
51
|
+
def url_for_work_item(work_item)
|
52
|
+
"#{base_url}/_workitems/edit/#{work_item['id']}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def connection
|
56
|
+
@connection ||= Faraday.new(api_endpoint) do |connection|
|
57
|
+
connection.basic_auth username, access_token
|
58
|
+
connection.headers['Content-Type'] = 'application/json'
|
59
|
+
connection.headers['Accept'] = 'application/json; api-version=6.0'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
class BaseCommand
|
7
|
+
attr_reader :arg_str, :organization_name, :project_name, :board_id, :work_item_id, :cli, :config
|
8
|
+
|
9
|
+
def initialize(arg_str:, cli:)
|
10
|
+
@arg_str = arg_str
|
11
|
+
|
12
|
+
@config = Configuration.new(cli: cli)
|
13
|
+
@cli = cli
|
14
|
+
|
15
|
+
if arg_str.nil?
|
16
|
+
use_current_args
|
17
|
+
else
|
18
|
+
use_arg_str(arg_str)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def sanitize_work_item(work_item)
|
25
|
+
return nil if work_item.nil?
|
26
|
+
|
27
|
+
work_item.merge(
|
28
|
+
'id' => work_item['id'].to_s,
|
29
|
+
'name' => work_item['fields']['System.Title'],
|
30
|
+
'url' => api.url_for_work_item(work_item)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def same_args_as_config?
|
35
|
+
organization_name == config.organization_name &&
|
36
|
+
project_name == config.project_name &&
|
37
|
+
board_id == config.board_id &&
|
38
|
+
work_item_id == config.work_item_id
|
39
|
+
end
|
40
|
+
|
41
|
+
def print_board(organization_name, project_name, board)
|
42
|
+
arg_str = "#{organization_name}/#{project_name}/#{board['id']}"
|
43
|
+
|
44
|
+
cli.print_provider_command('devops', arg_str, board['name'])
|
45
|
+
# cli.warn board['url'] if board.key?('url') && cli.output.isatty # TODO: Web URL
|
46
|
+
end
|
47
|
+
|
48
|
+
def print_work_item(organization, project, board, work_item)
|
49
|
+
arg_str = "#{organization}/#{project}/#{board['id']}/#{work_item['id']}"
|
50
|
+
|
51
|
+
cli.print_provider_command('devops', arg_str, work_item['name'])
|
52
|
+
cli.warn work_item['url'] if work_item.key?('url') && cli.output.isatty
|
53
|
+
end
|
54
|
+
|
55
|
+
def use_current_args
|
56
|
+
@organization_name = config.organization_name
|
57
|
+
@project_name = config.project_name
|
58
|
+
@board_id = config.board_id
|
59
|
+
@work_item_id = config.work_item_id
|
60
|
+
end
|
61
|
+
|
62
|
+
def use_arg_str(arg_str)
|
63
|
+
args = arg_str.to_s.split('/')
|
64
|
+
|
65
|
+
if args.length < 3
|
66
|
+
cli.abort 'Argument format is <organization>/<project>/<board-id>[/<work-item-id>]'
|
67
|
+
end
|
68
|
+
|
69
|
+
(@organization_name, @project_name, @board_id, @work_item_id) = args
|
70
|
+
end
|
71
|
+
|
72
|
+
def api
|
73
|
+
Abt::Providers::Devops::Api.new(organization_name: organization_name,
|
74
|
+
project_name: project_name,
|
75
|
+
username: config.username_for_organization(organization_name),
|
76
|
+
access_token: config.access_token_for_organization(organization_name))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
module Commands
|
7
|
+
class Clear < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'clear devops'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Clear DevOps config for current git repository'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
cli.warn 'Clearing 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 Devops
|
6
|
+
module Commands
|
7
|
+
class ClearGlobal < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'clear-global devops'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Clear all global configuration'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
cli.warn 'Clearing global DevOps configuration'
|
18
|
+
config.clear_global
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
module Commands
|
7
|
+
class Current < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'current devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Get or set DevOps configuration for current git repository'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
if same_args_as_config? || !config.local_available?
|
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 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?
|
35
|
+
print_board(organization_name, project_name, board)
|
36
|
+
else
|
37
|
+
print_work_item(organization_name, project_name, board, work_item)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_configuration
|
42
|
+
ensure_board_is_valid!
|
43
|
+
|
44
|
+
if work_item_id.nil?
|
45
|
+
update_board_config
|
46
|
+
config.work_item_id = nil
|
47
|
+
|
48
|
+
print_board(organization_name, project_name, board)
|
49
|
+
else
|
50
|
+
ensure_work_item_is_valid!
|
51
|
+
|
52
|
+
update_board_config
|
53
|
+
config.work_item_id = work_item_id
|
54
|
+
|
55
|
+
print_work_item(organization_name, project_name, board, work_item)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_board_config
|
60
|
+
config.organization_name = organization_name
|
61
|
+
config.project_name = project_name
|
62
|
+
config.board_id = board_id
|
63
|
+
end
|
64
|
+
|
65
|
+
def ensure_board_is_valid!
|
66
|
+
if board.nil?
|
67
|
+
cli.abort 'Board could not be found, ensure that settings for organization, project, and board are correct'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def ensure_work_item_is_valid!
|
72
|
+
cli.abort "No such work item: ##{work_item_id}" if work_item.nil?
|
73
|
+
end
|
74
|
+
|
75
|
+
def board
|
76
|
+
@board ||= begin
|
77
|
+
cli.warn 'Fetching board...'
|
78
|
+
api.get("work/boards/#{board_id}")
|
79
|
+
rescue HttpError::NotFoundError
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def work_item
|
85
|
+
@work_item ||= begin
|
86
|
+
cli.warn 'Fetching work item...'
|
87
|
+
work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
|
88
|
+
sanitize_work_item(work_item)
|
89
|
+
rescue HttpError::NotFoundError
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
module Commands
|
7
|
+
class HarvestTimeEntryData < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'harvest-time-entry-data devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Print Harvest time entry data for DevOps work item as json. Used by harvest start script.'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
body = {
|
18
|
+
notes: notes,
|
19
|
+
external_reference: {
|
20
|
+
id: work_item['id'],
|
21
|
+
group_id: 'AzureDevOpsWorkItem',
|
22
|
+
permalink: work_item['url']
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
cli.puts Oj.dump(body, mode: :json)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def notes
|
32
|
+
[
|
33
|
+
'Azure DevOps',
|
34
|
+
work_item['fields']['System.WorkItemType'],
|
35
|
+
"##{work_item['id']}",
|
36
|
+
'-',
|
37
|
+
work_item['name']
|
38
|
+
].join(' ')
|
39
|
+
end
|
40
|
+
|
41
|
+
def work_item
|
42
|
+
@work_item ||= begin
|
43
|
+
work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
|
44
|
+
sanitize_work_item(work_item)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
module Commands
|
7
|
+
class Init < BaseCommand
|
8
|
+
AZURE_DEV_URL_REGEX = %r{^https://dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)}.freeze
|
9
|
+
VS_URL_REGEX = %r{^https://(?<organization>[^.]+)\.visualstudio\.com/(?<project>[^/]+)}.freeze
|
10
|
+
|
11
|
+
def self.command
|
12
|
+
'init devops'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.description
|
16
|
+
'Pick DevOps board for current git repository'
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
cli.abort 'Must be run inside a git repository' unless config.local_available?
|
21
|
+
|
22
|
+
@organization_name = config.organization_name = organization_name_from_url
|
23
|
+
@project_name = config.project_name = project_name_from_url
|
24
|
+
|
25
|
+
board = cli.prompt_choice 'Select a project work board', boards
|
26
|
+
|
27
|
+
config.board_id = board['id']
|
28
|
+
|
29
|
+
print_board(organization_name, project_name, board)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def boards
|
35
|
+
@boards ||= api.get_paged('work/boards')
|
36
|
+
end
|
37
|
+
|
38
|
+
def project_name_from_url
|
39
|
+
if (match = AZURE_DEV_URL_REGEX.match(project_url)) ||
|
40
|
+
(match = VS_URL_REGEX.match(project_url))
|
41
|
+
match[:project]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def organization_name_from_url
|
46
|
+
if (match = AZURE_DEV_URL_REGEX.match(project_url)) ||
|
47
|
+
(match = VS_URL_REGEX.match(project_url))
|
48
|
+
match[:organization]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def project_url
|
53
|
+
@project_url ||= begin
|
54
|
+
loop do
|
55
|
+
url = cli.prompt([
|
56
|
+
'Please provide the URL for the devops project',
|
57
|
+
'For instance https://{organization}.visualstudio.com/{project} or https://dev.azure.com/{organization}/{project}',
|
58
|
+
'',
|
59
|
+
'Enter URL'
|
60
|
+
].join("\n"))
|
61
|
+
|
62
|
+
break url if AZURE_DEV_URL_REGEX =~ url || VS_URL_REGEX =~ url
|
63
|
+
|
64
|
+
cli.warn 'Invalid URL'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
module Commands
|
7
|
+
class Pick < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'pick devops[:<organization-name>/<project-name>/<board-id>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Pick work item for current git repository'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
cli.abort 'Must be run inside a git repository' unless config.local_available?
|
18
|
+
|
19
|
+
cli.warn "#{project_name} - #{board['name']}"
|
20
|
+
|
21
|
+
work_item = select_work_item
|
22
|
+
|
23
|
+
# We might have gotten org, project, board as arg str
|
24
|
+
config.organization_name = organization_name
|
25
|
+
config.project_name = project_name
|
26
|
+
config.board_id = board_id
|
27
|
+
config.work_item_id = work_item['id']
|
28
|
+
|
29
|
+
print_work_item(organization_name, project_name, board, work_item)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def select_work_item
|
35
|
+
loop do
|
36
|
+
column = cli.prompt_choice 'Which column?', columns
|
37
|
+
cli.warn 'Fetching work items...'
|
38
|
+
work_items = work_items_in_column(column)
|
39
|
+
|
40
|
+
if work_items.length.zero?
|
41
|
+
cli.warn 'Section is empty'
|
42
|
+
next
|
43
|
+
end
|
44
|
+
|
45
|
+
work_item = cli.prompt_choice 'Select a work item', work_items, true
|
46
|
+
return work_item if work_item
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
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(','))
|
61
|
+
|
62
|
+
work_items.map { |work_item| sanitize_work_item(work_item) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def columns
|
66
|
+
board['columns']
|
67
|
+
end
|
68
|
+
|
69
|
+
def board
|
70
|
+
@board ||= api.get("work/boards/#{board_id}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
module Commands
|
7
|
+
class Share < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'share devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Print DevOps config string'
|
14
|
+
end
|
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
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Devops
|
6
|
+
class Configuration
|
7
|
+
attr_accessor :cli
|
8
|
+
|
9
|
+
def initialize(cli:)
|
10
|
+
@cli = cli
|
11
|
+
@git = GitConfig.new(namespace: 'abt.devops')
|
12
|
+
end
|
13
|
+
|
14
|
+
def local_available?
|
15
|
+
GitConfig.local_available?
|
16
|
+
end
|
17
|
+
|
18
|
+
def organization_name
|
19
|
+
local_available? ? git['organizationName'] : nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def project_name
|
23
|
+
local_available? ? git['projectName'] : nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def board_id
|
27
|
+
local_available? ? git['boardId'] : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def work_item_id
|
31
|
+
local_available? ? git['workItemId'] : nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def organization_name=(value)
|
35
|
+
return if organization_name == value
|
36
|
+
|
37
|
+
clear_local
|
38
|
+
git['organizationName'] = value unless value.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
def project_name=(value)
|
42
|
+
return if project_name == value
|
43
|
+
|
44
|
+
git['projectName'] = value unless value.nil?
|
45
|
+
git['boardId'] = nil
|
46
|
+
git['workItemId'] = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def board_id=(value)
|
50
|
+
return if board_id == value
|
51
|
+
|
52
|
+
git['boardId'] = value unless value.nil?
|
53
|
+
git['workItemId'] = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def work_item_id=(value)
|
57
|
+
git['workItemId'] = value
|
58
|
+
end
|
59
|
+
|
60
|
+
def clear_local
|
61
|
+
cli.abort 'No local configuration was found' unless local_available?
|
62
|
+
|
63
|
+
git['organizationName'] = nil
|
64
|
+
git['projectName'] = nil
|
65
|
+
git['boardId'] = nil
|
66
|
+
git['workItemId'] = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def clear_global
|
70
|
+
git.global.keys.each do |key|
|
71
|
+
cli.puts 'Deleting configuration: ' + key
|
72
|
+
git.global[key] = nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def username_for_organization(organization_name)
|
77
|
+
username_key = "organizations.#{organization_name}.username"
|
78
|
+
|
79
|
+
return git.global[username_key] unless git.global[username_key].nil?
|
80
|
+
|
81
|
+
git.global[username_key] = cli.prompt([
|
82
|
+
"Please provide your username for the DevOps organization (#{organization_name}).",
|
83
|
+
'',
|
84
|
+
'Enter username'
|
85
|
+
].join("\n"))
|
86
|
+
end
|
87
|
+
|
88
|
+
def access_token_for_organization(organization_name)
|
89
|
+
access_token_key = "organizations.#{organization_name}.accessToken"
|
90
|
+
|
91
|
+
return git.global[access_token_key] unless git.global[access_token_key].nil?
|
92
|
+
|
93
|
+
git.global[access_token_key] = cli.prompt([
|
94
|
+
"Please provide your personal access token for the DevOps organization (#{organization_name}).",
|
95
|
+
'If you don\'t have one, follow the guide here: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate',
|
96
|
+
'',
|
97
|
+
'The token MUST have "Read" permission for Work Items',
|
98
|
+
'Future features will likely require "Write" or "Manage"',
|
99
|
+
'',
|
100
|
+
'Enter access token'
|
101
|
+
].join("\n"))
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
attr_reader :git
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -44,9 +44,10 @@ module Abt
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def clear_global
|
47
|
-
git.global
|
48
|
-
|
49
|
-
|
47
|
+
git.global.keys.each do |key|
|
48
|
+
cli.puts 'Deleting configuration: ' + key
|
49
|
+
git.global[key] = nil
|
50
|
+
end
|
50
51
|
end
|
51
52
|
|
52
53
|
def access_token
|
data/lib/abt/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abt-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jesper Sørensen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-01-
|
11
|
+
date: 2021-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-inflector
|
@@ -100,6 +100,17 @@ files:
|
|
100
100
|
- "./lib/abt/providers/asana/commands/start.rb"
|
101
101
|
- "./lib/abt/providers/asana/commands/tasks.rb"
|
102
102
|
- "./lib/abt/providers/asana/configuration.rb"
|
103
|
+
- "./lib/abt/providers/devops.rb"
|
104
|
+
- "./lib/abt/providers/devops/api.rb"
|
105
|
+
- "./lib/abt/providers/devops/base_command.rb"
|
106
|
+
- "./lib/abt/providers/devops/commands/clear.rb"
|
107
|
+
- "./lib/abt/providers/devops/commands/clear_global.rb"
|
108
|
+
- "./lib/abt/providers/devops/commands/current.rb"
|
109
|
+
- "./lib/abt/providers/devops/commands/harvest_time_entry_data.rb"
|
110
|
+
- "./lib/abt/providers/devops/commands/init.rb"
|
111
|
+
- "./lib/abt/providers/devops/commands/pick.rb"
|
112
|
+
- "./lib/abt/providers/devops/commands/share.rb"
|
113
|
+
- "./lib/abt/providers/devops/configuration.rb"
|
103
114
|
- "./lib/abt/providers/harvest.rb"
|
104
115
|
- "./lib/abt/providers/harvest/api.rb"
|
105
116
|
- "./lib/abt/providers/harvest/base_command.rb"
|