jira-cli 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,85 +1,68 @@
1
+ require_relative '../command'
2
+
1
3
  module Jira
2
4
  class CLI < Thor
3
5
 
4
6
  desc "describe", "Describes the input ticket"
5
7
  def describe(ticket=Jira::Core.ticket)
6
- if Jira::Core.ticket?(ticket)
7
- output = description(ticket.strip, false, true, true)
8
- puts output if !output.strip.empty?
9
- end
8
+ Command::Describe.new(ticket).run
10
9
  end
11
10
 
12
- desc "all", "Describes all local branches that match JIRA ticketing syntax"
13
- def all
14
- # determine which local branches match JIRA ticket syntax
15
- # TODO - move to Jira::Git
16
- tickets = {
17
- current: nil,
18
- others: []
19
- }
20
- branches = `git branch`.strip.split("\n")
21
- branches.each do |branch|
22
- ticket = branch.delete('*').strip
23
- if Jira::Core.ticket?(ticket, false)
24
- if branch.include?('*')
25
- tickets[:current] = ticket
26
- else
27
- tickets[:others] << ticket
28
- end
29
- end
11
+ end
12
+
13
+ module Command
14
+ class Describe < Base
15
+
16
+ attr_accessor :ticket
17
+
18
+ def initialize(ticket)
19
+ self.ticket = ticket
30
20
  end
31
21
 
22
+ def run
23
+ return if json.empty?
24
+ return if errored?
25
+ render_table(header, [row])
26
+ end
32
27
 
33
- # asynchronously fetch and describe tickets
34
- output = ""
35
- threads = []
36
- if !tickets[:current].nil?
37
- threads << Thread.new{ puts description(tickets[:current], true) }
28
+ def header
29
+ [ 'Ticket', 'Assignee', 'Status', 'Summary' ]
38
30
  end
39
- mutex = Mutex.new
40
- tickets[:others].each do |ticket|
41
- threads << Thread.new do
42
- out = description(ticket) + "\n"
43
- if !out.strip.empty?
44
- mutex.synchronize{ output << out }
45
- end
46
- end
31
+
32
+ def row
33
+ [ ticket, assignee, status, summary ]
34
+ end
35
+
36
+ def errored?
37
+ return false if errors.empty?
38
+ puts errors
39
+ true
47
40
  end
48
- threads.each{ |thread| thread.join }
49
- puts output if !output.empty?
50
- end
51
41
 
52
- protected
53
-
54
- #
55
- # Returns a formatted description of the input ticket
56
- #
57
- # @param ticket [String] the ticket to describe
58
- # @param star [Boolean] if true, adds a * indicator
59
- #
60
- # @return [String] formatted summary string
61
- #
62
- def description(ticket, star=false, verbose=false, describe=false)
63
- self.api.get("issue/#{ticket}", nil, verbose) do |json|
64
- summary = json['fields']['summary']
65
- status = json['fields']['status']['name']
66
- assignee = "nil"
67
- if json['fields'].has_key?("assignee")
68
- if !json['fields']['assignee'].nil?
69
- assignee = json['fields']['assignee']['name']
70
- end
71
- end
72
- description = describe ? "\n" + json['fields']['description'].to_s : ""
73
-
74
- return Jira::Format.ticket(ticket) +
75
- (star ? Jira::Format.star : " ") + " \t" +
76
- ("(" + Jira::Format.user(assignee) + ")").ljust(20) +
77
- Jira::Format.status(status).ljust(26) +
78
- Jira::Format.summary(summary) +
79
- description
80
- end
81
- return ""
42
+ def errors
43
+ @errors ||= (json['errorMessages'] || []).join('. ')
82
44
  end
83
45
 
46
+ def assignee
47
+ (fields['assignee'] || {})['name'] || 'Unassigned'
48
+ end
49
+
50
+ def status
51
+ (fields['status'] || {})['name'] || 'Unknown'
52
+ end
53
+
54
+ def summary
55
+ truncate(json['fields']['summary'], 45)
56
+ end
57
+
58
+ def fields
59
+ json['fields'] || {}
60
+ end
61
+
62
+ def json
63
+ @json ||= api.get "issue/#{ticket}"
64
+ end
65
+
66
+ end
84
67
  end
85
68
  end
@@ -1,29 +1,67 @@
1
- #
2
- # Installation Script
3
- #
1
+ require_relative '../command'
4
2
 
5
3
  module Jira
6
4
  class CLI < Thor
7
5
 
8
- desc "install", "Guides the user through JIRA installation"
6
+ desc "install", "Guides the user through JIRA CLI installation"
9
7
  def install
8
+ Command::Install.new.run
9
+ end
10
10
 
11
- inifile = IniFile.new(:comment => '#', :encoding => 'UTF-8', :filename => Jira::Core.cli_path)
11
+ end
12
12
 
