abt-cli 0.0.29 → 0.0.30

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fbc512b85932a7c8cfa8e6d9ec10176f3e2ac8711df78ad4ce67100ac79f003
4
- data.tar.gz: 7dc1c727ced7d316c84075458b75839bd931050e50c9ffe39f925324c6a8e63d
3
+ metadata.gz: 7f0389f2300f5dc3f3143f6672d770a7dc50cce27b77774915eba3873cdfc89c
4
+ data.tar.gz: bc7952525895e85a0b2d0b0e679ebf98a687647e86bf2b4250d9dcb0abc1e4ac
5
5
  SHA512:
6
- metadata.gz: 74f84e028cf84f156ee18f46177fae4e17f857eb538362daf2baf3b017f1ace428a8b9c16aa912ba0c486fdc13cbf2a41402fa64917fa74ee9578c6e041c6d7b
7
- data.tar.gz: fe06b4314f9a119950979b7a8c966758a56b4a3619dcabc9328209beaca94f317851bc9dedbc986a955a120afe29da0707225e78896e1517854c24864bba2efc
6
+ metadata.gz: 484a395a41009899bb56e42049f0acb33e764ff8b4f54fbabdb5e7a468e663717f537a9b6e29934b02e6d668b7b6f3218fca35f6de3983ff32dd3e51a48ba7b3
7
+ data.tar.gz: 0d1651b94be6bbc6c93fb68fee3b1ca2dab62aa5160994b29b0009ee68f77090c3303b145cb3aae49305c5742ace9153a7392c637c566422cdec088923de61df
@@ -4,15 +4,22 @@ module Abt
4
4
  module Providers
5
5
  module Devops
6
6
  class Api
7
+ # Shamelessly copied from ERB::Util.url_encode
8
+ # https://apidock.com/ruby/ERB/Util/url_encode
9
+ def self.rfc_3986_encode_path_segment(string)
10
+ string.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/) do |match|
11
+ format("%%%02X", match.unpack1("C"))
12
+ end
13
+ end
14
+
7
15
  VERBS = [:get, :post, :put].freeze
8
16
 
9
17
  CONDITIONAL_ACCESS_POLICY_ERROR_CODE = "VS403463"
10
18
 
11
19
  attr_reader :organization_name, :project_name, :username, :access_token, :cli
12
20
 
13
- def initialize(organization_name:, project_name:, username:, access_token:, cli:)
21
+ def initialize(organization_name:, username:, access_token:, cli:)
14
22
  @organization_name = organization_name
15
- @project_name = project_name
16
23
  @username = username
17
24
  @access_token = access_token
18
25
  @cli = cli
@@ -32,12 +39,12 @@ module Abt
32
39
  end
33
40
 
34
41
  def work_item_query(wiql)
35
- response = post("wit/wiql", Oj.dump({ query: wiql }, mode: :json))
42
+ response = post("_apis/wit/wiql", Oj.dump({ query: wiql }, mode: :json))
36
43
  ids = response["workItems"].map { |work_item| work_item["id"] }
37
44
 
38
45
  work_items = []
39
46
  ids.each_slice(200) do |page_ids|
40
- work_items += get_paged("wit/workitems", ids: page_ids.join(","))
47
+ work_items += get_paged("_apis/wit/workitems", ids: page_ids.join(","))
41
48
  end
42
49
 
43
50
  work_items
@@ -58,19 +65,17 @@ module Abt
58
65
  end
59
66
 
60
67
  def base_url
61
- "https://#{organization_name}.visualstudio.com/#{project_name}"
62
- end
63
-
64
- def api_endpoint
65
- "#{base_url}/_apis"
68
+ "https://#{organization_name}.visualstudio.com"
66
69
  end
67
70
 
68
71
  def url_for_work_item(work_item)
69
- "#{base_url}/_workitems/edit/#{work_item['id']}"
72
+ project_name = self.class.rfc_3986_encode_path_segment(work_item["fields"]["System.TeamProject"])
73
+ "#{base_url}/#{project_name}/_workitems/edit/#{work_item['id']}"
70
74
  end
