abt-cli 0.0.22 → 0.0.23

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b8172eca54c77feb083acbc9895bf9f221b69efc1c0b8170d87f250b486286a
4
- data.tar.gz: e07e6122c3b3c3bc1307769f5d52d01cb6db9126df0fe10bfb217b940a1eaf12
3
+ metadata.gz: 0a7665ad954a2011f10df120e0dd6ca13a0ab4ef9e0aea0338be4ec2e6cc62a3
4
+ data.tar.gz: 7c50793f7c8cf9d3d4fb88090dec2803691a4acfc931d39135aed85d0226b00f
5
5
  SHA512:
6
- metadata.gz: 5eee8193b985110d79170134593fa1ac03b50f6be36b43fa99bb99c8bdcf51d531f9e2b10fe5bca919524f388f503a3a46b9d74bf4764226e6f8d94b905d12ca
7
- data.tar.gz: 3a3d7f288dbf3faaab041934bcf703afbaa87b989a1e869bae02a084c83834fd02a1a93df78ed06b2bb6a0b29f4167cdae8a972940c6684e9660dd58d06a78ba
6
+ metadata.gz: 1db215088d055c4857f37ac01295afa0551d9ed88b11917971ff64ff8b8761db833422b9a52f83751e3b6cf476813fff205160d04443d7a8d7b0af21ce4d755c
7
+ data.tar.gz: 1e89308cc2a26401cfb5e06fd37dc2a80aef260a5bad6c0da0473e61d1469b92457bf14299dc823eca526326c3239ef1b060f0fd747d206ca55eb25a9ad6cf52
data/bin/abt CHANGED
@@ -6,7 +6,7 @@ require_relative "../lib/abt"
6
6
  begin
7
7
  Abt::Cli.new.perform
8
8
  rescue Abt::Cli::Abort => e
9
- abort(e.message)
9
+ abort(e.message.strip)
10
10
  rescue Interrupt
11
11
  abort("Aborted")
12
12
  end
@@ -11,7 +11,7 @@ module Abt
11
11
 
12
12
  def text(question)
13
13
  output.print("#{question.strip}: ")
14
- read_user_input
14
+ Abt::Helpers.read_user_input
15
15
  end
16
16
 
17
17
  def boolean(text)
@@ -20,12 +20,10 @@ module Abt
20
20
  loop do
21
21
  output.print("(y / n): ")
22
22
 
23
- case read_user_input
23
+ case Abt::Helpers.read_user_input
24
24
  when "y", "Y" then return true
25
25
  when "n", "N" then return false
26
- else
27
- output.puts "Invalid choice"
28
- end
26
+ else output.puts "Invalid choice" end
29
27
  end
30
28
  end
31
29
 
@@ -40,7 +38,7 @@ module Abt
40
38
  end
41
39
 
42
40
  print_options(options)
43
- select_options(options, nil_option)
41
+ select_option(options, nil_option)
44
42
  end
45
43
 
46
44
  def search(text, options)
@@ -60,40 +58,37 @@ module Abt
60
58
  end
61
59
  end
62
60
 
63
- def select_options(options, nil_option)
64
- loop do
65
- number = read_option_number(options.length, nil_option)
66
- if number.nil?
67
- return nil if nil_option
68
-
69
- next
70
- end
61
+ def select_option(options, nil_option)
62
+ number = prompt_valid_option_number(options, nil_option)
71
63
 
72
- option = options[number - 1]
64
+ return nil if number.nil?
73
65
 
74
- output.puts "Selected: (#{number}) #{option['name']}"
75
- return option
76
- end
66
+ option = options[number - 1]
67
+ output.puts "Selected: (#{number}) #{option['name']}"
68
+ option
77
69
  end
78
70
 
79
- def read_option_number(options_length, nil_option)
80
- str = "("
81
- str += options_length > 1 ? "1-#{options_length}" : "1"
82
- str += nil_option_string(nil_option)
83
- str += "): "
84
- output.print(str)
85
-
86
- input = read_user_input
71
+ def prompt_valid_option_number(options, nil_option)
72
+ output.print(options_info(options, nil_option))
73
+ input = Abt::Helpers.read_user_input
87
74
 
88
75
  return nil if nil_option && input == nil_option_character(nil_option)
89
76
 
90
77
  option_number = input.to_i
