lita-github 0.0.13 → 0.0.14

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: 0349ebd1948d263860af6b90c1a24447a6408aea
4
- data.tar.gz: a4e1279c91922ce93c18519701a91dca1f2ce76e
3
+ metadata.gz: 2012ebdeed7b0904f7d65cb1d1da4fe0988a031b
4
+ data.tar.gz: f8f82d62bd1e38ab05156f65b8ec159ae265b98b
5
5
  SHA512:
6
- metadata.gz: 19e4723298203e74eb221818523164fa6e54d7e2c4d92bbf6474c1f7cbb8c7751c2c457f2752780957bebbd28a3c221445477140363775dedd6210b71912f504
7
- data.tar.gz: 2d3b335bfa654197c4118bdd77b1d91e596e62b6701936dac401b692dab8ef5f8f79bc0c106d1c2be25d97a73cfdc938e6c603573dbfcb532763cd3dd8cb4504
6
+ metadata.gz: 9c74417471b025d45253f622a97e054e1cfa49a0fabbf55879c9b14b45d9293b414407e62c1d357d40a16a66817616831143d725665e9a720ba8472775d8dae5
7
+ data.tar.gz: bfba8e8cb4ea94d8b6164c5489bcb3d4624be502f28b2a8a1a357ce6289f7b3559da6567647685ac6b592dec74d1a73d505cb4002b1313716a3100e27f78afe3
@@ -25,3 +25,4 @@ require 'lita/handlers/github'
25
25
  require 'lita/handlers/github_repo'
26
26
  require 'lita/handlers/github_pr'
27
27
  require 'lita/handlers/github_org'
28
+ require 'lita/handlers/github_issues'
@@ -21,31 +21,37 @@ module LitaGithub
21
21
  #
22
22
  # @author Tim Heckman <tim@pagerduty.com>
23
23
  module General
24
- # Parse the options in the command using the standard option regex
24
+ # Convert the value of parameter 1 to an Integer if it looks like it should be one
25
25
  #
26
26
  # @author Tim Heckman <tim@pagerduty.com>
27
- # @param cmd [String] the full command line provided to Lita
28
- # @return [Hash] the key:value pairs that were in the command string
29
- def opts_parse(cmd)
30
- o = {}
31
- cmd.scan(LitaGithub::R::OPT_REGEX).flatten.each do |opt|
32
- k, v = opt.strip.split(':')
33
- k = k.to_sym
34
- o[k] = v unless o.key?(k)
35
- end
36
- o
27
+ # @param value [String] the value to see if it should be an Integer
28
+ # @return [Integer] if the String <tt>value</tt> looks like an Integer (<tt>/^\d+$/</tt>), return it as one
29
+ # @return [String] if the String <tt>value</tt> is not an Integer, return as is
30
+ def to_i_if_numeric(value)
31
+ /^\d+$/.match(value) ? value.to_i : value
37
32
  end
38
33
 
39
- # Parse the options in the command using the extended option regex
34
+ # For use when parsing options using <tt>opt_parse()</tt>, this method converts the key to a downcased symbol
35
+ # for use within the option Hash.
36
+ #
37
+ # @author Tim Heckman <tim@pagerduty.com>
38
+ # @param k [String] the key
39
+ # @param v [Any] the value
40
+ # @return [Array] the first element is <tt>key</tt> as a downcased symbol, the second is <tt>v</tt> with no changes
41
+ def symbolize_opt_key(k, v)
42
+ [k.downcase.to_sym, v]
43
+ end
44
+
45
+ # Parse the options in the command using the option regex
40
46
  #
41
47
  # @author Tim Heckman <tim@pagerduty.com>
42
48
  # @param cmd [String] the full command line provided to Lita
43
49
  # @return [Hash] the key:value pairs that were in the command string
44
- def e_opts_parse(cmd)
50
+ def opts_parse(cmd)
45
51
  o = {}
46
- cmd.scan(LitaGithub::R::E_OPT_REGEX).flatten.each do |opt|
47
- k, v = opt.strip.split(':')
48
- k = k.to_sym
52
+ cmd.scan(LitaGithub::R::OPT_REGEX).flatten.each do |opt|
53
+ k, v = symbolize_opt_key(*opt.strip.split(':'))
54
+ next if o.key?(k)
49
55
 
50
56
  # if it looks like we're using the extended option (first character is a " or '):
51
57
  # slice off the first and last character of the string
@@ -53,7 +59,7 @@ module LitaGithub
53
59
  # do nothing