71
75
 
72
- def url_for_board(board)
73
- "#{base_url}/_boards/board/#{rfc_3986_encode_path_segment(board['name'])}"
76
+ def url_for_board(project_name, team_name, board)
77
+ board_name = self.class.rfc_3986_encode_path_segment(board["name"])
78
+ "#{base_url}/#{project_name}/_boards/board/t/#{team_name}/#{board_name}"
74
79
  end
75
80
 
76
81
  def sanitize_work_item(work_item)
@@ -84,7 +89,7 @@ module Abt
84
89
  end
85
90
 
86
91
  def connection
87
- @connection ||= Faraday.new(api_endpoint) do |connection|
92
+ @connection ||= Faraday.new(base_url) do |connection|
88
93
  connection.basic_auth(username, access_token)
89
94
  connection.headers["Content-Type"] = "application/json"
90
95
  connection.headers["Accept"] = "application/json; api-version=6.0"
@@ -93,14 +98,6 @@ module Abt
93
98
 
94
99
  private
95
100
 
96
- # Shamelessly copied from ERB::Util.url_encode
97
- # https://apidock.com/ruby/ERB/Util/url_encode
98
- def rfc_3986_encode_path_segment(string)
99
- string.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/) do |match|
100
- format("%%%02X", match.unpack1("C"))
101
- end
102
- end
103
-
104
101
  def handle_denied_by_conditional_access_policy!(exception)
105
102
  raise exception unless exception.message.include?(CONDITIONAL_ACCESS_POLICY_ERROR_CODE)
106
103
 
@@ -8,7 +8,7 @@ module Abt
8
8
 
9
9
  attr_reader :config, :path
10
10
 
11
- def_delegators(:@path, :organization_name, :project_name, :board_id, :work_item_id)
11
+ def_delegators(:@path, :organization_name, :project_name, :team_name, :board_name, :work_item_id)
12
12
 
13
13
  def initialize(ari:, cli:)
14
14
  super
@@ -24,7 +24,7 @@ module Abt
24
24
  end
25
25
 
26
26
  def require_board!
27
- return if board_id && organization_name && project_name
27
+ return if board_name && organization_name && project_name && team_name
28
28
 
29
29
  abort("No current/specified board. Did you forget to `pick`?")
30
30
  end
@@ -36,12 +36,8 @@ module Abt
36
36
  abort("No current/specified work item. Did you forget to `pick`?")
37
37
  end
38
38
 
39
- def prompt_project!
40
- @path = Services::ProjectPicker.call(cli: cli).path
41
- end
42
-
43
39
  def prompt_board!
44
- result = Services::BoardPicker.call(cli: cli, path: path, config: config)
40
+ result = Services::BoardPicker.call(cli: cli, config: config)
45
41
  @path = result.path
46
42
  @board = result.board
47
43
  end
@@ -54,7 +50,7 @@ module Abt
54
50
 
55
51
  def board
56
52
  @board ||= begin
57
- api.get("work/boards/#{board_id}")
53
+ api.get("#{project_name}/#{team_name}/_apis/work/boards/#{board_name}")
58
54
  rescue HttpError::NotFoundError
59
55
  nil
60
56
  end
@@ -62,33 +58,34 @@ module Abt
62
58
 
63
59
  def work_item
64
60
  @work_item ||= begin
65
- work_item = api.get_paged("wit/workitems", ids: work_item_id)[0]
61
+ work_item = api.get_paged("_apis/wit/workitems", ids: work_item_id)[0]
66
62
  api.sanitize_work_item(work_item)
67
63
  rescue HttpError::NotFoundError
68
64
  nil
69
65
  end
70
66
  end
71
67
 
