abt-cli 0.0.10 → 0.0.15

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +0 -6
  3. data/lib/abt.rb +8 -0
  4. data/lib/abt/cli.rb +27 -11
  5. data/lib/abt/cli/prompt.rb +124 -0
  6. data/lib/abt/git_config.rb +28 -11
  7. data/lib/abt/helpers.rb +1 -1
  8. data/lib/abt/providers/asana/base_command.rb +11 -0
  9. data/lib/abt/providers/asana/commands/add.rb +75 -0
  10. data/lib/abt/providers/asana/commands/branch-name.rb +44 -0
  11. data/lib/abt/providers/asana/commands/current.rb +3 -3
  12. data/lib/abt/providers/asana/commands/finalize.rb +1 -1
  13. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +3 -4
  14. data/lib/abt/providers/asana/commands/init.rb +7 -2
  15. data/lib/abt/providers/asana/commands/pick.rb +3 -2
  16. data/lib/abt/providers/asana/commands/share.rb +3 -3
  17. data/lib/abt/providers/asana/commands/start.rb +3 -3
  18. data/lib/abt/providers/asana/commands/tasks.rb +2 -0
  19. data/lib/abt/providers/asana/configuration.rb +7 -5
  20. data/lib/abt/providers/devops.rb +19 -0
  21. data/lib/abt/providers/devops/api.rb +95 -0
  22. data/lib/abt/providers/devops/base_command.rb +98 -0
  23. data/lib/abt/providers/devops/commands/boards.rb +34 -0
  24. data/lib/abt/providers/devops/commands/branch-name.rb +45 -0
  25. data/lib/abt/providers/devops/commands/clear.rb +24 -0
  26. data/lib/abt/providers/devops/commands/clear_global.rb +24 -0
  27. data/lib/abt/providers/devops/commands/current.rb +93 -0
  28. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +53 -0
  29. data/lib/abt/providers/devops/commands/init.rb +72 -0
  30. data/lib/abt/providers/devops/commands/pick.rb +78 -0
  31. data/lib/abt/providers/devops/commands/share.rb +26 -0
  32. data/lib/abt/providers/devops/commands/work-items.rb +46 -0
  33. data/lib/abt/providers/devops/configuration.rb +110 -0
  34. data/lib/abt/providers/git.rb +19 -0
  35. data/lib/abt/providers/git/commands/branch.rb +80 -0
  36. data/lib/abt/providers/harvest/base_command.rb +11 -0
  37. data/lib/abt/providers/harvest/commands/current.rb +3 -3
  38. data/lib/abt/providers/harvest/commands/init.rb +2 -2
  39. data/lib/abt/providers/harvest/commands/pick.rb +2 -1
  40. data/lib/abt/providers/harvest/commands/start.rb +2 -4
  41. data/lib/abt/providers/harvest/commands/tasks.rb +2 -0
  42. data/lib/abt/providers/harvest/commands/track.rb +2 -3
  43. data/lib/abt/providers/harvest/configuration.rb +6 -5
  44. data/lib/abt/version.rb +1 -1
  45. metadata +21 -4
  46. data/lib/abt/cli/dialogs.rb +0 -86
  47. data/lib/abt/cli/io.rb +0 -23
