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,8 +4,8 @@ module Abt | |
| 4 4 | 
             
              module Providers
         | 
| 5 5 | 
             
                module Asana
         | 
| 6 6 | 
             
                  class Api
         | 
| 7 | 
            -
                    API_ENDPOINT =  | 
| 8 | 
            -
                    VERBS =  | 
| 7 | 
            +
                    API_ENDPOINT = "https://app.asana.com/api/1.0"
         | 
| 8 | 
            +
                    VERBS = [:get, :post, :put].freeze
         | 
| 9 9 |  | 
| 10 10 | 
             
                    attr_reader :access_token
         | 
| 11 11 |  | 
| @@ -15,7 +15,7 @@ module Abt | |
| 15 15 |  | 
| 16 16 | 
             
                    VERBS.each do |verb|
         | 
| 17 17 | 
             
                      define_method(verb) do |*args|
         | 
| 18 | 
            -
                        request(verb, *args)[ | 
| 18 | 
            +
                        request(verb, *args)["data"]
         | 
| 19 19 | 
             
                      end
         | 
| 20 20 | 
             
                    end
         | 
| 21 21 |  | 
| @@ -24,10 +24,10 @@ module Abt | |
| 24 24 |  | 
| 25 25 | 
             
                      loop do
         | 
| 26 26 | 
             
                        result = request(:get, path, query.merge(limit: 100))
         | 
| 27 | 
            -
                        records += result[ | 
| 28 | 
            -
                        break if result[ | 
| 27 | 
            +
                        records += result["data"]
         | 
| 28 | 
            +
                        break if result["next_page"].nil?
         | 
| 29 29 |  | 
| 30 | 
            -
                        path = result[ | 
| 30 | 
            +
                        path = result["next_page"]["path"][1..-1]
         | 
| 31 31 | 
             
                      end
         | 
| 32 32 |  | 
| 33 33 | 
             
                      records
         | 
| @@ -40,15 +40,15 @@ module Abt | |
| 40 40 | 
             
                        Oj.load(response.body)
         | 
| 41 41 | 
             
                      else
         | 
| 42 42 | 
             
                        error_class = Abt::HttpError.error_class_for_status(response.status)
         | 
| 43 | 
            -
                        encoded_response_body = response.body.force_encoding( | 
| 43 | 
            +
                        encoded_response_body = response.body.force_encoding("utf-8")
         | 
| 44 44 | 
             
                        raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
         | 
| 45 45 | 
             
                      end
         | 
| 46 46 | 
             
                    end
         | 
| 47 47 |  | 
| 48 48 | 
             
                    def connection
         | 
| 49 49 | 
             
                      @connection ||= Faraday.new(API_ENDPOINT) do |connection|
         | 
| 50 | 
            -
                        connection.headers[ | 
| 51 | 
            -
                        connection.headers[ | 
| 50 | 
            +
                        connection.headers["Authorization"] = "Bearer #{access_token}"
         | 
| 51 | 
            +
                        connection.headers["Content-Type"] = "application/json"
         | 
| 52 52 | 
             
                      end
         | 
| 53 53 | 
             
                    end
         | 
| 54 54 | 
             
                  end
         | 
| @@ -20,26 +20,28 @@ module Abt | |
| 20 20 |  | 
| 21 21 | 
             
                    private
         | 
| 22 22 |  | 
| 23 | 
            +
                    def require_local_config!
         | 
| 24 | 
            +
                      abort("Must be run inside a git repository") unless config.local_available?
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 23 27 | 
             
                    def require_project!
         | 
| 24 | 
            -
                      abort | 
| 28 | 
            +
                      abort("No current/specified project. Did you initialize Asana?") if project_gid.nil?
         | 
| 25 29 | 
             
                    end
         | 
| 26 30 |  | 
| 27 31 | 
             
                    def require_task!
         | 
| 28 | 
            -
                      if project_gid.nil?
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                      end
         | 
| 31 | 
            -
                      abort 'No current/specified task. Did you pick an Asana task?' if task_gid.nil?
         | 
| 32 | 
            +
                      abort("No current/specified project. Did you initialize Asana and pick a task?") if project_gid.nil?
         | 
| 33 | 
            +
                      abort("No current/specified task. Did you pick an Asana task?") if task_gid.nil?
         | 
| 32 34 | 
             
                    end
         | 
| 33 35 |  | 
| 34 36 | 
             
                    def print_project(project)
         | 
| 35 | 
            -
                      cli.print_ari( | 
| 36 | 
            -
                      warn | 
| 37 | 
            +
                      cli.print_ari("asana", project["gid"], project["name"])
         | 
| 38 | 
            +
                      warn(project["permalink_url"]) if project.key?("permalink_url") && cli.output.isatty
         | 
| 37 39 | 
             
                    end
         | 
| 38 40 |  | 
| 39 41 | 
             
                    def print_task(project, task)
         | 
| 40 | 
            -
                      project = {  | 
| 41 | 
            -
                      cli.print_ari( | 
| 42 | 
            -
                      warn | 
| 42 | 
            +
                      project = { "gid" => project } if project.is_a?(String)
         | 
| 43 | 
            +
                      cli.print_ari("asana", "#{project['gid']}/#{task['gid']}", task["name"])
         | 
| 44 | 
            +
                      warn(task["permalink_url"]) if task.key?("permalink_url") && cli.output.isatty
         | 
| 43 45 | 
             
                    end
         | 
| 44 46 |  | 
| 45 47 | 
             
                    def api
         | 
| @@ -6,22 +6,22 @@ module Abt | |
| 6 6 | 
             
                  module Commands
         | 
| 7 7 | 
             
                    class Add < BaseCommand
         | 
| 8 8 | 
             
                      def self.usage
         | 
| 9 | 
            -
                         | 
| 9 | 
            +
                        "abt add asana[:<project-gid>]"
         | 
| 10 10 | 
             
                      end
         | 
| 11 11 |  | 
| 12 12 | 
             
                      def self.description
         | 
| 13 | 
            -
                         | 
| 13 | 
            +
                        "Create a new task for the current/specified Asana project"
         | 
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def perform
         | 
| 17 17 | 
             
                        require_project!
         | 
| 18 18 |  | 
| 19 19 | 
             
                        task
         | 
| 20 | 
            -
                        warn | 
| 20 | 
            +
                        warn("Task created")
         | 
| 21 21 |  | 
| 22 22 | 
             
                        if section
         | 
| 23 23 | 
             
                          move_task
         | 
| 24 | 
            -
                          warn | 
| 24 | 
            +
                          warn("Moved to section: #{section['name']}")
         | 
| 25 25 | 
             
                        end
         | 
| 26 26 |  | 
| 27 27 | 
             
                        print_task(project, task)
         | 
| @@ -38,36 +38,37 @@ module Abt | |
| 38 38 | 
             
                              projects: [project_gid]
         | 
| 39 39 | 
             
                            }
         | 
| 40 40 | 
             
                          }
         | 
| 41 | 
            -
                          api.post( | 
| 41 | 
            +
                          api.post("tasks", Oj.dump(body, mode: :json))
         | 
| 42 42 | 
             
                        end
         | 
| 43 43 | 
             
                      end
         | 
| 44 44 |  | 
| 45 45 | 
             
                      def move_task
         | 
| 46 | 
            -
                        body = { data: { task: task[ | 
| 46 | 
            +
                        body = { data: { task: task["gid"] } }
         | 
| 47 47 | 
             
                        body_json = Oj.dump(body, mode: :json)
         | 
| 48 48 | 
             
                        api.post("sections/#{section['gid']}/addTask", body_json)
         | 
| 49 49 | 
             
                      end
         | 
| 50 50 |  | 
| 51 51 | 
             
                      def name
         | 
| 52 | 
            -
                        @name ||= cli.prompt.text | 
| 52 | 
            +
                        @name ||= cli.prompt.text("Enter task description")
         | 
| 53 53 | 
             
                      end
         | 
| 54 54 |  | 
| 55 55 | 
             
                      def notes
         | 
| 56 | 
            -
                        @notes ||= cli.prompt.text | 
| 56 | 
            +
                        @notes ||= cli.prompt.text("Enter task notes")
         | 
| 57 57 | 
             
                      end
         | 
| 58 58 |  | 
| 59 59 | 
             
                      def project
         | 
| 60 | 
            -
                        @project ||= api.get("projects/#{project_gid}", opt_fields:  | 
| 60 | 
            +
                        @project ||= api.get("projects/#{project_gid}", opt_fields: "name")
         | 
| 61 61 | 
             
                      end
         | 
| 62 62 |  | 
| 63 63 | 
             
                      def section
         | 
| 64 | 
            -
                        @section ||= cli.prompt.choice | 
| 64 | 
            +
                        @section ||= cli.prompt.choice("Add to section?", sections,
         | 
| 65 | 
            +
                                                       nil_option: ["q", "Don't add to section"])
         | 
| 65 66 | 
             
                      end
         | 
| 66 67 |  | 
| 67 68 | 
             
                      def sections
         | 
| 68 69 | 
             
                        @sections ||= begin
         | 
| 69 | 
            -
                          warn | 
| 70 | 
            -
                          api.get_paged("projects/#{project_gid}/sections", opt_fields:  | 
| 70 | 
            +
                          warn("Fetching sections...")
         | 
| 71 | 
            +
                          api.get_paged("projects/#{project_gid}/sections", opt_fields: "name")
         | 
| 71 72 | 
             
                        end
         | 
| 72 73 | 
             
                      end
         | 
| 73 74 | 
             
                    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 asana[:<project-gid>/<task-gid>]"
         | 
| 10 10 | 
             
                      end
         | 
| 11 11 |  | 
| 12 12 | 
             
                      def self.description
         | 
| 13 | 
            -
                         | 
| 13 | 
            +
                        "Suggest a git branch name for the current/specified task."
         | 
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def perform
         | 
| @@ -23,21 +23,21 @@ module Abt | |
| 23 23 | 
             
                      private
         | 
| 24 24 |  | 
| 25 25 | 
             
                      def name
         | 
| 26 | 
            -
                        task[ | 
| 26 | 
            +
                        task["name"].downcase.gsub(/[^\w]+/, "-").gsub(/(^-|-$)/, "")
         | 
| 27 27 | 
             
                      end
         | 
| 28 28 |  | 
| 29 29 | 
             
                      def ensure_current_is_valid!
         | 
| 30 | 
            -
                        abort | 
| 30 | 
            +
                        abort("Invalid task gid: #{task_gid}") if task.nil?
         | 
| 31 31 |  | 
| 32 | 
            -
                        return if task[ | 
| 32 | 
            +
                        return if task["memberships"].any? { |m| m.dig("project", "gid") == project_gid }
         | 
| 33 33 |  | 
| 34 | 
            -
                        abort | 
| 34 | 
            +
                        abort("Invalid or unmatching project gid: #{project_gid}")
         | 
| 35 35 | 
             
                      end
         | 
| 36 36 |  | 
| 37 37 | 
             
                      def task
         | 
| 38 38 | 
             
                        @task ||= begin
         | 
| 39 | 
            -
                          warn | 
| 40 | 
            -
                          api.get("tasks/#{task_gid}", opt_fields:  | 
| 39 | 
            +
                          warn("Fetching task...")
         | 
| 40 | 
            +
                          api.get("tasks/#{task_gid}", opt_fields: "name,memberships.project")
         | 
| 41 41 | 
             
                        rescue Abt::HttpError::NotFoundError
         | 
| 42 42 | 
             
                          nil
         | 
| 43 43 | 
             
                        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 asana"
         | 
| 10 10 | 
             
                      end
         | 
| 11 11 |  | 
| 12 12 | 
             
                      def self.description
         | 
| 13 | 
            -
                         | 
| 13 | 
            +
                        "Clear asana configuration"
         | 
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def self.flags
         | 
| 17 17 | 
             
                        [
         | 
| 18 | 
            -
                          [ | 
| 19 | 
            -
             | 
| 18 | 
            +
                          ["-g", "--global",
         | 
| 19 | 
            +
                           "Clear global instead of local asana configuration (credentials etc.)"],
         | 
| 20 | 
            +
                          ["-a", "--all", "Clear all asana 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 asana[:<project-gid>[/<task-gid>]]"
         | 
| 10 10 | 
             
                      end
         | 
| 11 11 |  | 
| 12 12 | 
             
                      def self.description
         | 
| 13 | 
            -
                         | 
| 13 | 
            +
                        "Get or set project and or task for current git repository"
         | 
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def perform
         | 
| 17 | 
            -
                         | 
| 18 | 
            -
             | 
| 17 | 
            +
                        require_local_config!
         | 
| 19 18 | 
             
                        require_project!
         | 
| 20 19 | 
             
                        ensure_valid_configuration!
         | 
| 21 20 |  | 
| 22 21 | 
             
                        if path != config.path
         | 
| 23 22 | 
             
                          config.path = path
         | 
| 24 | 
            -
                          warn | 
| 23 | 
            +
                          warn("Configuration updated")
         | 
| 25 24 | 
             
                        end
         | 
| 26 25 |  | 
| 27 26 | 
             
                        print_configuration
         | 
| @@ -34,25 +33,25 @@ module Abt | |
| 34 33 | 
             
                      end
         | 
| 35 34 |  | 
| 36 35 | 
             
                      def ensure_valid_configuration!
         | 
| 37 | 
            -
                        abort | 
| 38 | 
            -
                        abort | 
| 36 | 
            +
                        abort("Invalid project: #{project_gid}") if project.nil?
         | 
| 37 | 
            +
                        abort("Invalid task: #{task_gid}") if task_gid && task.nil?
         | 
| 39 38 | 
             
                      end
         | 
| 40 39 |  | 
| 41 40 | 
             
                      def project
         | 
| 42 41 | 
             
                        @project ||= begin
         | 
| 43 | 
            -
                          warn | 
| 44 | 
            -
                          api.get("projects/#{project_gid}", opt_fields:  | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 42 | 
            +
                          warn("Fetching project...")
         | 
| 43 | 
            +
                          api.get("projects/#{project_gid}", opt_fields: "name,permalink_url")
         | 
| 44 | 
            +
                        rescue Abt::HttpError::NotFoundError
         | 
| 45 | 
            +
                          nil
         | 
| 47 46 | 
             
                        end
         | 
| 48 47 | 
             
                      end
         | 
| 49 48 |  | 
| 50 49 | 
             
                      def task
         | 
| 51 50 | 
             
                        @task ||= begin
         | 
| 52 | 
            -
                          warn | 
| 53 | 
            -
                          api.get("tasks/#{task_gid}", opt_fields:  | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 51 | 
            +
                          warn("Fetching task...")
         | 
| 52 | 
            +
                          api.get("tasks/#{task_gid}", opt_fields: "name,permalink_url")
         | 
| 53 | 
            +
                        rescue Abt::HttpError::NotFoundError
         | 
| 54 | 
            +
                          nil
         | 
| 56 55 | 
             
                        end
         | 
| 57 56 | 
             
                      end
         | 
| 58 57 | 
             
                    end
         | 
| @@ -6,47 +6,49 @@ module Abt | |
| 6 6 | 
             
                  module Commands
         | 
| 7 7 | 
             
                    class Finalize < BaseCommand
         | 
| 8 8 | 
             
                      def self.usage
         | 
| 9 | 
            -
                         | 
| 9 | 
            +
                        "abt finalize asana[:<project-gid>/<task-gid>]"
         | 
| 10 10 | 
             
                      end
         | 
| 11 11 |  | 
| 12 12 | 
             
                      def self.description
         | 
| 13 | 
            -
                         | 
| 13 | 
            +
                        "Move current/specified task to section (column) for finalized tasks"
         | 
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def perform
         | 
| 17 | 
            -
                        unless config. | 
| 18 | 
            -
                          abort 'This is a no-op for tasks outside the current project'
         | 
| 19 | 
            -
                        end
         | 
| 17 | 
            +
                        abort("This is a no-op for tasks outside the current project") unless project_gid == config.path.project_gid
         | 
| 20 18 | 
             
                        require_task!
         | 
| 21 19 | 
             
                        print_task(project_gid, task)
         | 
| 22 20 |  | 
| 21 | 
            +
                        maybe_move_task
         | 
| 22 | 
            +
                      end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                      private
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      def maybe_move_task
         | 
| 23 27 | 
             
                        if task_already_in_finalized_section?
         | 
| 24 | 
            -
                          warn | 
| 28 | 
            +
                          warn("Task already in section: #{current_task_section['name']}")
         | 
| 25 29 | 
             
                        else
         | 
| 26 | 
            -
                          warn | 
| 30 | 
            +
                          warn("Moving task to section: #{finalized_section['name']}")
         | 
| 27 31 | 
             
                          move_task
         | 
| 28 32 | 
             
                        end
         | 
| 29 33 | 
             
                      end
         | 
| 30 34 |  | 
| 31 | 
            -
                      private
         | 
| 32 | 
            -
             | 
| 33 35 | 
             
                      def task_already_in_finalized_section?
         | 
| 34 36 | 
             
                        !task_section_membership.nil?
         | 
| 35 37 | 
             
                      end
         | 
| 36 38 |  | 
| 37 39 | 
             
                      def current_task_section
         | 
| 38 | 
            -
                        task_section_membership&.dig( | 
| 40 | 
            +
                        task_section_membership&.dig("section")
         | 
| 39 41 | 
             
                      end
         | 
| 40 42 |  | 
| 41 43 | 
             
                      def task_section_membership
         | 
| 42 | 
            -
                        task[ | 
| 43 | 
            -
                          membership.dig( | 
| 44 | 
            +
                        task["memberships"].find do |membership|
         | 
| 45 | 
            +
                          membership.dig("section", "gid") == config.finalized_section_gid
         | 
| 44 46 | 
             
                        end
         | 
| 45 47 | 
             
                      end
         | 
| 46 48 |  | 
| 47 49 | 
             
                      def finalized_section
         | 
| 48 50 | 
             
                        @finalized_section ||= api.get("sections/#{config.finalized_section_gid}",
         | 
| 49 | 
            -
                                                       opt_fields:  | 
| 51 | 
            +
                                                       opt_fields: "name")
         | 
| 50 52 | 
             
                      end
         | 
| 51 53 |  | 
| 52 54 | 
             
                      def move_task
         | 
| @@ -57,11 +59,8 @@ module Abt | |
| 57 59 |  | 
| 58 60 | 
             
                      def task
         | 
| 59 61 | 
             
                        @task ||= begin
         | 
| 60 | 
            -
                           | 
| 61 | 
            -
             | 
| 62 | 
            -
                          else
         | 
| 63 | 
            -
                            api.get("tasks/#{task_gid}", opt_fields: 'name,memberships.section.name,permalink_url')
         | 
| 64 | 
            -
                          end
         | 
| 62 | 
            +
                          api.get("tasks/#{task_gid}",
         | 
| 63 | 
            +
                                  opt_fields: "name,memberships.section.name,permalink_url")
         | 
| 65 64 | 
             
                        end
         | 
| 66 65 | 
             
                      end
         | 
| 67 66 | 
             
                    end
         | 
| @@ -6,45 +6,47 @@ module Abt | |
| 6 6 | 
             
                  module Commands
         | 
| 7 7 | 
             
                    class HarvestTimeEntryData < BaseCommand
         | 
| 8 8 | 
             
                      def self.usage
         | 
| 9 | 
            -
                         | 
| 9 | 
            +
                        "abt harvest-time-entry-data asana[:<project-gid>/<task-gid>]"
         | 
| 10 10 | 
             
                      end
         | 
| 11 11 |  | 
| 12 12 | 
             
                      def self.description
         | 
| 13 | 
            -
                         | 
| 13 | 
            +
                        "Print Harvest time entry data for Asana task as json. Used by harvest start script."
         | 
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def perform
         | 
| 17 17 | 
             
                        require_task!
         | 
| 18 18 | 
             
                        ensure_current_is_valid!
         | 
| 19 19 |  | 
| 20 | 
            -
                        body  | 
| 21 | 
            -
             | 
| 20 | 
            +
                        puts Oj.dump(body, mode: :json)
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      private
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      def body
         | 
| 26 | 
            +
                        {
         | 
| 27 | 
            +
                          notes: task["name"],
         | 
| 22 28 | 
             
                          external_reference: {
         | 
| 23 29 | 
             
                            id: task_gid.to_i,
         | 
| 24 30 | 
             
                            group_id: project_gid.to_i,
         | 
| 25 | 
            -
                            permalink: task[ | 
| 31 | 
            +
                            permalink: task["permalink_url"]
         | 
| 26 32 | 
             
                          }
         | 
| 27 33 | 
             
                        }
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                        puts Oj.dump(body, mode: :json)
         | 
| 30 34 | 
             
                      end
         | 
| 31 35 |  | 
| 32 | 
            -
                      private
         | 
| 33 | 
            -
             | 
| 34 36 | 
             
                      def ensure_current_is_valid!
         | 
| 35 | 
            -
                        abort | 
| 37 | 
            +
                        abort("Invalid task gid: #{task_gid}") if task.nil?
         | 
| 36 38 |  | 
| 37 | 
            -
                        return if task[ | 
| 39 | 
            +
                        return if task["memberships"].any? { |m| m.dig("project", "gid") == project_gid }
         | 
| 38 40 |  | 
| 39 | 
            -
                        abort | 
| 41 | 
            +
                        abort("Invalid or unmatching project gid: #{project_gid}")
         | 
| 40 42 | 
             
                      end
         | 
| 41 43 |  | 
| 42 44 | 
             
                      def task
         | 
| 43 45 | 
             
                        @task ||= begin
         | 
| 44 | 
            -
                          warn | 
| 45 | 
            -
                          api.get("tasks/#{task_gid}", opt_fields:  | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 46 | 
            +
                          warn("Fetching task...")
         | 
| 47 | 
            +
                          api.get("tasks/#{task_gid}", opt_fields: "name,permalink_url,memberships.project")
         | 
| 48 | 
            +
                        rescue Abt::HttpError::NotFoundError
         | 
| 49 | 
            +
                          nil
         | 
| 48 50 | 
             
                        end
         | 
| 49 51 | 
             
                      end
         | 
| 50 52 | 
             
                    end
         |