72
- def print_board(organization_name, project_name, board)
73
- path = "#{organization_name}/#{project_name}/#{board['id']}"
68
+ def print_board(organization_name, project_name, team_name, board)
69
+ board_name = Api.rfc_3986_encode_path_segment(board["name"])
70
+ path = "#{organization_name}/#{project_name}/#{team_name}/#{board_name}"
74
71
 
75
72
  cli.print_ari("devops", path, board["name"])
76
- warn(api.url_for_board(board)) if cli.output.isatty
73
+ warn(api.url_for_board(project_name, team_name, board)) if cli.output.isatty
77
74
  end
78
75
 
79
- def print_work_item(organization, project, board, work_item)
80
- path = "#{organization}/#{project}/#{board['id']}/#{work_item['id']}"
76
+ def print_work_item(organization, project, team_name, board, work_item)
77
+ board_name = Api.rfc_3986_encode_path_segment(board["name"])
78
+ path = "#{organization}/#{project}/#{team_name}/#{board_name}/#{work_item['id']}"
81
79
 
82
80
  cli.print_ari("devops", path, work_item["name"])
83
81
  warn(work_item["url"]) if work_item.key?("url") && cli.output.isatty
84
82
  end
85
83
 
86
84
  def api
87
- Abt::Providers::Devops::Api.new(organization_name: organization_name,
88
- project_name: project_name,
89
- username: config.username_for_organization(organization_name),
90
- access_token: config.access_token_for_organization(organization_name),
91
- cli: cli)
85
+ Api.new(organization_name: organization_name,
86
+ username: config.username_for_organization(organization_name),
87
+ access_token: config.access_token_for_organization(organization_name),
88
+ cli: cli)
92
89
  end
93
90
  end
94
91
  end
@@ -19,11 +19,9 @@ module Abt
19
19
  if work_item
20
20
  puts name
21
21
  else
22
- args = [organization_name, project_name, board_id, work_item_id].compact
23
-
24
22
  abort(<<~TXT)
25
23
  Unable to find work item for configuration:
26
- devops:#{args.join('/')}
24
+ devops:#{path}
27
25
  TXT
28
26
  end
29
27
  end
@@ -30,9 +30,9 @@ module Abt
30
30
 
31
31
  def print_configuration
32
32
  if work_item_id.nil?
33
- print_board(organization_name, project_name, board)
33
+ print_board(organization_name, project_name, team_name, board)
34
34
  else
35
- print_work_item(organization_name, project_name, board, work_item)
35
+ print_work_item(organization_name, project_name, team_name, board, work_item)
36
36
  end
37
37
  end
38
38
 
@@ -19,11 +19,9 @@ module Abt
19
19
  if work_item
20
20
  puts Oj.dump(body, mode: :json)
21
21
  else
22
- args = [organization_name, project_name, board_id, work_item_id].compact
23
-
24
22
  abort(<<~TXT)
25
23
  Unable to find work item for configuration:
26
- devops:#{args.join('/')}
24
+ devops:#{path}
27
25
  TXT
28
26
  end
29
27
  end
@@ -23,7 +23,7 @@ module Abt
23
23
  def perform
24
24
  pick!
25
25
 
26
- print_work_item(organization_name, project_name, board, work_item)
26
+ print_work_item(organization_name, project_name, team_name, board, work_item)
27
27
 
28
28
  return if flags[:"dry-run"]
29
29
 
@@ -37,8 +37,7 @@ module Abt
37
37
  private
38
38
 
39
39
  def pick!
40
- prompt_project! if project_name.nil? || flags[:clean]
41
- prompt_board! if board_id.nil? || flags[:clean]
40
+ prompt_board! if board_name.nil? || flags[:clean]
42
41
  prompt_work_item!
43
42
  end
44
43
  end
@@ -14,11 +14,10 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
- prompt_project! unless project_name
18
- prompt_board! unless board_id
17
+ prompt_board! unless board_name
19
18
 
20
19
  work_items.each do |work_item|