54
60
  v = v.slice!(1, (v.length - 2)) if %w(' ").include?(v.slice(0))
55
61
 
56
- o[k] = v unless o.key?(k)
62
+ o[k] = to_i_if_numeric(v)
57
63
  end
58
64
  o
59
65
  end
@@ -22,13 +22,9 @@ module LitaGithub
22
22
  # command alias regex (!gh or !github for example)
23
23
  A_REG ||= '(?:gh|github)\s+?'
24
24
 
25
- # option regex, letting you scan the command string of things like:
26
- # key1:value key2:value, etc.
27
- OPT_REGEX ||= /((?:\s+?[a-zA-Z0-9_]+?):(?:[a-zA-Z0-9_]+))/
28
-
29
- # extended option regex, letting you scan the string for things like:
25
+ # option regex, letting you scan the string for things like:
30
26
  # key1:value key2:"value2" key3:'a better value'
31
- E_OPT_REGEX ||= /((?:\s+?[a-zA-Z0-9_]+?):(?:(?:".+?")|(?:'.+?')|(?:[a-zA-Z0-9_]+)))/
27
+ OPT_REGEX ||= /((?:\s+?[a-zA-Z0-9_]+?):(?:(?:".+?")|(?:'.+?')|(?:[a-zA-Z0-9_]+)))/
32
28
 
33
29
  # regex matcher for: Org/repo
34
30
  REPO_REGEX = '(?:(?<org>[a-zA-Z0-9_\-]+)(?:\s+?)?\/)?(?:\s+?)?(?<repo>[a-zA-Z0-9_\-]+)'
@@ -21,8 +21,8 @@
21
21
  # @author Tim Heckman <tim@pagerduty.com>
22
22
  module LitaGithub
23
23
  # lita-github version
24
- VERSION = '0.0.13'
24
+ VERSION = '0.0.14'
25
25
 
26
26
  # lita-github version split amongst different revisions
27
- MAJ, MIN, REV = VERSION.split('.').map(&:to_i)
27
+ MAJOR_VERSION, MINOR_VERSION, REVISION = VERSION.split('.').map(&:to_i)
28
28
  end
@@ -68,8 +68,9 @@ module Lita
68
68
  def self.default_config(config)
69
69
  # when setting default configuration values please remember one thing:
70
70
  # secure and safe by default
71
- config.default_team_slug = nil
72
- config.repo_private_default = true
71
+ config.default_team_slug = nil
72
+ config.repo_private_default = true
73
+ config.org_team_add_allowed_perms = %w(pull)
73
74
 
74
75
  ####
75
76
  # Method Filters
@@ -92,7 +93,9 @@ module Lita
92
93
  # Lita::Handlers::GithubOrg
93
94
  config.org_team_add_enabled = false
94
95
  config.org_team_rm_enabled = false
95
- config.org_team_add_allowed_perms = %w(pull)
96
+ config.org_user_add_enabled = false
97
+ config.org_user_rm_enabled = false
98
+ config.org_eject_user_enabled = false
96
99
  end
97
100
 
98
101
  def status(response)
@@ -0,0 +1,110 @@
1
+ # -*- coding: UTF-8 -*-
2
+ #
3
+ # Copyright 2014 PagerDuty, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'lita-github/r'
18
+ require 'lita-github/general'
19
+ require 'lita-github/config'
20
+ require 'lita-github/octo'
21
+ require 'lita-github/org'
22
+ require 'lita-github/repo'
23
+ require 'lita-github/filters'
24
+
25
+ module Lita
26
+ # Lita Handler
27
+ module Handlers
28
+ # GitHub Issues Lita Handler
29
+ class GithubIssues < Handler
30
+ include LitaGithub::General # Github handler common-use methods
31
+ include LitaGithub::Config # Github handler Lita configuration methods
32
+ include LitaGithub::Octo # Github handler common-use Octokit methods
33
+ include LitaGithub::Org # Github handler common-use Organization methods
34
+ include LitaGithub::Repo # Github handler common-use Repository methods
35
+ include LitaGithub::Filters # Github handler common-use method filters
36
+
37
+ on :loaded, :setup_octo # from LitaGithub::Octo
38
+
39
+ # issue states
40
+ LI_STATES = %w(open closed all)
41
+ # issue states
42
+ LI_SM = %w(created updated comments)
43
+ # sort direction
44
+ LI_DIR = %w(asc desc)
45
+
46
+ route(
47
+ /#{LitaGithub::R::A_REG}(?:issues|repo\s+?issues)\s+?#{LitaGithub::R::REPO_REGEX}/,
48
+ :issues_list,
49
+ command: true,
50
+ help: {
51
+ 'gh issues PagerDuty/lita-github' => 'list the issues on a repo',
52
+ 'gh issues PagerDuty/lita-github state:closed sort:updated sort:desc' => 'just showing some option usage'
53
+ }
54
+ )
55
+
56
+ # This is the handler for listing issues on a repository
57
+ # @author Tim Heckman <tim@pagerduty.com>
58
+ def issues_list(response)
59
+ full_name = rpo(*repo_match(response.match_data))
60
+ opts = opts_parse(response.message.body)
61
+
62
+ oops = validate_list_opts(opts)
63
+
64
+ return response.reply(oops) unless oops.empty?
65
+
66
+ return response.reply(t('repo_not_found', repo: full_name)) unless repo?(full_name)
67
+
68
+ # get the issues that are not pull requests
69
+ begin
70
+ issues = octo.list_issues(full_name, opts).reject { |i| i.key?(:pull_request) }
71
+ rescue Octokit::UnprocessableEntity => e
72
+ return response.reply(t('issues_list.invalid_opts', m: e.message))
73
+ rescue StandardError => e
74
+ return response.reply(t('boom', m: e.message))
75
+ end
76
+
77
+ if issues.empty?
78
+ reply = t('issues_list.none', r: full_name)
79
+ else
80
+ reply = t('issues_list.header', n: issues.length, r: full_name)
81
+ issues.each { |i| reply << t('issues_list.item', i.to_h.merge(u: i[:user][:login], r: full_name)) }
82
+ end
83
+
84
+ response.reply(reply)
85
+ end
86
+
87
+ private
88
+
89
+ # rubocop:disable Metrics/CyclomaticComplexity
90
+ # rubocop:disable Metrics/PerceivedComplexity
91
+ #
92
+ # Validate the command options
93
+ #
94
+ # @param opts [Hash] the key:value pairs from the command
95
+ # @return [String] the response to reply with if there was a validation failure
96
+ def validate_list_opts(opts)
97
+ resp = ''
98
+ resp << t('issues_list.val_states') if opts.key?(:state) && !LI_STATES.include?(opts[:state])
99
+ resp << t('issues_list.val_sort') if opts.key?(:sort) && !LI_SM.include?(opts[:sort])
100
+ resp << t('issues_list.val_direction') if opts.key?(:direction) && !LI_DIR.include?(opts[:direction])
101
+ resp = "Invalid option(s):\n#{resp}" unless resp.empty?
102
+ resp
103
+ end
104
+ # rubocop:enable Metrics/CyclomaticComplexity
105
+ # rubocop:enable Metrics/PerceivedComplexity
106
+ end
107
+
108
+ Lita.register_handler(GithubIssues)
109
+ end
110
+ end
@@ -68,6 +68,42 @@ module Lita
68
68
  }
