abt-cli 0.0.19 → 0.0.24
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 +3 -3
- data/lib/abt.rb +6 -6
- data/lib/abt/ari.rb +2 -2
- data/lib/abt/ari_list.rb +1 -1
- data/lib/abt/base_command.rb +7 -7
- data/lib/abt/cli.rb +49 -47
- data/lib/abt/cli/arguments_parser.rb +6 -3
- data/lib/abt/cli/global_commands.rb +23 -0
- data/lib/abt/cli/global_commands/commands.rb +23 -0
- data/lib/abt/cli/global_commands/examples.rb +23 -0
- data/lib/abt/cli/global_commands/help.rb +23 -0
- data/lib/abt/cli/global_commands/readme.rb +23 -0
- data/lib/abt/cli/global_commands/share.rb +36 -0
- data/lib/abt/cli/global_commands/version.rb +23 -0
- data/lib/abt/cli/prompt.rb +64 -52
- data/lib/abt/docs.rb +48 -26
- data/lib/abt/docs/cli.rb +3 -3
- data/lib/abt/docs/markdown.rb +10 -7
- data/lib/abt/git_config.rb +4 -6
- data/lib/abt/helpers.rb +26 -8
- data/lib/abt/providers/asana/api.rb +9 -9
- data/lib/abt/providers/asana/base_command.rb +12 -10
- data/lib/abt/providers/asana/commands/add.rb +13 -12
- data/lib/abt/providers/asana/commands/branch_name.rb +8 -8
- data/lib/abt/providers/asana/commands/clear.rb +7 -8
- data/lib/abt/providers/asana/commands/current.rb +14 -15
- data/lib/abt/providers/asana/commands/finalize.rb +17 -18
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +18 -16
- data/lib/abt/providers/asana/commands/init.rb +8 -41
- data/lib/abt/providers/asana/commands/pick.rb +22 -26
- data/lib/abt/providers/asana/commands/projects.rb +5 -5
- data/lib/abt/providers/asana/commands/share.rb +7 -5
- data/lib/abt/providers/asana/commands/start.rb +28 -21
- data/lib/abt/providers/asana/commands/tasks.rb +6 -6
- data/lib/abt/providers/asana/configuration.rb +37 -29
- data/lib/abt/providers/asana/path.rb +6 -6
- data/lib/abt/providers/devops/api.rb +12 -12
- data/lib/abt/providers/devops/base_command.rb +14 -10
- data/lib/abt/providers/devops/commands/boards.rb +5 -7
- data/lib/abt/providers/devops/commands/branch_name.rb +9 -9
- data/lib/abt/providers/devops/commands/clear.rb +7 -8
- data/lib/abt/providers/devops/commands/current.rb +17 -18
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +21 -19
- data/lib/abt/providers/devops/commands/init.rb +21 -14
- data/lib/abt/providers/devops/commands/pick.rb +25 -19
- data/lib/abt/providers/devops/commands/share.rb +7 -5
- data/lib/abt/providers/devops/commands/{work-items.rb → work_items.rb} +3 -3
- data/lib/abt/providers/devops/configuration.rb +15 -15
- data/lib/abt/providers/devops/path.rb +7 -6
- data/lib/abt/providers/git/commands/branch.rb +23 -21
- data/lib/abt/providers/harvest/api.rb +8 -8
- data/lib/abt/providers/harvest/base_command.rb +10 -8
- data/lib/abt/providers/harvest/commands/clear.rb +7 -8
- data/lib/abt/providers/harvest/commands/current.rb +13 -14
- data/lib/abt/providers/harvest/commands/init.rb +10 -39
- data/lib/abt/providers/harvest/commands/pick.rb +15 -11
- data/lib/abt/providers/harvest/commands/projects.rb +5 -5
- data/lib/abt/providers/harvest/commands/share.rb +7 -5
- data/lib/abt/providers/harvest/commands/start.rb +5 -3
- data/lib/abt/providers/harvest/commands/stop.rb +12 -12
- data/lib/abt/providers/harvest/commands/tasks.rb +7 -7
- data/lib/abt/providers/harvest/commands/track.rb +52 -37
- data/lib/abt/providers/harvest/configuration.rb +18 -18
- data/lib/abt/providers/harvest/path.rb +6 -6
- data/lib/abt/version.rb +1 -1
- metadata +12 -5
| @@ -4,15 +4,15 @@ module Abt | |
| 4 4 | 
             
              module Providers
         | 
