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.
- 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
|