91
- if option_number <= 0 || option_number > options_length
92
- output.puts "Invalid selection"
93
- return nil
94
- end
78
+ return option_number if (1..options.length).cover?(option_number)
95
79
 
96
- option_number
80
+ output.puts "Invalid selection"
81
+
82
+ # Prompt again if the selection was invalid
83
+ prompt_valid_option_number(options, nil_option)
84
+ end
85
+
86
+ def options_info(options, nil_option)
87
+ str = "("
88
+ str += options.length > 1 ? "1-#{options.length}" : "1"
89
+ str += nil_option_string(nil_option)
90
+ str += "): "
91
+ str
97
92
  end
98
93
 
99
94
  def nil_option_string(nil_option)
@@ -115,10 +110,6 @@ module Abt
115
110
  nil_option[1]
116
111
  end
117
112
 
118
- def read_user_input
119
- open(tty_path, &:gets).strip # rubocop:disable Security/Open
120
- end
121
-
122
113
  def get_search_result(options)
123
114
  matches = matches_for_string(text("Enter search"), options)
124
115
  if matches.empty?
@@ -141,16 +132,6 @@ module Abt
141
132
  def sanitize_string(string)
142
133
  string.downcase.gsub(/[^\w]/, "")
143
134
  end
144
-
145
- def tty_path
146
- @tty_path ||= begin
147
- candidates = ["/dev/tty", "CON:"] # Unix: '/dev/tty', Windows: 'CON:'
148
- selected = candidates.find { |candidate| File.exist?(candidate) }
149
- raise Abort, "Unable to prompt for user input" if selected.nil?
150
-
151
- selected
152
- end
153
- end
154
135
  end
155
136
  end
156
137
  end
@@ -50,8 +50,7 @@ module Abt
50
50
  def example_commands
51
51
  lines = []
52
52
 
53
- examples = Docs.basic_examples.merge(Docs.extended_examples)
54
- examples.each_with_index do |(title, commands), index|
53
+ complete_examples.each_with_index do |(title, commands), index|
55
54
  lines << "" unless index.zero?
56
55
  lines << title
57
56
 
@@ -84,6 +83,10 @@ module Abt
84
83
  lines.join("\n")
85
84
  end
86
85
 
86
+ def complete_examples
87
+ Docs.basic_examples.merge(Docs.extended_examples)
88
+ end
89
+
87
90
  def inflector
88
91
  Dry::Inflector.new
89
92
  end
data/lib/abt/helpers.rb CHANGED
@@ -2,15 +2,33 @@
2
2
 
3
3
  module Abt
4
4
  module Helpers
5
- def self.const_to_command(string)
6
- string = string.to_s.dup
7
- string[0] = string[0].downcase
8
- string.gsub(/([A-Z])/, '-\1').downcase
9
- end
5
+ class << self
6
+ def const_to_command(string)
7
+ string = string.to_s.dup
8
+ string[0] = string[0].downcase
9
+ string.gsub(/([A-Z])/, '-\1').downcase
10
+ end
11
+
12
+ def command_to_const(string)
13
+ inflector = Dry::Inflector.new
14
+ inflector.camelize(inflector.underscore(string))
15
+ end
16
+
17
+ def read_user_input
18
+ open(tty_path, &:gets).strip # rubocop:disable Security/Open
19
+ end
20
+
21
+ private
22
+
23
+ def tty_path
24
+ @tty_path ||= begin
25
+ candidates = ["/dev/tty", "CON:"] # Unix: '/dev/tty', Windows: 'CON:'
26
+ selected = candidates.find { |candidate| File.exist?(candidate) }
27
+ raise Abort, "Unable to prompt for user input" if selected.nil?
10
28
 
11
- def self.command_to_const(string)
12
- inflector = Dry::Inflector.new
13
- inflector.camelize(inflector.underscore(string))
29
+ selected
30
+ end
31
+ end
14
32
  end
15
33
  end
16
34
  end
@@ -20,6 +20,10 @@ 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
28
  abort("No current/specified project. Did you initialize Asana?") if project_gid.nil?
25
29
  end
@@ -14,8 +14,7 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
- abort("Must be run inside a git repository") unless config.local_available?
18
-
17
+ require_local_config!
19
18
  require_project!
20
19
  ensure_valid_configuration!
21
20
 
@@ -18,6 +18,12 @@ module Abt
18
18
  require_task!
19
19
  print_task(project_gid, task)
20
20
 