13
- url = self.io.ask("Enter your JIRA URL")
13
+ module Command
14
+ class Install < Base
14
15
 
15
- username = self.io.ask("Enter your JIRA username")
16
- password = self.io.ask("Enter your JIRA password", password: true)
16
+ def run
17
+ io.say('Please enter your JIRA information.')
18
+ inifile[:global] = params
19
+ inifile.write
20
+ end
17
21
 
18
- inifile[:global] = {
19
- url: url,
20
- username: username,
21
- password: password
22
- }
23
- inifile.write
22
+ private
24
23
 
25
- Jira::Core.send(:discard_memoized)
26
- end
24
+ def params
25
+ args = {
26
+ url: url,
27
+ username: username,
28
+ }
29
+ response = io.select("Select an authentication type:", ["basic", "token"])
30
+ case response
31
+ when "basic"
32
+ args[:password] = password
33
+ when "token"
34
+ args[:token] = token
35
+ else
36
+ raise InstallationException
37
+ end
38
+ args
39
+ end
40
+
41
+ def url
42
+ io.ask("JIRA URL:")
43
+ end
44
+
45
+ def username
46
+ io.ask("JIRA username:")
47
+ end
27
48
 
49
+ def password
50
+ io.mask("JIRA password:")
51
+ end
52
+
53
+ def token
54
+ io.ask("JIRA token:")
55
+ end
56
+
57
+ def inifile
58
+ @inifile ||= IniFile.new(
59
+ comment: '#',
60
+ encoding: 'UTF-8',
61
+ filename: Jira::Core.cli_path
62
+ )
63
+ end
64
+
65
+ end
28
66
  end
29
67
  end
@@ -1,19 +1,44 @@
1
+ require_relative '../command'
2
+
1
3
  module Jira
2
4
  class CLI < Thor
3
5
 
4
6
  desc "link", "Creates a link between two tickets in JIRA"
5
7
  def link(ticket=Jira::Core.ticket)
6
- self.api.get("issueLinkType") do |json|
7
- # determine issue link type
8
- issue_link_type = select_issue_link_type(json)
9
- break if issue_link_type.nil?
8
+ Command::Link.new(ticket).run
9
+ end
10
+
11
+ end
12
+
13
+ module Command
14
+ class Link < Base
15
+
16
+ attr_accessor :ticket
17
+
18
+ def initialize(ticket)
19
+ self.ticket = ticket
20
+ end
21
+
22
+ def run
23
+ return if ticket.empty?
24
+ return if metadata.empty?
25
+ return if issue_link_type.empty?
26
+ return if outward_ticket.empty?
27
+ return unless invalid_ticket?
10
28
 
11
- # determine outward ticket
12
- outward_ticket = self.io.ask("Outward ticket").strip
13
- break if !Jira::Core.ticket?(outward_ticket)
29
+ begin
30
+ api.post "issueLink",
31
+ params: params,
32
+ success: on_success,
33
+ failure: on_failure
34
+ rescue CommandException
35
+ end
36
+ end
14
37
 
15
- # determine api parameters
16
- params = {
38
+ private
39
+
40
+ def params
41
+ {
17
42
  type: {
18
43
  name: issue_link_type[:name]
19
44
  },
@@ -24,41 +49,46 @@ module Jira
24
49
  key: outward_ticket
25
50
  }
26
51
  }
27
-
28
- self.api.post("issueLink", params) do |json|
29
- puts "Successfully linked ticket #{ticket} to ticket #{outward_ticket}."
30
- return
31
- end
32
52
  end
33
- puts "No ticket linked."
34
- end
35
53
 
36
- protected
37
-
38
- #
39
- # Given the issue link type metadata, prompts the user for
40
- # the issue link type to use, then return the issue link
41
- # type hash
42
- #
43
- # @param json [Hash] issue link type metadata
44
- #
45
- # @return [Hash] selected issue link type metadata
46
- #
47
- def select_issue_link_type(json)
48
- issue_link_types = {}
49
- json['issueLinkTypes'].each do |issue_link_type|
54
+ def issue_link_type
55
+ return @issue_link_type unless @issue_link_type.nil?
56
+
57
+ types = {}
58
+ metadata['issueLinkTypes'].each do |type|
50
59
  data = {
51
- id: issue_link_type['id'],
52
- name: issue_link_type['name'],
53
- inward: issue_link_type['inward'],
54
- outward: issue_link_type['outward']
60
+ id: type['id'],
61
+ name: type['name'],
62
+ inward: type['inward'],
63
+ outward: type['outward']
55
64
  }
56
- issue_link_types[issue_link_type['name']] = data
65
+ types[type['name']] = data
57
66
  end