@@ -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.command
9
+ '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 call
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
@@ -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,93 @@
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
+ require_board!
18
+
19
+ if same_args_as_config? || !config.local_available?
20
+ show_current_configuration
21
+ else
22
+ cli.warn 'Updating configuration'
23
+ update_configuration
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def show_current_configuration
30
+ if work_item_id.nil?
31
+ print_board(organization_name, project_name, board)
32
+ else
33
+ print_work_item(organization_name, project_name, board, work_item)
34
+ end
35
+ end
36
+
37
+ def update_configuration
38
+ ensure_board_is_valid!
39
+
40
+ if work_item_id.nil?
41
+ update_board_config
42
+ config.work_item_id = nil
43
+
44
+ print_board(organization_name, project_name, board)
45
+ else
46
+ ensure_work_item_is_valid!
47
+
48
+ update_board_config
49
+ config.work_item_id = work_item_id
50
+
51
+ print_work_item(organization_name, project_name, board, work_item)
52
+ end
53
+ end
54
+
55
+ def update_board_config
56
+ config.organization_name = organization_name
57
+ config.project_name = project_name
58
+ config.board_id = board_id
59
+ end
60
+
61
+ def ensure_board_is_valid!
62
+ if board.nil?
63
+ cli.abort 'Board could not be found, ensure that settings for organization, project, and board are correct'
64
+ end
65
+ end
66
+
67
+ def ensure_work_item_is_valid!
68
+ cli.abort "No such work item: ##{work_item_id}" if work_item.nil?
69
+ end
70
+
71
+ def board
72
+ @board ||= begin
73
+ cli.warn 'Fetching board...'
74
+ api.get("work/boards/#{board_id}")
75
+ rescue HttpError::NotFoundError
76
+ nil
77
+ end
78
+ end
79
+
80
+ def work_item
81
+ @work_item ||= begin
82
+ cli.warn 'Fetching work item...'
83
+ work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
84
+ sanitize_work_item(work_item)
85
+ rescue HttpError::NotFoundError
86
+ nil
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,53 @@
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
+ require_work_item!
18
+
19
+ body = {
20
+ notes: notes,
21
+ external_reference: {
22
+ id: work_item['id'],
23
+ group_id: 'AzureDevOpsWorkItem',
24
+ permalink: work_item['url']
25
+ }
26
+ }
27
+
28
+ cli.puts Oj.dump(body, mode: :json)
29
+ end
30
+
31
+ private
32
+
33
+ def notes
34
+ [
35
+ 'Azure DevOps',
36
+ work_item['fields']['System.WorkItemType'],
37
+ "##{work_item['id']}",
38
+ '-',
39
+ work_item['name']
40
+ ].join(' ')
41
+ end
42
+
43
+ def work_item
44
+ @work_item ||= begin
45
+ work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
46
+ sanitize_work_item(work_item)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ 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.text([
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,78 @@
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
+ require_board!
19
+
20
+ cli.warn "#{project_name} - #{board['name']}"
21
+
22
+ work_item = select_work_item
23
+
24
+ update_config!(work_item)
25
+
26
+ print_work_item(organization_name, project_name, board, work_item)
27
+ end
28
+
29
+ private
30
+
31
+ def update_config!(work_item)
32
+ config.organization_name = organization_name
33
+ config.project_name = project_name
34
+ config.board_id = board_id
35
+ config.work_item_id = work_item['id']
36
+ end
37
+
38
+ def select_work_item
39
+ loop do
40
+ column = cli.prompt.choice 'Which column?', columns
41
+ cli.warn 'Fetching work items...'
42
+ work_items = work_items_in_column(column)
43
+
44
+ if work_items.length.zero?
45
+ cli.warn 'Section is empty'
46
+ next
47
+ end
48
+
49
+ work_item = cli.prompt.choice 'Select a work item', work_items, true
50
+ return work_item if work_item
51
+ end
52
+ end
53
+
54
+ def work_items_in_column(column)
55
+ work_items = api.work_item_query(
56
+ <<~WIQL
57
+ SELECT [System.Id]
58
+ FROM WorkItems
59
+ WHERE [System.BoardColumn] = '#{column['name']}'
60
+ ORDER BY [Microsoft.VSTS.Common.BacklogPriority] ASC
61
+ WIQL
62
+ )
63
+
64
+ work_items.map { |work_item| sanitize_work_item(work_item) }
65
+ end
66
+
67
+ def columns
68
+ board['columns']
69
+ end
70
+
71
+ def board
72
+ @board ||= api.get("work/boards/#{board_id}")
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,26 @@
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
+ require_work_item!
18
+
19
+ args = [organization_name, project_name, board_id, work_item_id].compact
20
+ cli.print_provider_command('devops', args.join('/'))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ 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.command
9
+ '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 call
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