jira-cli 0.2.1 → 0.2.2
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/lib/jira.rb +0 -14
- data/lib/jira/api.rb +51 -82
- data/lib/jira/command.rb +36 -0
- data/lib/jira/commands/all.rb +75 -0
- data/lib/jira/commands/assign.rb +53 -7
- data/lib/jira/commands/attachments.rb +27 -4
- data/lib/jira/commands/comment.rb +3 -3
- data/lib/jira/commands/delete.rb +81 -27
- data/lib/jira/commands/describe.rb +51 -68
- data/lib/jira/commands/install.rb +54 -16
- data/lib/jira/commands/link.rb +68 -38
- data/lib/jira/commands/log.rb +4 -4
- data/lib/jira/commands/new.rb +137 -79
- data/lib/jira/commands/rename.rb +44 -10
- data/lib/jira/commands/sprint.rb +80 -0
- data/lib/jira/commands/tickets.rb +28 -4
- data/lib/jira/commands/transition.rb +60 -24
- data/lib/jira/commands/vote.rb +89 -16
- data/lib/jira/commands/watch.rb +90 -16
- data/lib/jira/constants.rb +1 -1
- data/lib/jira/core.rb +25 -53
- data/lib/jira/legacy_api.rb +102 -0
- data/lib/jira/mixins.rb +4 -8
- data/lib/jira/sprint_api.rb +49 -0
- metadata +69 -11
- data/lib/jira/io.rb +0 -17
@@ -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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
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
|
-
|
11
|
+
end
|
12
12
|
|
13
|
-
|
13
|
+
module Command
|
14
|
+
class Install < Base
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
def run
|
17
|
+
io.say('Please enter your JIRA information.')
|
18
|
+
inifile[:global] = params
|
19
|
+
inifile.write
|
20
|
+
end
|
17
21
|
|
18
|
-
|
19
|
-
url: url,
|
20
|
-
username: username,
|
21
|
-
password: password
|
22
|
-
}
|
23
|
-
inifile.write
|
22
|
+
private
|
24
23
|
|
25
|
-
|
26
|
-
|
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
|
data/lib/jira/commands/link.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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:
|
52
|
-
name:
|
53
|
-
inward:
|
54
|
-
outward:
|
60
|
+
id: type['id'],
|
61
|
+
name: type['name'],
|
62
|
+
inward: type['inward'],
|
63
|
+
outward: type['outward']
|
55
64
|
}
|
56
|
-
|
65
|
+
types[type['name']] = data
|
57
66
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
data/lib/jira/commands/log.rb
CHANGED
@@ -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.
|
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.
|
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|
|
data/lib/jira/commands/new.rb
CHANGED
@@ -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
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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[
|
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
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
48
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|