21
+ maybe_move_task
22
+ end
23
+
24
+ private
25
+
26
+ def maybe_move_task
21
27
  if task_already_in_finalized_section?
22
28
  warn("Task already in section: #{current_task_section['name']}")
23
29
  else
@@ -26,8 +32,6 @@ module Abt
26
32
  end
27
33
  end
28
34
 
29
- private
30
-
31
35
  def task_already_in_finalized_section?
32
36
  !task_section_membership.nil?
33
37
  end
@@ -17,7 +17,13 @@ module Abt
17
17
  require_task!
18
18
  ensure_current_is_valid!
19
19
 
20
- body = {
20
+ puts Oj.dump(body, mode: :json)
21
+ end
22
+
23
+ private
24
+
25
+ def body
26
+ {
21
27
  notes: task["name"],
22
28
  external_reference: {
23
29
  id: task_gid.to_i,
@@ -25,12 +31,8 @@ module Abt
25
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
37
  abort("Invalid task gid: #{task_gid}") if task.nil?
36
38
 
@@ -14,12 +14,12 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
- abort("Must be run inside a git repository") unless config.local_available?
17
+ require_local_config!
18
18
 
19
19
  projects # Load projects up front to make it obvious that searches are instant
20
20
  project = cli.prompt.search("Select a project", projects)
21
21
 
22
- config.path = Path.from_ids(project["gid"])
22
+ config.path = Path.from_ids(project_gid: project["gid"])
23
23
 
24
24
  print_project(project)
25
25
  end
@@ -20,18 +20,17 @@ module Abt
20
20
  end
21
21
 
22
22
  def perform
23
- abort("Must be run inside a git repository") unless config.local_available?
23
+ require_local_config!
24
24
  require_project!
25
25
 
26
26
  warn(project["name"])
27
-
28
27
  task = select_task
29
28
 
30
29
  print_task(project, task)
31
30
 
32
31
  return if flags[:"dry-run"]
33
32
 
34
- config.path = Path.from_ids(project_gid, task["gid"])
33
+ config.path = Path.from_ids(project_gid: project_gid, task_gid: task["gid"])
35
34
  end
36
35
 
37
36
  private
@@ -41,18 +40,15 @@ module Abt
41
40
  end
42
41
 
43
42
  def select_task
44
- loop do
45
- section = cli.prompt.choice("Which section?", sections)
46
- warn("Fetching tasks...")
47
- tasks = tasks_in_section(section)
48
-
49
- if tasks.length.zero?
50
- warn("Section is empty")
51
- next
52
- end
53
-
54
- task = cli.prompt.choice("Select a task", tasks, nil_option: true)
55
- return task if task
43
+ section = cli.prompt.choice("Which section?", sections)
44
+ warn("Fetching tasks...")
45
+ tasks = tasks_in_section(section)
46
+
47
+ if tasks.length.zero?
48
+ warn("Section is empty")
49
+ select_task
50
+ else
51
+ cli.prompt.choice("Select a task", tasks, nil_option: true) || select_task
56
52
  end
57
53
  end
58
54
 
@@ -42,19 +42,25 @@ module Abt
42
42
  end
43
43
 
44
44
  def update_assignee_if_needed
45
- current_assignee = task["assignee"]
46
-
47
45
  if current_assignee.nil?
48
46
  warn("Assigning task to user: #{current_user['name']}")
49
47
  update_assignee
50
48
  elsif current_assignee["gid"] == current_user["gid"]
51
49
  warn("You are already assigned to this task")
52
- elsif cli.prompt.boolean("Task is assigned to: #{current_assignee['name']}, take over?")
50
+ elsif should_reassign?
53
51
  warn("Reassigning task to user: #{current_user['name']}")
54
52
  update_assignee
55
53
  end
56
54
  end
57
55
 
56
+ def current_assignee
57
+ task["assignee"]
58
+ end
59
+
60
+ def should_reassign?
61
+ cli.prompt.boolean("Task is assigned to: #{current_assignee['name']}, take over?")
62
+ end
63
+
58
64
  def move_if_needed
59
65
  unless project_gid == config.path.project_gid
60
66
  warn("Task was not moved, this is not implemented for tasks outside current project")
@@ -26,7 +26,7 @@ module Abt
26
26
  @workspace_gid ||= begin
27
27
  current = git_global["workspaceGid"]
28
28
  if current.nil?
29
- prompt_workspace["gid"]
29
+ prompt_workspace_gid
30
30
  else
31
31
  current
32
32
  end
@@ -92,20 +92,28 @@ module Abt
92
92
  cli.prompt.choice(message, sections)
93
93
  end
94
94
 
95
- def prompt_workspace
96
- cli.warn("Fetching workspaces...")
97
- workspaces = api.get_paged("workspaces", opt_fields: "name")
98
- if workspaces.empty?
99
- cli.abort("Your asana access token does not have access to any workspaces")
100
- elsif workspaces.one?
95
+ def prompt_workspace_gid
96
+ cli.abort("Your asana access token does not have access to any workspaces") if workspaces.empty?
97
+
98
+ if workspaces.one?
101
99
  workspace = workspaces.first
102
100
  cli.warn("Selected Asana workspace: #{workspace['name']}")
103
101
  else
104
- workspace = cli.prompt.choice("Select Asana workspace", workspaces)
102
+ workspace = pick_workspace
105
103
  end
106
104
 
107
105
  git_global["workspaceGid"] = workspace["gid"]
108
- workspace
106
+ end
107
+
108
+ def pick_workspace
109
+ cli.prompt.choice("Select Asana workspace", workspaces)
110
+ end
111
+
112
+ def workspaces
113
+ @workspaces ||= begin
114
+ cli.warn("Fetching workspaces...")
115
+ api.get_paged("workspaces", opt_fields: "name")
116
+ end
109
117
  end
110
118
 
111
119
  def api
@@ -6,7 +6,7 @@ module Abt
6
6
  class Path < String
7
7
  PATH_REGEX = %r{^(?<project_gid>\d+)?/?(?<task_gid>\d+)?$}.freeze
8
8
 
9
- def self.from_ids(project_gid = nil, task_gid = nil)
9
+ def self.from_ids(project_gid: nil, task_gid: nil)
10
10
  path = project_gid ? [project_gid, *task_gid].join("/") : ""
11
11
  new(path)
12
12
  end
@@ -19,6 +19,10 @@ 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
 
@@ -14,8 +14,7 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
- abort("Must be run inside a git repository") unless config.local_available?
18
-
17
+ require_local_config!
19
18
  require_board!
20
19
  ensure_valid_configuration!
21
20
 
@@ -16,15 +16,6 @@ module Abt
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
@@ -38,6 +29,17 @@ module Abt
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
45
  "Azure DevOps",
@@ -17,11 +17,14 @@ module Abt
17
17
  end
18
18
 
19
19
  def perform
20
- abort("Must be run inside a git repository") unless config.local_available?
21
-
20
+ require_local_config!
22
21
  board = cli.prompt.choice("Select a project work board", boards)
23
22
 
24
- config.path = Path.from_ids(organization_name, project_name, board["id"])
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
 
@@ -20,7 +20,7 @@ module Abt
20
20
  end
21
21
 
22
22
  def perform
23
- abort("Must be run inside a git repository") unless config.local_available?
23
+ require_local_config!
24
24
  require_board!
25
25
 
26
26
  warn("#{project_name} - #{board['name']}")
@@ -30,24 +30,30 @@ module Abt
30
30
 
31
31
  return if flags[:"dry-run"]
32
32
 
33
- config.path = Path.from_ids(organization_name, project_name, board_id, work_item["id"])
33
+ update_config(work_item)
34
34
  end
35
35
 
36
36
  private
37
37
 
38
+ def update_config(work_item)
39
+ config.path = Path.from_ids(
40
+ organization_name: organization_name,
41
+ project_name: project_name,
42
+ board_id: board_id,
43
+ work_item_id: work_item["id"]
44
+ )
45
+ end
46
+
38
47
  def select_work_item
39
- loop do
40
- column = cli.prompt.choice("Which column?", columns)
41
- warn("Fetching work items...")
42
- work_items = work_items_in_column(column)
43
-
44
- if work_items.length.zero?
45
- warn("Section is empty")
46
- next
47
- end
48
-
49
- work_item = cli.prompt.choice("Select a work item", work_items, nil_option: true)
50
- return work_item if work_item
48
+ column = cli.prompt.choice("Which column?", columns)
49
+ warn("Fetching work items...")
50
+ work_items = work_items_in_column(column)
51
+
52
+ if work_items.length.zero?
53
+ warn("Section is empty")
54
+ select_work_item
55
+ else
56
+ cli.prompt.choice("Select a work item", work_items, nil_option: true) || select_work_item
51
57
  end
52
58
  end
53
59
 
@@ -47,7 +47,15 @@ module Abt
47
47
 
48
48
  return git_global[access_token_key] unless git_global[access_token_key].nil?
49
49
 
50
- git_global[access_token_key] = cli.prompt.text(access_token_prompt_text)
50
+ git_global[access_token_key] = cli.prompt.text(<<~TXT)
51
+ Please provide your personal access token for the DevOps organization (#{organization_name}).
52
+ If you don't have one, follow the guide here: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate
53
+
54
+ The token MUST have "Read" permission for Work Items
55
+ Future features will likely require "Write" or "Manage
56
+
57
+ Enter access token"
58
+ TXT
51
59
  end
52
60
 
53
61
  private
@@ -59,18 +67,6 @@ module Abt
59
67
  def git_global
60
68
  @git_global ||= GitConfig.new("global", "abt.devops")
61
69
  end
62
-
63
- def access_token_prompt_text
64
- <<~TXT
65
- Please provide your personal access token for the DevOps organization (#{organization_name}).
66
- If you don't have one, follow the guide here: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate
67
-
68
- The token MUST have "Read" permission for Work Items
69
- Future features will likely require "Write" or "Manage
70
-
71
- Enter access token"
72
- TXT
73
- end
74
70
  end
75
71
  end
76
72
  end
@@ -10,12 +10,12 @@ module Abt
10
10
  WORK_ITEM_ID_REGEX = /(?<work_item_id>\d+)/.freeze
11
11
 
12
12
  PATH_REGEX =
13
- %r{^(#{ORGANIZATION_NAME_REGEX}/#{PROJECT_NAME_REGEX}/#{BOARD_ID_REGEX})?(/#{WORK_ITEM_ID_REGEX})?}.freeze
13
+ %r{^(#{ORGANIZATION_NAME_REGEX}/#{PROJECT_NAME_REGEX}(/#{BOARD_ID_REGEX}(/#{WORK_ITEM_ID_REGEX})?)?)?}.freeze
14
14
 
15
- def self.from_ids(organization_id = nil, project_name = nil, board_id = nil, work_item_id = nil)
16
- return new unless organization_id && project_name && board_id
15
+ def self.from_ids(organization_name: nil, project_name: nil, board_id: nil, work_item_id: nil)
16
+ return new unless organization_name && project_name && board_id
17
17
 
18
- new([organization_id, project_name, board_id, *work_item_id].join("/"))
18
+ new([organization_name, project_name, board_id, *work_item_id].join("/"))
19
19
  end
20
20
 
21
21
  def initialize(path = "")
@@ -61,8 +61,6 @@ module Abt
61
61
  end
62
62
 
63
63
  def branch_names_from_aris
64
- other_aris = cli.aris - [ari]
65
-
66
64
  abort("You must provide an additional ARI that responds to: branch-name. E.g., asana") if other_aris.empty?
67
65
 
68
66
  input = StringIO.new(cli.aris.to_s)
@@ -71,6 +69,10 @@ module Abt
71
69
 
72
70
  output.string.lines.map(&:strip).compact
73
71
  end
72
+
73
+ def other_aris
74
+ @other_aris ||= cli.aris - [ari]
75
+ end
74
76
  end
75
77
  end
76
78
  end
@@ -19,6 +19,10 @@ 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_project!
23
27
  return if project_id
24
28
 
@@ -14,8 +14,7 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
- abort("Must be run inside a git repository") unless config.local_available?
18
-
17
+ require_local_config!
19
18
  require_project!
20
19
  ensure_valid_configuration!
21
20
 
@@ -14,12 +14,11 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
- abort("Must be run inside a git repository") unless config.local_available?
18
-
17
+ require_local_config!
19
18
  projects # Load projects up front to make it obvious that searches are instant
20
19
  project = cli.prompt.search("Select a project", searchable_projects)["project"]
21
20
 
22
- config.path = Path.from_ids(project["id"])
21
+ config.path = Path.from_ids(project_id: project["id"])
23
22
 
24
23
  print_project(project)
25
24
  end
@@ -20,17 +20,17 @@ module Abt
20
20
  end
21
21
 
22
22
  def perform
23
- abort("Must be run inside a git repository") unless config.local_available?
23
+ require_local_config!
24
24
  require_project!
25
25
 
26
26
  warn(project["name"])
27
- task = cli.prompt.choice("Select a task", tasks)
27
+ task = pick_task
28
28
 
29
29
  print_task(project, task)
30
30
 
31
31
  return if flags[:"dry-run"]
32
32
 
33
- config.path = Path.from_ids(project_id, task["id"])
33
+ config.path = Path.from_ids(project_id: project_id, task_id: task["id"])
34
34
  end
35
35
 
36
36
  private
@@ -39,6 +39,10 @@ module Abt
39
39
  project_assignment["project"]
40
40
  end
41
41
 
42
+ def pick_task
43
+ cli.prompt.choice("Select a task", tasks)
44
+ end
45
+
42
46
  def tasks
43
47
  @tasks ||= project_assignment["task_assignments"].map { |ta| ta["task"] }
44
48
  end
@@ -42,8 +42,7 @@ module Abt
42
42
  end
43
43
 
44
44
  def create_time_entry
45
- body = time_entry_base_data
46
- body[:hours] = flags[:time] if flags.key?(:time)
45
+ body = time_entry_data
47
46
 
48
47
  result = api.post("time_entries", Oj.dump(body, mode: :json))
49
48
 
@@ -52,47 +51,62 @@ module Abt
52
51
  result
53
52
  end
54
53
 
54
+ def time_entry_data
55
+ body = time_entry_base_data
56
+
57
+ maybe_add_external_link(body)
58
+ maybe_add_comment(body)
59
+ maybe_add_time(body)
60
+
61
+ body
62
+ end
63
+
55
64
  def time_entry_base_data
56
- body = {
65
+ {
57
66
  project_id: project_id,
58
67
  task_id: task_id,
59
68
  user_id: config.user_id,
60
69
  spent_date: Date.today.iso8601
61
70
  }
71
+ end
62
72
 
73
+ def maybe_add_external_link(body)
63
74
  if external_link_data
64
75
  warn(<<~TXT)
65
76
  Linking to:
66
- #{external_link_data[:notes]}
67
- #{external_link_data[:external_reference][:permalink]}
77
+ #{external_link_data[:notes]}
78
+ #{external_link_data[:external_reference][:permalink]}
68
79
  TXT
69
80
  body.merge!(external_link_data)
70
81
  else
71
82
  warn("No external link provided")
72
83
  end
84
+ end
73
85
 
86
+ def maybe_add_comment(body)
74
87
  body[:notes] = flags[:comment] if flags.key?(:comment)
75
88
  body[:notes] ||= cli.prompt.text("Fill in comment (optional)")
76
- body
89
+ end
90
+
91
+ def maybe_add_time(body)
92
+ body[:hours] = flags[:time] if flags.key?(:time)
77
93
  end
78
94
 
79
95
  def external_link_data
80
- @external_link_data ||= begin
81
- lines = call_harvest_time_entry_data_for_other_aris
82
-
83
- if lines.empty?
84
- nil
85
- else
86
- if lines.length > 1
87
- abort("Got reference data from multiple scheme providers, only one is supported at a time")
88
- end
89
-
90
- Oj.load(lines.first, symbol_keys: true)
91
- end
96
+ return @external_link_data if instance_variable_defined?(:@external_link_data)
97
+
98
+ lines = fetch_link_data_lines
99
+
100
+ return @external_link_data = nil if lines.empty?
101
+
102
+ if lines.length > 1
103
+ abort("Got reference data from multiple scheme providers, only one is supported at a time")
92
104
  end
105
+
106
+ @external_link_data = Oj.load(lines.first, symbol_keys: true)
93
107
  end
94
108
 
95
- def call_harvest_time_entry_data_for_other_aris
109
+ def fetch_link_data_lines
96
110
  other_aris = cli.aris - [ari]
97
111
  return [] if other_aris.empty?
98
112
 
@@ -6,7 +6,7 @@ module Abt
6
6
  class Path < String
7
7
  PATH_REGEX = %r{^(?<project_id>\d+)?/?(?<task_id>\d+)?$}.freeze
8
8
 
9
- def self.from_ids(project_id = nil, task_id = nil)
9
+ def self.from_ids(project_id: nil, task_id: nil)
10
10
  path = project_id ? [project_id, *task_id].join("/") : ""
11
11
  new(path)
12
12
  end
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.22"
4
+ VERSION = "0.0.23"
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.22
4
+ version: 0.0.23
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-11 00:00:00.000000000 Z
11
+ date: 2021-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-inflector