58
- issue_link_types['Cancel'] = nil
59
- choice = self.io.choose("Select a link type", issue_link_types.keys)
60
- return issue_link_types[choice]
67
+ choice = io.select("Select a link type:", types.keys)
68
+ @issue_link_type = types[choice]
69
+ end
70
+
71
+ def on_success
72
+ ->{ puts "Successfully linked ticket #{ticket} to ticket #{outward_ticket}." }
61
73
  end
62
74
 
75
+ def on_failure
76
+ ->{ puts "No ticket linked." }
77
+ end
78
+
79
+ def outward_ticket
80
+ @outward_ticket ||= io.ask("Outward ticket:").strip
81
+ end
82
+
83
+ def invalid_ticket?
84
+ return true unless Jira::Core.ticket?(outward_ticket)
85
+ return false
86
+ end
87
+
88
+ def metadata
89
+ @metadata ||= api.get("issueLinkType")
90
+ end
91
+
92
+ end
63
93
  end
64
94
  end
@@ -3,7 +3,7 @@ module Jira
3
3
 
4
4
  desc "log", "Logs work against the input ticket"
5
5
  def log(ticket=Jira::Core.ticket)
6
- time_spent = self.io.ask("Time spent on ticket #{ticket}")
6
+ time_spent = self.io.ask("Time spent on ticket #{ticket}:")
7
7
  self.api.post("issue/#{ticket}/worklog", { timeSpent: time_spent }) do |json|
8
8
  puts "Successfully logged #{time_spent} on ticket #{ticket}."
9
9
  end
@@ -11,7 +11,7 @@ module Jira
11
11
 
12
12
  desc "logd", "Deletes work against the input ticket"
13
13
  def logd(ticket=Jira::Core.ticket)
14
- logs(ticket) if self.io.agree("List worklogs for ticket #{ticket}")
14
+ logs(ticket) if self.io.yes?("List worklogs for ticket #{ticket}?")
15
15
 
16
16
  index = self.get_type_of_index("worklog", "delete")
17
17
  puts "No worklog deleted." and return if index < 0
@@ -53,12 +53,12 @@ module Jira
53
53
 
54
54
  desc "logu", "Updates work against the input ticket"
55
55
  def logu(ticket=Jira::Core.ticket)
56
- logs(ticket) if self.io.agree("List worklogs for ticket #{ticket}")
56
+ logs(ticket) if self.io.yes?("List worklogs for ticket #{ticket}?")
57
57
 
58
58
  index = self.get_type_of_index("worklog", "update")
59
59
  puts "No worklog updated." and return if index < 0
60
60
 
61
- time_spent = self.io.ask("Time spent on #{ticket}").strip
61
+ time_spent = self.io.ask("Time spent on #{ticket}:").strip
62
62
  puts "No worklog updated." and return if time_spent.empty?
63
63
 
64
64
  self.api.get("issue/#{ticket}/worklog") do |json|
@@ -1,97 +1,155 @@
1
+ require_relative '../command'
2
+
1
3
  module Jira
2
4
  class CLI < Thor
3
5
 
4
6
  desc "new", "Creates a new ticket in JIRA and checks out the git branch"
