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