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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf31b62944c6318cc60e62393171499223c81557
4
- data.tar.gz: 78e2f534aa333c215c8d3128689c664331563f30
3
+ metadata.gz: 8113060c03e8b470d3c0582958c574651b0aa650
4
+ data.tar.gz: a9ed25712ca61c36f13e263b129922579cf78f46
5
5
  SHA512:
6
- metadata.gz: 5893c0f57f13ce2dd62a8c1254de8ae190520c86eb13ebb1fae90b99058b1b2befea144735f9082d6fcbba2e5f2141114c6329c837cfe628267f6ef1e193b824
7
- data.tar.gz: 35c45ba31d312511bdf68be0021ee7a3460130ec6d4260785c1d28c3610d7f65ee5faf3333222ab0b28e3e5cf98446d5b1ea180e9fc545ef532d01fac736676d
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
- TYPES = [
5
- :rest,
6
- :agile
7
- ].freeze
7
+ def get(path, options={})
8
+ response = client.get(path, options[:params] || {}, headers)
9
+ process(response, options)
10
+ end
8
11
 
9
- ENDPOINTS = {
10
- rest: 'rest/api/2',
11
- agile: 'rest/greenhopper/latest'
12
- }.freeze
12
+ def post(path, options={})
13
+ response = client.post(path, options[:params] || {}, headers)
14
+ process(response, options)
15
+ end
13
16
 
14
- #
15
- # Initialize Jira::API
16
- #
17
- # @param type [Symbol]
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
- self.define_actions
22
+ def delete(path, options={})
23
+ response = client.delete(path, options[:params] || {}, headers)
24
+ process(response, options)
25
25
  end
26
26
 
27
- protected
27
+ private
28
28
 
29
- #
30
- # Defines the API DELETE, GET, POST, PUT interaction methods
31
- #
32
- def define_actions
33
- #
34
- # def method(path, params={})
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
- # If any, outputs all API errors described by the input JSON
64
- #
65
- # @param json [Hash] API response JSON
66
- # @param verbose [Boolean] true if errors should be output
67
- #
68
- # @return [Boolean] true if no errors exist
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
- # Returns the full JIRA REST API endpoint
81
- #
82
- # @param path [String] API path
83
- #
84
- # @return [String] API endpoint
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
- # Returns the default API headers
92
- #
93
- # @return [Hash] API headers
94
- #
95
- def headers
96
- { 'Content-Type' => 'application/json' }
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
@@ -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
@@ -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
- # determine assignee
7
- assignee = self.io.ask("Assignee (default auto)").strip
8
- assignee = "-1" if assignee.empty? # automatic assignee is used
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
- self.api.put("issue/#{ticket}/assignee", { name: assignee }) do |json|
11
- return
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
- self.api.get("issue/#{ticket}") do |json|
7
- attachments=json['fields']['attachment']
8
- if attachments.count > 0
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.agree("List comments for ticket #{ticket}")
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.agree("List comments for ticket #{ticket}")
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('[~@','[~')
@@ -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
- force = options[:force]
8
- self.api.get("issue/#{ticket}") do |json|
9
- issue_type = json['fields']['issuetype']
10
- if !issue_type['subtask']
11
- if !json['fields']['subtasks'].empty? && !force
12
- force = self.io.agree("Delete all sub-tasks for ticket #{ticket}")
13
- puts "No ticket deleted." and return if !force
14
- end
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
- self.api.delete("issue/#{ticket}?deleteSubtasks=#{force}") do |json|
18
- branches = `git branch --list 2> /dev/null`.split(' ')
19
- branches.delete("*")
20
- branches.delete("#{ticket}")
21
- create_branch = self.io.agree("Create branch") if branches.count > 1
22
- if branches.count == 1 or create_branch
23
- puts "Creating a new branch."
24
- new_branch = self.io.ask("Branch").strip
25
- new_branch.delete!(" ")
26
- puts "No ticket deleted." and return if new_branch.empty?
27
- `git branch #{new_branch} 2> /dev/null`
28
- branches << new_branch
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