lita-github 0.0.13 → 0.0.14

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: 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}"