69
69
  )
70
70
 
71
+ # rubocop:disable Metrics/LineLength
72
+ route(
73
+ /#{LitaGithub::R::A_REG}org\s+?user\s+?add(?<org>\s+?[a-zA-Z0-9_\-]+)?(?<team>\s?[a-zA-Z0-9_\-]+)\s+?(?<username>[a-zA-Z0-9_\-]+)/,
74
+ :org_user_add,
75
+ command: true,
76
+ # confirmation: { allow_self: false },
77
+ help: {
78
+ 'gh org user add PagerDuty everyone theckman' => 'add the user theckman to the PagerDuty/everyone team -- this requires confirmation from another user. NOTE: This will add the member to the organization if they are not already!!',
79
+ 'gh org user add PagerDuty 42 theckman' => "same as above, except with the team's ID instead of the slug"
80
+ }
81
+ )
82
+
83
+ route(
84
+ /#{LitaGithub::R::A_REG}org\s+?user\s+?rm(?<org>\s+?[a-zA-Z0-9_\-]+)?(?<team>\s?[a-zA-Z0-9_\-]+)\s+?(?<username>[a-zA-Z0-9_\-]+)/,
85
+ :org_user_rm,
86
+ comamnd: true,
87
+ # confirmation: { allow_self: false },
88
+ help: {
89
+ 'gh org team rm PagerDuty everyone theckman' => 'remove the user theckman from the PagerDuty/everyone team, if this is their last team will remove them from the org. Requires confirmation from another user.',
90
+ 'gh org team rm PagerDuty 42 theckman' => "same as above, except with the team's ID instead of the slug"
91
+ }
92
+ )
93
+ # rubocop:enable Metrics/LineLength
94
+
95
+ route(
96
+ /#{LitaGithub::R::A_REG}org\s+?eject(?<org>\s+?[a-zA-Z0-9_\-]+)?(?<username>\s+?[a-zA-Z0-9_\-]+)/,
97
+ :org_eject_user,
98
+ command: true,
99
+ # confirmation: { allow_self: false },
100
+ help: {
101
+ 'gh org eject PagerDuty theckman' => 'forcibly removes the user from all groups in the organization -- ' \
102
+ 'this is meant for someone leaving the organization. Requires ' \
103
+ 'confirmation from another user.'
104
+ }
105
+ )
106
+
71
107
  def org_teams_list(response)
