abt-cli 0.0.22 → 0.0.23

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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