| 5 5 | 
             
                module Asana
         | 
| 6 6 | 
             
                  class Path < String
         | 
| 7 | 
            -
                    PATH_REGEX = %r{^(?<project_gid>\d+) | 
| 7 | 
            +
                    PATH_REGEX = %r{^(?<project_gid>\d+)?/?(?<task_gid>\d+)?$}.freeze
         | 
| 8 8 |  | 
| 9 | 
            -
                    def self.from_ids(project_gid  | 
| 10 | 
            -
                      path = project_gid ? [project_gid, *task_gid].join( | 
| 11 | 
            -
                      new | 
| 9 | 
            +
                    def self.from_ids(project_gid: nil, task_gid: nil)
         | 
| 10 | 
            +
                      path = project_gid ? [project_gid, *task_gid].join("/") : ""
         | 
| 11 | 
            +
                      new(path)
         | 
| 12 12 | 
             
                    end
         | 
| 13 13 |  | 
| 14 | 
            -
                    def initialize(path =  | 
| 15 | 
            -
                      raise Abt::Cli::Abort, "Invalid path: #{path}" unless path | 
| 14 | 
            +
                    def initialize(path = "")
         | 
| 15 | 
            +
                      raise Abt::Cli::Abort, "Invalid path: #{path}" unless PATH_REGEX.match?(path)
         | 
| 16 16 |  | 
| 17 17 | 
             
                      super
         | 
| 18 18 | 
             
                    end
         | 
| @@ -4,9 +4,9 @@ module Abt | |
| 4 4 | 
             
              module Providers
         | 
| 5 5 | 
             
                module Devops
         | 
| 6 6 | 
             
                  class Api
         | 
| 7 | 
            -
                    VERBS =  | 
| 7 | 
            +
                    VERBS = [:get, :post, :put].freeze
         | 
| 8 8 |  | 
| 9 | 
            -
                    CONDITIONAL_ACCESS_POLICY_ERROR_CODE =  | 
| 9 | 
            +
                    CONDITIONAL_ACCESS_POLICY_ERROR_CODE = "VS403463"
         | 
| 10 10 |  | 
| 11 11 | 
             
                    attr_reader :organization_name, :project_name, :username, :access_token, :cli
         | 
| 12 12 |  | 
| @@ -26,18 +26,18 @@ module Abt | |
| 26 26 |  | 
| 27 27 | 
             
                    def get_paged(path, query = {})
         | 
| 28 28 | 
             
                      result = request(:get, path, query)
         | 
| 29 | 
            -
                      result[ | 
| 29 | 
            +
                      result["value"]
         | 
| 30 30 |  | 
| 31 31 | 
             
                      # TODO: Loop if necessary
         | 
| 32 32 | 
             
                    end
         | 
| 33 33 |  | 
| 34 34 | 
             
                    def work_item_query(wiql)
         | 
| 35 | 
            -
                      response = post( | 
| 36 | 
            -
                      ids = response[ | 
| 35 | 
            +
                      response = post("wit/wiql", Oj.dump({ query: wiql }, mode: :json))
         | 
| 36 | 
            +
                      ids = response["workItems"].map { |work_item| work_item["id"] }
         | 
| 37 37 |  | 
| 38 38 | 
             
                      work_items = []
         | 
| 39 39 | 
             
                      ids.each_slice(200) do |page_ids|
         | 
| 40 | 
            -
                        work_items += get_paged( | 
| 40 | 
            +
                        work_items += get_paged("wit/workitems", ids: page_ids.join(","))
         | 
| 41 41 | 
             
                      end
         | 
| 42 42 |  | 
| 43 43 | 
             
                      work_items
         | 
| @@ -50,7 +50,7 @@ module Abt | |
| 50 50 | 
             
                        Oj.load(response.body)
         | 
| 51 51 | 
             
                      else
         | 
| 52 52 | 
             
                        error_class = Abt::HttpError.error_class_for_status(response.status)
         | 
| 53 | 
            -
                        encoded_response_body = response.body.force_encoding( | 
| 53 | 
            +
                        encoded_response_body = response.body.force_encoding("utf-8")
         | 
| 54 54 | 
             
                        raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
         | 
| 55 55 | 
             
                      end
         | 
| 56 56 | 
             
                    rescue Abt::HttpError::ForbiddenError => e
         | 
| @@ -75,9 +75,9 @@ module Abt | |
| 75 75 |  | 
| 76 76 | 
             
                    def connection
         | 
| 77 77 | 
             
                      @connection ||= Faraday.new(api_endpoint) do |connection|
         | 
| 78 | 
            -
                        connection.basic_auth | 
| 79 | 
            -
                        connection.headers[ | 
| 80 | 
            -
                        connection.headers[ | 
| 78 | 
            +
                        connection.basic_auth(username, access_token)
         | 
| 79 | 
            +
                        connection.headers["Content-Type"] = "application/json"
         | 
| 80 | 
            +
                        connection.headers["Accept"] = "application/json; api-version=6.0"
         | 
| 81 81 | 
             
                      end
         | 
| 82 82 | 
             
                    end
         | 
| 83 83 |  | 
| @@ -87,14 +87,14 @@ module Abt | |
| 87 87 | 
             
                    # https://apidock.com/ruby/ERB/Util/url_encode
         | 
| 88 88 | 
             
                    def rfc_3986_encode_path_segment(string)
         | 
| 89 89 | 
             
                      string.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/) do |match|
         | 
| 90 | 
            -
                        format( | 
| 90 | 
            +
                        format("%%%02X", match.unpack1("C"))
         | 
| 91 91 | 
             
                      end
         | 
| 92 92 | 
             
                    end
         | 
| 93 93 |  | 
| 94 94 | 
             
                    def handle_denied_by_conditional_access_policy!(exception)
         | 
| 95 95 | 
             
                      raise exception unless exception.message.include?(CONDITIONAL_ACCESS_POLICY_ERROR_CODE)
         | 
| 96 96 |  | 
| 97 | 
            -
                      cli.abort | 
| 97 | 
            +
                      cli.abort(<<~TXT)
         | 
| 98 98 | 
             
                        Access denied by conditional access policy.
         | 
| 99 99 | 
             
                        Try logging into the board using the URL below, then retry the command.
         | 
| 100 100 |  | 
| @@ -19,44 +19,48 @@ module Abt | |
| 19 19 |  | 
| 20 20 | 
             
                    private
         | 
| 21 21 |  | 
| 22 | 
            +
                    def require_local_config!
         | 
| 23 | 
            +
                      abort("Must be run inside a git repository") unless config.local_available?
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 22 26 | 
             
                    def require_board!
         | 
| 23 27 | 
             
                      return if organization_name && project_name && board_id
         | 
| 24 28 |  | 
| 25 | 
            -
                      abort | 
| 29 | 
            +
                      abort("No current/specified board. Did you initialize DevOps?")
         | 
| 26 30 | 
             
                    end
         | 
| 27 31 |  | 
| 28 32 | 
             
                    def require_work_item!
         | 
| 29 33 | 
             
                      unless organization_name && project_name && board_id
         | 
| 30 | 
            -
                        abort | 
| 34 | 
            +
                        abort("No current/specified board. Did you initialize DevOps and pick a work item?")
         | 
| 31 35 | 
             
                      end
         | 
| 32 36 |  | 
| 33 37 | 
             
                      return if work_item_id
         | 
| 34 38 |  | 
| 35 | 
            -
                      abort | 
| 39 | 
            +
                      abort("No current/specified work item. Did you pick a DevOps work item?")
         | 
| 36 40 | 
             
                    end
         | 
| 37 41 |  | 
| 38 42 | 
             
                    def sanitize_work_item(work_item)
         | 
| 39 43 | 
             
                      return nil if work_item.nil?
         | 
| 40 44 |  | 
| 41 45 | 
             
                      work_item.merge(
         | 
| 42 | 
            -
                         | 
| 43 | 
            -
                         | 
| 44 | 
            -
                         | 
| 46 | 
            +
                        "id" => work_item["id"].to_s,
         | 
| 47 | 
            +
                        "name" => work_item["fields"]["System.Title"],
         | 
| 48 | 
            +
                        "url" => api.url_for_work_item(work_item)
         | 
| 45 49 | 
             
                      )
         | 
| 46 50 | 
             
                    end
         | 
| 47 51 |  | 
| 48 52 | 
             
                    def print_board(organization_name, project_name, board)
         | 
| 49 53 | 
             
                      path = "#{organization_name}/#{project_name}/#{board['id']}"
         | 
| 50 54 |  | 
| 51 | 
            -
                      cli.print_ari( | 
| 52 | 
            -
                      warn | 
| 55 | 
            +
                      cli.print_ari("devops", path, board["name"])
         | 
| 56 | 
            +
                      warn(api.url_for_board(board)) if cli.output.isatty
         | 
| 53 57 | 
             
                    end
         | 
| 54 58 |  | 
| 55 59 | 
             
                    def print_work_item(organization, project, board, work_item)
         | 
| 56 60 | 
             
                      path = "#{organization}/#{project}/#{board['id']}/#{work_item['id']}"
         | 
| 57 61 |  | 
| 58 | 
            -
                      cli.print_ari( | 
| 59 | 
            -
                      warn | 
| 62 | 
            +
                      cli.print_ari("devops", path, work_item["name"])
         | 
| 63 | 
            +
                      warn(work_item["url"]) if work_item.key?("url") && cli.output.isatty
         | 
| 60 64 | 
             
                    end
         | 
| 61 65 |  | 
| 62 66 | 
             
                    def api
         | 
| @@ -6,18 +6,16 @@ module Abt | |
| 6 6 | 
             
                  module Commands
         | 
| 7 7 | 
             
                    class Boards < BaseCommand
         | 
| 8 8 | 
             
                      def self.usage
         | 
| 9 | 
            -
                         | 
| 9 | 
            +
                        "abt boards devops"
         | 
| 10 10 | 
             
                      end
         | 
| 11 11 |  | 
| 12 12 | 
             
                      def self.description
         | 
| 13 | 
            -
                         | 
| 13 | 
            +
                        "List all boards - useful for piping into grep etc"
         | 
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def perform
         | 
| 17 | 
            -
                        if organization_name.nil?
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                        end
         | 
| 20 | 
            -
                        abort 'No project selected. Did you initialize DevOps?' if project_name.nil?
         | 
| 17 | 
            +
                        abort("No organization selected. Did you initialize DevOps?") if organization_name.nil?
         | 
| 18 | 
            +
                        abort("No project selected. Did you initialize DevOps?") if project_name.nil?
         | 
| 21 19 |  | 
| 22 20 | 
             
                        boards.map do |board|
         | 
| 23 21 | 
             
                          print_board(organization_name, project_name, board)
         | 
| @@ -27,7 +25,7 @@ module Abt | |
| 27 25 | 
             
                      private
         | 
| 28 26 |  | 
| 29 27 | 
             
                      def boards
         | 
| 30 | 
            -
                        @boards ||= api.get_paged( | 
| 28 | 
            +
                        @boards ||= api.get_paged("work/boards")
         | 
| 31 29 | 
             
                      end
         | 
| 32 30 | 
             
                    end
         | 
| 33 31 | 
             
                  end
         | 
| @@ -6,11 +6,11 @@ module Abt | |
| 6 6 | 
             
                  module Commands
         | 
| 7 7 | 
             
                    class BranchName < BaseCommand
         | 
| 8 8 | 
             
                      def self.usage
         | 
| 9 | 
            -
                         | 
| 9 | 
            +
                        "abt branch-name devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]"
         | 
| 10 10 | 
             
                      end
         | 
| 11 11 |  | 
| 12 12 | 
             
                      def self.description
         | 
| 13 | 
            -
                         | 
| 13 | 
            +
                        "Suggest a git branch name for the current/specified work-item."
         | 
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def perform
         | 
| @@ -21,24 +21,24 @@ module Abt | |
| 21 21 | 
             
                        args = [organization_name, project_name, board_id, work_item_id].compact
         | 
| 22 22 |  | 
| 23 23 | 
             
                        error_message = [
         | 
| 24 | 
            -
                           | 
| 24 | 
            +
                          "Unable to find work item for configuration:",
         | 
| 25 25 | 
             
                          "devops:#{args.join('/')}"
         | 
| 26 26 | 
             
                        ].join("\n")
         | 
| 27 | 
            -
                        abort | 
| 27 | 
            +
                        abort(error_message)
         | 
| 28 28 | 
             
                      end
         | 
| 29 29 |  | 
| 30 30 | 
             
                      private
         | 
| 31 31 |  | 
| 32 32 | 
             
                      def name
         | 
| 33 | 
            -
                        str = work_item[ | 
| 34 | 
            -
                        str +=  | 
| 35 | 
            -
                        str += work_item[ | 
| 36 | 
            -
                        str.gsub( | 
| 33 | 
            +
                        str = work_item["id"]
         | 
| 34 | 
            +
                        str += "-"
         | 
| 35 | 
            +
                        str += work_item["name"].downcase.gsub(/[^\w]/, "-")
         | 
| 36 | 
            +
                        str.squeeze("-").gsub(/(^-|-$)/, "")
         | 
| 37 37 | 
             
                      end
         | 
| 38 38 |  | 
| 39 39 | 
             
                      def work_item
         | 
| 40 40 | 
             
                        @work_item ||= begin
         | 
| 41 | 
            -
                          work_item = api.get_paged( | 
| 41 | 
            +
                          work_item = api.get_paged("wit/workitems", ids: work_item_id)[0]
         | 
| 42 42 | 
             
                          sanitize_work_item(work_item)
         | 
| 43 43 | 
             
                        end
         | 
| 44 44 | 
             
                      end
         | 
| @@ -6,29 +6,28 @@ module Abt | |
| 6 6 | 
             
                  module Commands
         | 
| 7 7 | 
             
                    class Clear < BaseCommand
         | 
| 8 8 | 
             
                      def self.usage
         | 
| 9 | 
            -
                         | 
| 9 | 
            +
                        "abt clear devops"
         | 
| 10 10 | 
             
                      end
         | 
| 11 11 |  | 
| 12 12 | 
             
                      def self.description
         | 
| 13 | 
            -
                         | 
| 13 | 
            +
                        "Clear DevOps configuration"
         | 
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def self.flags
         | 
| 17 17 | 
             
                        [
         | 
| 18 | 
            -
                          [ | 
| 19 | 
            -
             | 
| 18 | 
            +
                          ["-g", "--global",
         | 
| 19 | 
            +
                           "Clear global instead of local DevOp configuration (credentials etc.)"],
         | 
| 20 | 
            +
                          ["-a", "--all", "Clear all DevOp configuration"]
         | 
| 20 21 | 
             
                        ]
         | 
| 21 22 | 
             
                      end
         | 
| 22 23 |  | 
| 23 24 | 
             
                      def perform
         | 
| 24 | 
            -
                        if flags[:global] && flags[:all]
         | 
| 25 | 
            -
                          abort('Flags --global and --all cannot be used at the same time')
         | 
| 26 | 
            -
                        end
         | 
| 25 | 
            +
                        abort("Flags --global and --all cannot be used at the same time") if flags[:global] && flags[:all]
         | 
| 27 26 |  | 
| 28 27 | 
             
                        config.clear_local unless flags[:global]
         | 
| 29 28 | 
             
                        config.clear_global if flags[:global] || flags[:all]
         | 
| 30 29 |  | 
| 31 | 
            -
                        warn | 
| 30 | 
            +
                        warn("Configuration cleared")
         | 
| 32 31 | 
             
                      end
         | 
| 33 32 | 
             
                    end
         | 
| 34 33 | 
             
                  end
         | 
| @@ -6,22 +6,21 @@ module Abt | |
| 6 6 | 
             
                  module Commands
         | 
| 7 7 | 
             
                    class Current < BaseCommand
         | 
| 8 8 | 
             
                      def self.usage
         | 
| 9 | 
            -
                         | 
| 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 16 | 
             
                      def perform
         | 
| 17 | 
            -
                         | 
| 18 | 
            -
             | 
| 17 | 
            +
                        require_local_config!
         | 
| 19 18 | 
             
                        require_board!
         | 
| 20 19 | 
             
                        ensure_valid_configuration!
         | 
| 21 20 |  | 
| 22 21 | 
             
                        if path != config.path && config.local_available?
         | 
| 23 22 | 
             
                          config.path = path
         | 
| 24 | 
            -
                          warn | 
| 23 | 
            +
                          warn("Configuration updated")
         | 
| 25 24 | 
             
                        end
         | 
| 26 25 |  | 
| 27 26 | 
             
                        print_configuration
         | 
| @@ -39,28 +38,28 @@ module Abt | |
| 39 38 |  | 
| 40 39 | 
             
                      def ensure_valid_configuration!
         | 
| 41 40 | 
             
                        if board.nil?
         | 
| 42 | 
            -
                          abort | 
| 41 | 
            +
                          abort("Board could not be found, ensure that settings for organization, project, and board are correct")
         | 
| 43 42 | 
             
                        end
         | 
| 44 | 
            -
                        abort | 
| 43 | 
            +
                        abort("No such work item: ##{work_item_id}") if work_item_id && work_item.nil?
         | 
| 45 44 | 
             
                      end
         | 
| 46 45 |  | 
| 47 46 | 
             
                      def board
         | 
| 48 47 | 
             
                        @board ||= begin
         | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 48 | 
            +
                          warn("Fetching board...")
         | 
| 49 | 
            +
                          api.get("work/boards/#{board_id}")
         | 
| 50 | 
            +
                        rescue HttpError::NotFoundError
         | 
| 51 | 
            +
                          nil
         | 
| 52 | 
            +
                        end
         | 
| 54 53 | 
             
                      end
         | 
| 55 54 |  | 
| 56 55 | 
             
                      def work_item
         | 
| 57 56 | 
             
                        @work_item ||= begin
         | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 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
         | 
| 64 63 | 
             
                      end
         | 
| 65 64 | 
             
                    end
         | 
| 66 65 | 
             
                  end
         | 
| @@ -6,51 +6,53 @@ module Abt | |
| 6 6 | 
             
                  module Commands
         | 
| 7 7 | 
             
                    class HarvestTimeEntryData < BaseCommand
         | 
| 8 8 | 
             
                      def self.usage
         | 
| 9 | 
            -
                         | 
| 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 16 | 
             
                      def perform
         | 
| 17 17 | 
             
                        require_work_item!
         | 
| 18 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 19 | 
             
                        puts Oj.dump(body, mode: :json)
         | 
| 29 20 | 
             
                      rescue HttpError::NotFoundError
         | 
| 30 21 | 
             
                        args = [organization_name, project_name, board_id, work_item_id].compact
         | 
| 31 22 |  | 
| 32 23 | 
             
                        error_message = [
         | 
| 33 | 
            -
                           | 
| 24 | 
            +
                          "Unable to find work item for configuration:",
         | 
| 34 25 | 
             
                          "devops:#{args.join('/')}"
         | 
| 35 26 | 
             
                        ].join("\n")
         | 
| 36 | 
            -
                        abort | 
| 27 | 
            +
                        abort(error_message)
         | 
| 37 28 | 
             
                      end
         | 
| 38 29 |  | 
| 39 30 | 
             
                      private
         | 
| 40 31 |  | 
| 32 | 
            +
                      def body
         | 
| 33 | 
            +
                        {
         | 
| 34 | 
            +
                          notes: notes,
         | 
| 35 | 
            +
                          external_reference: {
         | 
| 36 | 
            +
                            id: work_item["id"],
         | 
| 37 | 
            +
                            group_id: "AzureDevOpsWorkItem",
         | 
| 38 | 
            +
                            permalink: work_item["url"]
         | 
| 39 | 
            +
                          }
         | 
| 40 | 
            +
                        }
         | 
| 41 | 
            +
                      end
         | 
| 42 | 
            +
             | 
| 41 43 | 
             
                      def notes
         | 
| 42 44 | 
             
                        [
         | 
| 43 | 
            -
                           | 
| 44 | 
            -
                          work_item[ | 
| 45 | 
            +
                          "Azure DevOps",
         | 
| 46 | 
            +
                          work_item["fields"]["System.WorkItemType"],
         | 
| 45 47 | 
             
                          "##{work_item['id']}",
         | 
| 46 | 
            -
                           | 
| 47 | 
            -
                          work_item[ | 
| 48 | 
            -
                        ].join( | 
| 48 | 
            +
                          "-",
         | 
| 49 | 
            +
                          work_item["name"]
         | 
| 50 | 
            +
                        ].join(" ")
         | 
| 49 51 | 
             
                      end
         | 
| 50 52 |  | 
| 51 53 | 
             
                      def work_item
         | 
| 52 54 | 
             
                        @work_item ||= begin
         | 
| 53 | 
            -
                          work_item = api.get_paged( | 
| 55 | 
            +
                          work_item = api.get_paged("wit/workitems", ids: work_item_id)[0]
         | 
| 54 56 | 
             
                          sanitize_work_item(work_item)
         | 
| 55 57 | 
             
                        end
         | 
| 56 58 | 
             
                      end
         | 
| @@ -9,26 +9,29 @@ module Abt | |
| 9 9 | 
             
                      VS_URL_REGEX = %r{^https://(?<organization>[^.]+)\.visualstudio\.com/(?<project>[^/]+)}.freeze
         | 
| 10 10 |  | 
| 11 11 | 
             
                      def self.usage
         | 
| 12 | 
            -
                         | 
| 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 19 | 
             
                      def perform
         | 
| 20 | 
            -
                         | 
| 20 | 
            +
                        require_local_config!
         | 
| 21 | 
            +
                        board = cli.prompt.choice("Select a project work board", boards)
         | 
| 21 22 |  | 
| 22 | 
            -
                         | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 23 | 
            +
                        config.path = Path.from_ids(
         | 
| 24 | 
            +
                          organization_name: organization_name,
         | 
| 25 | 
            +
                          project_name: project_name,
         | 
| 26 | 
            +
                          board_id: board["id"]
         | 
| 27 | 
            +
                        )
         | 
| 25 28 | 
             
                        print_board(organization_name, project_name, board)
         | 
| 26 29 | 
             
                      end
         | 
| 27 30 |  | 
| 28 31 | 
             
                      private
         | 
| 29 32 |  | 
| 30 33 | 
             
                      def boards
         | 
| 31 | 
            -
                        @boards ||= api.get_paged( | 
| 34 | 
            +
                        @boards ||= api.get_paged("work/boards")
         | 
| 32 35 | 
             
                      end
         | 
| 33 36 |  | 
| 34 37 | 
             
                      def project_name
         | 
| @@ -52,19 +55,23 @@ module Abt | |
| 52 55 | 
             
                      def project_url
         | 
| 53 56 | 
             
                        @project_url ||= begin
         | 
| 54 57 | 
             
                          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"))
         | 
| 58 | 
            +
                            url = cli.prompt.text(project_url_prompt_text)
         | 
| 61 59 |  | 
| 62 60 | 
             
                            break url if AZURE_DEV_URL_REGEX =~ url || VS_URL_REGEX =~ url
         | 
| 63 61 |  | 
| 64 | 
            -
                            warn | 
| 62 | 
            +
                            warn("Invalid URL")
         | 
| 65 63 | 
             
                          end
         | 
| 66 64 | 
             
                        end
         | 
| 67 65 | 
             
                      end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                      def project_url_prompt_text
         | 
| 68 | 
            +
                        <<~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
         | 
| 68 75 | 
             
                    end
         | 
| 69 76 | 
             
                  end
         | 
| 70 77 | 
             
                end
         |