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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8113060c03e8b470d3c0582958c574651b0aa650
|
4
|
+
data.tar.gz: a9ed25712ca61c36f13e263b129922579cf78f46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7fc9c0bcc93c5f425bbcc1dd076656d7b3ca3c50e2111ac814a34d7d10a02574c74f70c0202b55af585871bf29e1ac0c1bc5975252971cdcc79892733bb1471c
|
7
|
+
data.tar.gz: 5ee4cb4c5adc1f38f3f4dece433a0b52cfadd3257c99e48e1b7d4073476f044a6b14eda73e5663233a674540325937eb9b00d4de72e88352acdf78deed18730a
|
data/lib/jira.rb
CHANGED
@@ -9,19 +9,5 @@ Dir.glob(
|
|
9
9
|
module Jira
|
10
10
|
class CLI < Thor
|
11
11
|
|
12
|
-
def initialize(args=[], options={}, config={})
|
13
|
-
super
|
14
|
-
self.suppress{ Jira::Core.setup }
|
15
|
-
self.suppress{ self.api }
|
16
|
-
end
|
17
|
-
|
18
|
-
protected
|
19
|
-
|
20
|
-
def suppress
|
21
|
-
yield
|
22
|
-
rescue GitException
|
23
|
-
rescue InstallationException
|
24
|
-
end
|
25
|
-
|
26
12
|
end
|
27
13
|
end
|
data/lib/jira/api.rb
CHANGED
@@ -1,100 +1,69 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
|
1
4
|
module Jira
|
2
5
|
class API
|
3
6
|
|
4
|
-
|
5
|
-
:
|
6
|
-
|
7
|
-
|
7
|
+
def get(path, options={})
|
8
|
+
response = client.get(path, options[:params] || {}, headers)
|
9
|
+
process(response, options)
|
10
|
+
end
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
def post(path, options={})
|
13
|
+
response = client.post(path, options[:params] || {}, headers)
|
14
|
+
process(response, options)
|
15
|
+
end
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
def initialize(type)
|
20
|
-
@type = type
|
21
|
-
@client = Faraday.new
|
22
|
-
@client.basic_auth(Jira::Core.username, Jira::Core.password)
|
17
|
+
def patch(path, options={})
|
18
|
+
response = client.put(path, options[:params] || {}, headers)
|
19
|
+
process(response, options)
|
20
|
+
end
|
23
21
|
|
24
|
-
|
22
|
+
def delete(path, options={})
|
23
|
+
response = client.delete(path, options[:params] || {}, headers)
|
24
|
+
process(response, options)
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
private
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# Issue an API DELETE, GET, POST, or PUT request and return parse JSON
|
37
|
-
#
|
38
|
-
# @param path [String] API path
|
39
|
-
# @param params [Hash] params to send
|
40
|
-
#
|
41
|
-
# @yield(Hash) yields to a success block
|
42
|
-
#
|
43
|
-
# @return [JSON] parased API response
|
44
|
-
#
|
45
|
-
[:delete, :get, :post, :put].each do |method|
|
46
|
-
self.class.send(:define_method, method) do |path, params=nil, verbose=true, &block|
|
47
|
-
params = params.to_json if !params.nil?
|
48
|
-
response = @client.send(
|
49
|
-
method,
|
50
|
-
self.endpoint(path),
|
51
|
-
params,
|
52
|
-
self.headers
|
53
|
-
)
|
54
|
-
json = response.body.to_s.from_json
|
55
|
-
if self.errorless?(json, verbose)
|
56
|
-
block.call(json)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
29
|
+
def process(response, options)
|
30
|
+
json = response.body || {}
|
31
|
+
if response.success? && json['errorMessages'].nil?
|
32
|
+
respond_to(options[:success], json)
|
33
|
+
else
|
34
|
+
puts json['errorMessages'].join('. ') unless json['errorMessages'].nil?
|
35
|
+
respond_to(options[:failure], json)
|
60
36
|
end
|
37
|
+
json
|
38
|
+
end
|
61
39
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
#
|
70
|
-
def errorless?(json, verbose=true)
|
71
|
-
errors = json['errorMessages']
|
72
|
-
if !errors.nil?
|
73
|
-
puts errors.join('. ') if verbose
|
74
|
-
return false
|
75
|
-
end
|
76
|
-
return true
|
40
|
+
def respond_to(block, json)
|
41
|
+
return if block.nil?
|
42
|
+
case block.arity
|
43
|
+
when 0
|
44
|
+
block.call
|
45
|
+
when 1
|
46
|
+
block.call(json)
|
77
47
|
end
|
48
|
+
end
|
78
49
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
def endpoint(path)
|
87
|
-
"#{Jira::Core.url}/#{ENDPOINTS[@type]}/#{path}"
|
50
|
+
def client
|
51
|
+
@client ||= Faraday.new(endpoint) do |faraday|
|
52
|
+
faraday.request :basic_auth, Jira::Core.username, Jira::Core.password unless Jira::Core.password.nil?
|
53
|
+
faraday.request :token_auth, Jira::Core.token unless Jira::Core.token.nil?
|
54
|
+
faraday.request :json
|
55
|
+
faraday.response :json
|
56
|
+
faraday.adapter :net_http
|
88
57
|
end
|
58
|
+
end
|
89
59
|
|
90
|
-
|
91
|
-
#
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
60
|
+
def endpoint
|
61
|
+
"#{Jira::Core.url}/rest/api/2"
|
62
|
+
end
|
63
|
+
|
64
|
+
def headers
|
65
|
+
{ 'Content-Type' => 'application/json' }
|
66
|
+
end
|
98
67
|
|
99
68
|
end
|
100
69
|
end
|
data/lib/jira/command.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'tty-table'
|
2
|
+
|
3
|
+
module Jira
|
4
|
+
module Command
|
5
|
+
class Base
|
6
|
+
|
7
|
+
def run
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def api
|
14
|
+
@api ||= Jira::API.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def sprint_api
|
18
|
+
@sprint_api ||= Jira::SprintAPI.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def io
|
22
|
+
@io ||= TTY::Prompt.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def render_table(header, rows)
|
26
|
+
puts TTY::Table.new(header, rows).render(:ascii, padding: [0,1])
|
27
|
+
end
|
28
|
+
|
29
|
+
def truncate(string, limit=80)
|
30
|
+
return string if string.length < limit
|
31
|
+
string[0..limit-3] + '...'
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Jira
|
2
|
+
class CLI < Thor
|
3
|
+
|
4
|
+
desc "all", "Describes all local branches that match JIRA ticketing syntax"
|
5
|
+
def all
|
6
|
+
Command::All.new.run
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
module Command
|
12
|
+
class All < Base
|
13
|
+
|
14
|
+
def run
|
15
|
+
puts 'No tickets' and return if tickets.empty?
|
16
|
+
return if json.empty?
|
17
|
+
return if errored?
|
18
|
+
render_table(header, rows)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def header
|
24
|
+
[ 'Ticket', 'Assignee', 'Status', 'Summary' ]
|
25
|
+
end
|
26
|
+
|
27
|
+
def rows
|
28
|
+
json['issues'].map do |issue|
|
29
|
+
[
|
30
|
+
issue['key'],
|
31
|
+
issue['fields']['assignee']['name'],
|
32
|
+
issue['fields']['status']['name'],
|
33
|
+
truncate(issue['fields']['summary'], 45)
|
34
|
+
]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def errored?
|
39
|
+
return false if errors.empty?
|
40
|
+
puts errors
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def errors
|
45
|
+
@errors ||= (json['errorMessages'] || []).join('. ')
|
46
|
+
end
|
47
|
+
|
48
|
+
def json
|
49
|
+
@json ||= api.get "search", params: params
|
50
|
+
end
|
51
|
+
|
52
|
+
def params
|
53
|
+
{
|
54
|
+
jql: "key in (#{tickets.join(',')})"
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def tickets
|
59
|
+
@tickets ||= (
|
60
|
+
tickets = []
|
61
|
+
branches.each do |branch|
|
62
|
+
ticket = branch.delete('*').strip
|
63
|
+
tickets << ticket if Jira::Core.ticket?(ticket, false)
|
64
|
+
end
|
65
|
+
tickets
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def branches
|
70
|
+
`git branch`.strip.split("\n")
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/jira/commands/assign.rb
CHANGED
@@ -1,17 +1,63 @@
|
|
1
|
+
require_relative '../command'
|
2
|
+
|
1
3
|
module Jira
|
2
4
|
class CLI < Thor
|
3
5
|
|
4
6
|
desc "assign", "Assign a ticket to a user"
|
5
7
|
def assign(ticket=Jira::Core.ticket)
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
Command::Assign.new(ticket).run
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
module Command
|
14
|
+
class Assign < Base
|
15
|
+
|
16
|
+
attr_accessor :ticket
|
9
17
|
|
10
|
-
|
11
|
-
|
18
|
+
def initialize(ticket)
|
19
|
+
self.ticket = ticket
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
api.patch path,
|
24
|
+
params: params,
|
25
|
+
success: on_success,
|
26
|
+
failure: on_failure
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_success
|
30
|
+
-> do
|
31
|
+
puts "Ticket #{ticket} assigned to #{name}."
|
32
|
+
end
|
12
33
|
end
|
13
|
-
puts "Ticket #{ticket} not assigned."
|
14
|
-
end
|
15
34
|
|
35
|
+
def on_failure
|
36
|
+
->(json) do
|
37
|
+
message = (json['errors'] || {})['assignee']
|
38
|
+
puts message || "Ticket #{ticket} was not assigned."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def path
|
43
|
+
"issue/#{ticket}/assignee"
|
44
|
+
end
|
45
|
+
|
46
|
+
def params
|
47
|
+
{ name: assignee }
|
48
|
+
end
|
49
|
+
|
50
|
+
def name
|
51
|
+
assignee == '-1' ? 'default user' : "'#{assignee}'"
|
52
|
+
end
|
53
|
+
|
54
|
+
def assignee
|
55
|
+
@assignee ||= (
|
56
|
+
assignee = io.ask('Assignee?', default: 'auto')
|
57
|
+
assignee == 'auto' ? '-1' : assignee
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
16
62
|
end
|
17
63
|
end
|
@@ -3,9 +3,27 @@ module Jira
|
|
3
3
|
|
4
4
|
desc "attachments", "View ticket attachments"
|
5
5
|
def attachments(ticket=Jira::Core.ticket)
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
Command::Attachments.new(ticket).run
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
module Command
|
12
|
+
class Attachments < Base
|
13
|
+
|
14
|
+
attr_accessor :ticket
|
15
|
+
|
16
|
+
def initialize(ticket)
|
17
|
+
self.ticket = ticket
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
return if ticket.empty?
|
22
|
+
return if metadata.empty?
|
23
|
+
return if metadata['fields'].nil?
|
24
|
+
|
25
|
+
attachments=metadata['fields']['attachment']
|
26
|
+
if !attachments.nil? and attachments.count > 0
|
9
27
|
attachments.each do |attachment|
|
10
28
|
name=attachment['filename']
|
11
29
|
url=attachment['content']
|
@@ -16,7 +34,12 @@ module Jira
|
|
16
34
|
puts "No attachments found"
|
17
35
|
end
|
18
36
|
end
|
19
|
-
end
|
20
37
|
|
38
|
+
private
|
39
|
+
|
40
|
+
def metadata
|
41
|
+
@metadata ||= api.get("issue/#{ticket}")
|
42
|
+
end
|
43
|
+
end
|
21
44
|
end
|
22
45
|
end
|
@@ -47,7 +47,7 @@ module Jira
|
|
47
47
|
protected
|
48
48
|
|
49
49
|
def comment_delete(ticket)
|
50
|
-
comments(ticket) if self.io.
|
50
|
+
comments(ticket) if self.io.yes?("List comments for ticket #{ticket}?")
|
51
51
|
|
52
52
|
index = self.get_type_of_index("comment", "delete")
|
53
53
|
puts "No comment deleted." and return if index < 0
|
@@ -66,7 +66,7 @@ module Jira
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def comment_update(ticket)
|
69
|
-
comments(ticket) if self.io.
|
69
|
+
comments(ticket) if self.io.yes?("List comments for ticket #{ticket}?")
|
70
70
|
|
71
71
|
index = self.get_type_of_index("comment", "update")
|
72
72
|
puts "No comment updated." and return if index < 0
|
@@ -92,7 +92,7 @@ module Jira
|
|
92
92
|
# @return comment [String] asked comment body
|
93
93
|
#
|
94
94
|
def get_comment_body(ticket)
|
95
|
-
comment = self.io.ask("Leave a comment for ticket #{ticket}").strip
|
95
|
+
comment = self.io.ask("Leave a comment for ticket #{ticket}:").strip
|
96
96
|
temp = comment.gsub(/\@[a-zA-Z]+/,'[~\0]')
|
97
97
|
temp = comment if temp.nil?
|
98
98
|
temp = temp.gsub('[~@','[~')
|
data/lib/jira/commands/delete.rb
CHANGED
@@ -1,40 +1,94 @@
|
|
1
|
+
require_relative '../command'
|
2
|
+
|
1
3
|
module Jira
|
2
4
|
class CLI < Thor
|
3
5
|
|
4
6
|
desc "delete", "Deletes a ticket in JIRA and the git branch"
|
5
7
|
method_option :force, type: :boolean, default: false
|
6
8
|
def delete(ticket=Jira::Core.ticket)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
Command::Delete.new(ticket, options[:force]).run
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
module Command
|
15
|
+
class Delete < Base
|
16
|
+
|
17
|
+
attr_accessor :ticket, :force
|
18
|
+
|
19
|
+
def initialize(ticket, force)
|
20
|
+
self.ticket = ticket
|
21
|
+
self.force = force
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
return if ticket.empty?
|
26
|
+
return if metadata.empty?
|
27
|
+
return if metadata['fields'].nil?
|
28
|
+
return if subtasks_failure?
|
29
|
+
|
30
|
+
api.delete "issue/#{ticket}?deleteSubtasks=#{force}",
|
31
|
+
success: on_success,
|
32
|
+
failure: on_failure
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def on_success
|
38
|
+
-> do
|
39
|
+
on_failure and return unless create_branch?
|
40
|
+
on_failure and return unless delete_branch?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_failure
|
45
|
+
-> { puts "No change made to ticket #{ticket}." }
|
46
|
+
end
|
47
|
+
|
48
|
+
def branches
|
49
|
+
branches = `git branch --list 2> /dev/null`.split(' ')
|
50
|
+
branches.delete("*")
|
51
|
+
branches.delete("#{ticket}")
|
52
|
+
branches
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_branch?
|
56
|
+
response = io.yes?("Create branch?")
|
57
|
+
|
58
|
+
if branches.count == 1 or response
|
59
|
+
io.say("Creating a new branch.")
|
60
|
+
new_branch = io.ask("Branch?").strip
|
61
|
+
new_branch.delete!(" ")
|
62
|
+
on_failure and return false if new_branch.empty?
|
63
|
+
`git branch #{new_branch} 2> /dev/null`
|
15
64
|
end
|
65
|
+
true
|
66
|
+
end
|
16
67
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
chosen_branch = self.io.choose("Select a branch", branches)
|
31
|
-
`git checkout #{chosen_branch} 2> /dev/null`
|
32
|
-
`git branch -D #{ticket} 2> /dev/null`
|
33
|
-
return
|
68
|
+
def delete_branch?
|
69
|
+
response = self.io.select("Select a branch:", branches)
|
70
|
+
`git checkout #{response} 2> /dev/null`
|
71
|
+
`git branch -D #{ticket} 2> /dev/null`
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def subtasks_failure?
|
76
|
+
return false unless subtask?
|
77
|
+
if !metadata['fields']['subtasks'].empty? && !force
|
78
|
+
self.force = io.yes?("Delete all sub-tasks for ticket #{ticket}?")
|
79
|
+
return true unless force
|
34
80
|
end
|
81
|
+
false
|
82
|
+
end
|
83
|
+
|
84
|
+
def subtask?
|
85
|
+
metadata['fields']['issuetype']['subtask']
|
35
86
|
end
|
36
|
-
puts "No ticket deleted."
|
37
|
-
end
|
38
87
|
|
88
|
+
def metadata
|
89
|
+
@metadata ||= api.get("issue/#{ticket}")
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
39
93
|
end
|
40
94
|
end
|