72
108
  md = response.match_data
73
109
  org = md[:org].nil? ? config.default_org : organization(md[:org].strip)
@@ -93,7 +129,7 @@ module Lita
93
129
  def org_team_add(response)
94
130
  return response.reply(t('method_disabled')) if func_disabled?(__method__)
95
131
 
96
- opts = e_opts_parse(response.message.body)
132
+ opts = opts_parse(response.message.body)
97
133
  vo = validate_team_add_opts(opts)
98
134
  return response.reply(vo[:msg]) unless vo[:success]
99
135
 
@@ -131,6 +167,101 @@ module Lita
131
167
  end
132
168
  end
133
169
 
170
+ # rubocop:disable Metrics/CyclomaticComplexity
171
+ # rubocop:disable Metrics/PerceivedComplexity
172
+ def org_user_add(response)
173
+ return response.reply(t('method_disabled')) if func_disabled?(__method__)
174
+
175
+ md = response.match_data
176
+ org, team, username = [organization(md['org'].strip), md['team'].strip, md['username']]
177
+
178
+ begin
179
+ user_id = octo.user(username)[:id]
180
+ rescue Octokit::NotFound
181
+ return response.reply(t('user_not_found', n: username))
182
+ end
183
+
184
+ return response.reply(t('nope')) if octo.user[:id] == user_id
185
+
186
+ begin
187
+ team_obj = octo.team(team_id(team, org))
188
+ rescue Octokit::NotFound
189
+ return response.reply(t('team_not_found', team: team))
190
+ end
191
+
192
+ begin
193
+ resp = octo.add_team_membership(team_obj[:id], username)
194
+ rescue StandardError => e
195
+ return response.reply(t('boom', m: e.message))
196
+ end
197
+
198
+ if resp
199
+ response.reply(t('org_user_add.added', u: username, o: org, t: team_obj[:name], s: team_obj[:slug]))
200
+ else
201
+ response.reply(t('org_user_add.failed', t: team_obj[:name]))
202
+ end
203
+ end
204
+
205
+ def org_user_rm(response)
206
+ return response.reply(t('method_disabled')) if func_disabled?(__method__)
207
+ md = response.match_data
208
+ org, team, username = [organization(md['org'].strip), md['team'].strip, md['username']]
209
+
210
+ begin
211
+ user_id = octo.user(username)[:id]
212
+ rescue Octokit::NotFound
213
+ return response.reply(t('user_not_found', n: username))
214
+ end
215
+
216
+ return response.reply(t('nope')) if octo.user[:id] == user_id
217
+
218
+ begin
219
+ team = octo.team(team_id(team, org))
220
+ rescue Octokit::NotFound
221
+ return response.reply(t('team_not_found', team: team))
222
+ end
223
+
224
+ begin
225
+ resp = octo.remove_team_member(team[:id], username)
226
+ rescue StandardError => e
227
+ return response.reply(t('boom', m: e.message))
228
+ end
229
+
230
+ if resp
231
+ response.reply(t('org_user_rm.removed', u: username, o: org, t: team[:name], s: team[:slug]))
232
+ else
233
+ response.reply(t('org_user_rm.failed'), t: team[:name])
234
+ end
235
+ end
236
+ # rubocop:enable Metrics/CyclomaticComplexity
237
+ # rubocop:enable Metrics/PerceivedComplexity
238
+
239
+ def org_eject_user(response)
240
+ return response.reply(t('method_disabled')) if func_disabled?(__method__)
241
+ md = response.match_data
242
+ org, username = [organization(md['org'].strip), md['username'].strip]
243
+
244
+ begin
245
+ user_id = octo.user(username)[:id]
246
+ rescue Octokit::NotFound
247
+ return response.reply(t('user_not_found', n: username))
248
+ end
249
+
250
+ return response.reply(t('nope')) if octo.user[:id] == user_id
251
+
252
+ begin
253
+ resp = octo.remove_organization_member(org, username)
254
+ rescue StandardError => e
255
+ return response.reply(t('boom', m: e.message))
256
+ end
257
+
258
+ if resp
259
+ response.reply(t('org_eject_user.ejected', user: username, org: org))
260
+ else
261
+ response.reply(t('org_eject_user.failed'))
262
+ end
263
+ end
264
+
134
265
  private