21
- print_work_item(organization_name, project_name, board, work_item)
20
+ print_work_item(organization_name, project_name, team_name, board, work_item)
22
21
  end
23
22
  end
24
23
 
@@ -20,8 +20,7 @@ module Abt
20
20
  end
21
21
 
22
22
  def perform
23
- prompt_project! if project_name.nil? || flags[:clean]
24
- prompt_board! if board_id.nil? || flags[:clean]
23
+ prompt_board! if board_name.nil? || flags[:clean]
25
24
 
26
25
  update_directory_config!
27
26
 
@@ -35,7 +34,8 @@ module Abt
35
34
  "path" => Path.from_ids(
36
35
  organization_name: organization_name,
37
36
  project_name: project_name,
38
- board_id: board_id
37
+ team_name: team_name,
38
+ board_name: board_name
39
39
  ).to_s
40
40
  }
41
41
  cli.directory_config.save!
@@ -6,16 +6,28 @@ module Abt
6
6
  class Path < String
7
7
  ORGANIZATION_NAME_REGEX = %r{(?<organization_name>[^/ ]+)}.freeze
8
8
  PROJECT_NAME_REGEX = %r{(?<project_name>[^/ ]+)}.freeze
9
- BOARD_ID_REGEX = /(?<board_id>[a-z0-9\-]+)/.freeze
9
+ TEAM_NAME_REGEX = %r{(?<team_name>[^/ ]+)}.freeze
10
+ BOARD_NAME_REGEX = %r{(?<board_name>[^/ ]+)}.freeze
10
11
  WORK_ITEM_ID_REGEX = /(?<work_item_id>\d+)/.freeze
11
12
 
12
13
  PATH_REGEX =