5
- def new(ticket = Jira::Core.ticket)
6
- self.api.get("issue/createmeta") do |meta|
7
- # determine project
8
- project = self.select_project(meta)
9
- break if project.empty?
10
-
11
- # determine issue type
12
- issue_type = self.select_issue_type(project)
13
- break if issue_type.nil?
14
-
15
- # determine parent (ticket)
16
- parent = nil
17
- parent = ticket if issue_type['subtask']
18
- break if !parent.nil? and !Jira::Core.ticket?(parent)
19
-
20
- # determine summary and description
21
- summary = self.io.ask("Summary")
22
- description = self.io.ask("Description")
23
-
24
- # determine api parameters
25
- params = {
7
+ def new
8
+ Command::New.new.run
9
+ end
10
+
11
+ end
12
+
13
+ module Command
14
+ class New < Base
15
+
16
+ def run
17
+ return if metadata.empty?
18
+ return if project.empty?
19
+ return if project_metadata.empty?
20
+ components # Select components if any after a project
21
+ return if issue_type.empty?
22
+ return if assign_parent? && parent.empty?
23
+ return if summary.empty?
24
+ return if description.empty?
25
+
26
+ api.post 'issue',
27
+ params: params,
28
+ success: on_success,
29
+ failure: on_failure
30
+ end
31
+
32
+ private
33
+
34
+ attr_accessor :ticket
35
+
36
+ def params
37
+ {
26
38
  fields: {
27
- project: { id: project[:id] },
39
+ project: { id: project['id'] },
28
40
  issuetype: { id: issue_type['id'] },
29
41
  summary: summary,
30
- description: description
42
+ description: description,
43
+ parent: @parent.nil? ? {} : { key: @parent },
44
+ components: @components.nil? ? [] : @components
31
45
  }
32
46
  }
33
- params[:fields][:parent] = { key: parent } if !parent.nil?
47
+ end
48
+
49
+ def on_success
50
+ ->(json) do
51
+ self.ticket = json['key']
52
+ io.say("Ticket #{ticket} created.")
53
+ assign?
54
+ create_branch? && checkout_branch?
55
+ end
56
+ end
57
+
58
+ def assign?
59
+ Command::Assign.new(ticket).run if io.yes?('Assign?')
60
+ end
61
+
62
+ def create_branch?
63
+ return false if io.no?("Create branch?")
64
+ `git branch #{ticket} 2> /dev/null`
65
+ true
66
+ end
67
+
68
+ def checkout_branch?
69
+ return false if io.no?("Check-out branch?")
70
+ `git checkout #{ticket} 2> /dev/null`
71
+ true
72
+ end
73
+
74
+ def on_failure
75
+ ->{ puts "No ticket created." }
76
+ end
77
+
78
+ def metadata
79
+ # TODO - {} on 200 but jira error
80
+ @metadata ||= api.get('issue/createmeta')
81
+ end
82
+
83
+ def project
84
+ @project ||= projects[
85
+ io.select("Select a project:", projects.keys)
86
+ ]
87
+ end
34
88
 
35
- # post issue to server
36
- self.api.post("issue", params) do |json|
37
- ticket = json['key']
38
- if self.io.agree("Assign")
39
- self.assign(ticket)
89
+ def projects
90
+ @projects ||= (
91
+ projects = {}
92
+ metadata['projects'].each do |project|
93
+ projects[project['name']] = {
94
+ 'id' => project['id'],
95
+ 'issues' => project['issuetypes']
96
+ }
40
97
  end
41
- if self.io.agree("Create branch")
42
- `git branch #{ticket} 2> /dev/null`
43
- if self.io.agree("Check-out branch")
44
- `git checkout #{ticket} 2> /dev/null`
45
- end
98
+ projects
99
+ )
100
+ end
101
+
102
+ def project_metadata
103
+ id = project['id']
104
+ @project_metadata ||= api.get("project/#{id}")
105
+ end
106
+
107
+ def components
108
+ @components ||= (
109
+ components = {}
110
+ project_metadata['components'].each do |component|
111
+ components[component['name']] = {
112
+ 'id' => component['id']
113
+ }
46
114
  end
47
- return
48
- end
115
+ io.multi_select("Select component(s):", components) unless components.empty?
116
+ )
49
117
  end
50
- puts "No ticket created."
51
- end
52
118
 
53
- protected
54
-
55
- #
56
- # Given the creation metadata, prompts the user for the project to use,
57
- # then return the project data hash
58
- #
59
- # @param json [Hash] creation metadata
60
- #
61
- # @return [Hash] selected project's metadata
62
- #
63
- def select_project(json)
64
- projects = {}
65
- json['projects'].each do |project|
66
- data = {
67
- id: project['id'],
68
- issues: project['issuetypes']
69
- }
70
- projects[project['name']] = data
71
- end
72
- projects['Cancel'] = nil
73
- choice = self.io.choose("Select a project", projects.keys)
74
- return {} if choice == 'Cancel'
75
- return projects[choice]
76
- end
77
-
78
- #
79
- # Given the project metadata, prompts the user for the issue type to use
80
- # and returns the selected issue type.
81
- #
82
- # @param project_data [Hash] project metadata
83
- #
84
- # @return [Hash] selected issue type
85
- #
86
- def select_issue_type(project_data)
87
- issue_types = {}
88
- project_data[:issues].each do |issue_type|
89
- issue_types[issue_type['name']] = issue_type
90
- end
91
- issue_types['Cancel'] = nil
92
- choice = self.io.choose("Select an issue type", issue_types.keys)
93
- return issue_types[choice]
119
+ def assign_parent?
120
+ return false unless issue_type['subtask']
121
+ return false if io.no?('Set parent of subtask?')
122
+ true
94
123
  end
95
124
 
125
+ def parent
126
+ @parent ||= io.ask('Subtask parent:', default: Jira::Core.ticket)
127
+ end
128
+
129
+ def issue_type
130
+ @issue_type ||= issue_types[
131
+ io.select("Select an issue type:", issue_types.keys)
132
+ ]
133
+ end
134
+
135
+ def issue_types
136
+ @issue_types ||= (
137
+ issue_types = {}
138
+ project['issues'].each do |issue_type|
139
+ issue_types[issue_type['name']] = issue_type
140
+ end
141
+ issue_types
142
+ )
143
+ end
144
+
145
+ def summary
146
+ @summary ||= io.ask("Summary:")
147
+ end
148
+
149
+ def description
150
+ @description ||= io.ask("Description:")
151
+ end
152
+
153
+ end
96
154
  end
97
155
  end