135
266
 
136
267
  def validate_team_add_opts(opts)
@@ -216,7 +216,7 @@ module Lita
216
216
  end
217
217
 
218
218
  def list_line(pr, full_name)
219
- t('pr_info.header', build_pr_header!({}, pr).merge(repo: full_name))
219
+ t('pr_info.header_long', build_pr_header!({}, pr).merge(repo: full_name, u: pr[:user][:login]))
220
220
  end
221
221
  end
222
222
 
@@ -17,7 +17,7 @@ en:
17
17
  company: "Company: %{c}\n"
18
18
  location: "Located: %{l}\n"
19
19
  account_info: "GitHub Admin: %{site_admin}, Repos: %{public_repos}, Gists: %{public_gists}\n"
20
- user_info: "Following: %{following}, Followers: %{followers}, Created: %{created_at}"
20
+ user_info: "Following: %{following}, Followers: %{followers}, Joined: %{created_at}"
21
21
  github_repo:
22
22
  method_disabled: "Sorry, this function has either been disabled or not enabled in the config"
23
23
  not_found: "That repo (%{org}/%{repo}) was not found"
@@ -55,7 +55,8 @@ en:
55
55
  exception: "An unexpected exception was hit during the GitHub API operation. Please make sure all arguments are proper and try again, or try checking the GitHub status (gh status)"
56
56
  not_found: "Pull request #%{pr} on %{org}/%{repo} not found"
57
57
  pr_info:
58
- header: "%{repo} #%{number}: %{title} :: %{url}\n"
58
+ header: "%{repo} #%{number}: '%{title}' :: %{url}\n"
59
+ header_long: "%{repo} #%{number}: '%{title}' opened by %{u} :: %{url}\n"
59
60
  status: "Opened By: %{user} | State: %{state_str} | Build: %{build_status}"
60
61
  merged: " | Merged By: %{merged_by}\n"
61
62
  mergeable: " | Mergeable: %{mergeable}\n"
@@ -71,6 +72,9 @@ en:
71
72
  method_disabled: "Sorry, this function has either been disabled or not enabled in the config"
72
73
  org_not_found: "The organization '%{org}' was not found. Does my user have ownership perms?"
73
74
  team_not_found: "Unable to match any teams based on: %{team}"
75
+ user_not_found: "Unable to find the GitHub user %{n}"
76
+ nope: "No...\n\nಠ_ಠ"
77
+ boom: "I had a problem :( ... %{m}"
74
78
  org_teams_list:
75
79
  header: "Showing %{num_teams} team(s) for %{org}:\n"
76
80
  team: "Name: %{name}, Slug: %{slug}, ID: %{id}, Perms: %{permission}\n"
@@ -82,3 +86,23 @@ en:
82
86
  org_team_rm:
83
87
  pass: "The '%{name}' team was deleted. Its ID was %{id}"
84
88
  fail: "Something went wrong trying to delete the '%{name}' team. Is Github having issues?"
89
+ org_eject_user:
90
+ ejected: "Ejected %{user} out of %{org}"
91
+ failed: "Failed to eject the user from the organization for an unknown reason"
92
+ org_user_rm:
93
+ removed: "%{u} has been removed from the '%{o}/%{t}' (%{s}) team"
94
+ failed: "Failed to remove the user from the '%{t}' team for some unknown reason"
95
+ org_user_add:
96
+ added: "%{u} has been added to the '%{o}/%{t}' (%{s}) team"
97
+ failed: "Failed to add the user to the '%{t}' team for some unknown reason"
98
+ github_issues:
99
+ repo_not_found: "That repo (%{repo}) was not found"
100
+ boom: "I had a problem :( ... %{m}"
101
+ issues_list:
102
+ none: "There are no open issues for %{r}"
103
+ header: "Showing %{n} issue(s) for %{r}\n"
104
+ item: "%{r} #%{number}: '%{title}' opened by %{u} :: %{html_url}\n"
105
+ val_states: "Issues can be one of the following states: 'open', 'closed', or 'all'\n"
106
+ val_sort: "Issues can be sorted by one of the following: 'created', 'updated', 'comments'\n"
107
+ val_direction: "Issues can be ordered either 'asc' (ascending) or 'desc' (descending)\n"
108
+ invalid_opts: "An invalid option was provided, here's the error from Octokit:\n%{m}"