13
- %r{^(#{ORGANIZATION_NAME_REGEX}/#{PROJECT_NAME_REGEX}(/#{BOARD_ID_REGEX}(/#{WORK_ITEM_ID_REGEX})?)?)?}.freeze
14
+ %r{^(#{ORGANIZATION_NAME_REGEX}/#{PROJECT_NAME_REGEX}(/#{TEAM_NAME_REGEX}(/#{BOARD_NAME_REGEX}(/#{WORK_ITEM_ID_REGEX})?)?)?)?}.freeze # rubocop:disable Layout/LineLength
14
15
 
15
- def self.from_ids(organization_name: nil, project_name: nil, board_id: nil, work_item_id: nil)
16
+ def self.from_ids(organization_name: nil, project_name: nil, team_name: nil, board_name: nil, work_item_id: nil)
16
17
  return new unless organization_name && project_name
17
18
 
18
- new([organization_name, project_name, *board_id, *work_item_id].join("/"))
19
+ parts = [organization_name, project_name]
20
+
21
+ if team_name
22
+ parts << team_name
23
+
24
+ if board_name
25
+ parts << board_name
26
+ parts << work_item_id if work_item_id
27
+ end
28
+ end
29
+
30
+ new(parts.join("/"))
19
31
  end
20
32
 
21
33
  def initialize(path = "")
@@ -32,8 +44,12 @@ module Abt
32
44
  match[:project_name]
33
45
  end
34
46
 
35
- def board_id
36
- match[:board_id]
47
+ def team_name
48
+ match[:team_name]
49
+ end
50
+
51
+ def board_name
52
+ match[:board_name]
37
53
  end
38
54
 
39
55
  def work_item_id
@@ -20,36 +20,47 @@ module Abt
20
20
 
21
21
  attr_reader :cli, :config, :path
22
22
 
23
- def initialize(cli:, path:, config:)
23
+ def initialize(cli:, config:)
24
24
  @cli = cli
25
25
  @config = config
26
- @path = path
27
26
  end
28
27
 
29
28
  def call
29
+ @path = ProjectPicker.call(cli: cli).path
30
30
  board = cli.prompt.choice("Select a project work board", boards)
31
31
 
32
- path_with_board = Path.from_ids(
32
+ Result.new(board: board, path: path_with_board(team, board))
33
+ end
34
+
35
+ private
36
+
37
+ def path_with_board(team, board)
38
+ Path.from_ids(
33
39
  organization_name: path.organization_name,
34
40
  project_name: path.project_name,
35
- board_id: board["id"]
41
+ team_name: Api.rfc_3986_encode_path_segment(team["name"]),
42
+ board_name: Api.rfc_3986_encode_path_segment(board["name"])
36
43
  )
44
+ end
37
45
 
38
- Result.new(board: board, path: path_with_board)
46
+ def team
47
+ @team ||= cli.prompt.choice("Select a team", teams)
39
48
  end
40
49
 
41
- private
50
+ def teams
51
+ @teams ||= api.get_paged("/_apis/projects/#{path.project_name}/teams")
52
+ end
42
53
 
43
54
  def boards
44
- @boards ||= api.get_paged("work/boards")
55
+ team_name = Api.rfc_3986_encode_path_segment(team["name"])
56
+ @boards ||= api.get_paged("#{path.project_name}/#{team_name}/_apis/work/boards")
45
57
  end
46
58
 
47
59
  def api
48
- Abt::Providers::Devops::Api.new(organization_name: path.organization_name,
49
- project_name: path.project_name,
50
- username: config.username_for_organization(path.organization_name),
51
- access_token: config.access_token_for_organization(path.organization_name),
52
- cli: cli)
60
+ Api.new(organization_name: path.organization_name,
61
+ username: config.username_for_organization(path.organization_name),
62
+ access_token: config.access_token_for_organization(path.organization_name),
63
+ cli: cli)
53
64
  end
54
65
  end
55
66
  end
@@ -33,7 +33,8 @@ module Abt
33
33
  path_with_work_item = Path.from_ids(
34
34
  organization_name: path.organization_name,
35
35
  project_name: path.project_name,
36
- board_id: path.board_id,
36
+ team_name: path.team_name,
37
+ board_name: path.board_name,
37
38
  work_item_id: work_item["id"]
38
39
  )
39
40
 
@@ -79,14 +80,14 @@ module Abt
79
80
  end
80
81
 
81
82
  def columns
82
- board["columns"] || api.get("work/boards/#{path.board_id}")["columns"]
83
+ board["columns"] ||
84
+ api.get("#{path.project_name}/#{path.team_name}/_apis/work/boards/#{path.board_name}")["columns"]
83
85
  end
84
86
 
85
87
  private
86
88
 
87
89
  def api
88
90
  Abt::Providers::Devops::Api.new(organization_name: path.organization_name,
89
- project_name: path.project_name,
90
91
  username: config.username_for_organization(path.organization_name),
91
92
  access_token: config.access_token_for_organization(path.organization_name),
92
93
  cli: cli)
data/lib/abt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Abt
4
- VERSION = "0.0.29"
4
+ VERSION = "0.0.30"
5
5
  end
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.29
4
+ version: 0.0.30
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-03-28 00:00:00.000000000 Z
11
+ date: 2021-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-inflector
@@ -118,7 +118,6 @@ files:
118
118
  - "./lib/abt/providers/devops.rb"
119
119
  - "./lib/abt/providers/devops/api.rb"
120
120
  - "./lib/abt/providers/devops/base_command.rb"
121
- - "./lib/abt/providers/devops/commands/boards.rb"
122
121
  - "./lib/abt/providers/devops/commands/branch_name.rb"
123
122
  - "./lib/abt/providers/devops/commands/clear.rb"
124
123
  - "./lib/abt/providers/devops/commands/current.rb"
@@ -1,33 +0,0 @@
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
- prompt_project! unless project_name
18
-
19
- boards.map do |board|
20
- print_board(organization_name, project_name, board)
21
- end
22
- end
23
-
24
- private
25
-
26
- def boards
27
- @boards ||= api.get_paged("work/boards")
28
- end
29
- end
30
- end
31
- end
32
- end
33
- end