class BenderBot
Constants
- JARO
Public Class Methods
set_commands(without)
click to toggle source
# File lib/bender/bot.rb, line 39 def self.set_commands without commands = { help: { desc: 'Display this help text', }, list: { cmd: '', desc: 'List open incidents' }, show: { cmd: '', fmt: 'INCIDENT_NUMBER', desc: 'Display incident details' }, resolve: { fmt: 'INCIDENT_NUMBER', desc: 'Resolve an incident', }, close: { fmt: 'INCIDENT_NUMBER', desc: 'Close an incident', }, open: { fmt: "#{SEVERITY_FIELD.upcase}=#{SEVERITIES.keys.sort_by(&:to_i).join(',')} SUMMARY_TEXT", desc: 'Open a new incident', }, summary: { desc: 'Summarize incidents from past 24 hours (open or closed)' }, comment: { fmt: 'INCIDENT_NUMBER COMMENT_TEXT', desc: 'Add a comment to an incident' } } without.each { |wo| commands.delete wo.to_sym } BenderBot.const_set :COMMANDS, commands end
Public Instance Methods
handle(room, sender, message)
click to toggle source
# File lib/bender/bot.rb, line 117 def handle room, sender, message refresh_room room @room = room @sender = sender @message = message severities = Hash.new { |h,k| h[k] = [] } case message.strip when /^\/#{config[:jira_user]}$/ reply_html QUOTES.sample(1).first, :red when /^\/whoami$/ u = user_where name: sender if u m = '<b>%{nick}</b>: %{name} (<a href="mailto:%{email}">%{email}</a>)' % u reply_html m, :purple else reply_html "Couldn't find the associated user in JIRA", :red end when /^\/lookup\s+(.+)$/ u = user_where(name: $1) || user_where(nick: $1) if u m = '<b>%{nick}</b>: %{name} (<a href="mailto:%{email}">%{email}</a>)' % u reply_html m, :purple else reply_html "Couldn't find the associated user in JIRA", :red end # /prefix help - This help text when /^(\?#{prefix}|\/#{prefix}\s+help)$/ reply_with_help # /prefix - List open incidents when /^\/#{prefix}$/ refresh_incidents is = store['incidents'].reverse.map do |i| status = normalize_value i['fields']['status'] unless status =~ /resolved|closed/i '%s (%s - %s) [%s]: %s' % [ incident_link(i), short_severity(sev_for(i)), normalize_value(i['fields']['status']), friendly_date(i['fields']['created']), i['fields']['summary'] ] end end.compact.join('<br />') if is.empty? reply_html 'Good news everyone! No open incidents at the moment', :green else reply_html is end # /prefix summary - Summarize recent incidents when /^\/#{prefix}\s+summary$/ refresh_incidents statuses = Hash.new { |h,k| h[k] = 0 } store['incidents'].reverse.each do |i| if recent_incident? i status = normalize_value(i['fields']['status']) repr = '%s (%s) [%s]: %s' % [ incident_link(i), status, friendly_date(i['fields']['created']), i['fields']['summary'] ] severities[sev_for(i)] << repr statuses[status] += 1 end end summary = [] summary << 'By Status:' statuses.each do |status, size| summary << '%s: %d incident(s)' % [ status, size ] end summary << '' summary << "By #{SEVERITY_FIELD.capitalize}:" severities.keys.sort.each do |severity| summary << '%s: %d incident(s)' % [ short_severity(severity), severities[severity].size ] end if severities.empty? reply_html 'Good news everyone! No open incidents at the moment', :green else is = severities.keys.sort.map do |sev| "%s:<br />%s" % [ sev, severities[sev].join("<br />") ] end.join("<br /><br />") reply_html(summary.join("<br />") + "<br /><br />" + is) end # /prefix NUM - Show incident details when /^\/#{prefix}\s+(\d+)$/ incident = select_incident $1 if incident.nil? reply_html 'Sorry, no such incident!', :red else fields = SHOW_FIELDS.keys - %w[ summary ] i = fields.map do |f| val = incident['fields'][f] if val key = SHOW_FIELDS[f] val = normalize_value val '%s: %s' % [ key, val ] end end.compact reply_html "%s - %s<br />%s" % [ incident_link(incident), incident['fields']['summary'], i.join("<br />") ] end # /prefix resolve NUM - Resolve an incident when /^\/#{prefix}\s+resolve\s+(\d+)$/ unless allowed? reply_html "You're not allowed to do that!", :red else incident = select_incident $1 if incident reply_html(*resolve_incident(incident)) else reply_html 'Sorry, no such incident!', :red end end # /prefix close NUM - Close an incident when /^\/#{prefix}\s+close\s+(\d+)$/ unless allowed? reply_html "You're not allowed to do that!", :red else incident = select_incident $1 if incident reply_html(*close_incident(incident)) else reply_html 'Sorry, no such incident!', :red end end # /prefix open SEVERITY SUMMARY - File a new incident when /^\/#{prefix}\s+open\s+(severity|sev|s|p)?(\d+)\s+(.*)/im unless allowed? reply_html "You're not allowed to do that!", :red else user = user_where name: sender data = { fields: { project: { key: config[:jira_project] }, issuetype: { name: config[:jira_type] }, reporter: { name: user[:nick] }, summary: $3, SEVERITY_FIELD => { id: SEVERITIES[$2.to_i] } } } reply_html(*file_incident(data)) end # /prefix comment [INCIDENT_NUMBER] [COMMENT_TEXT] when /^\/#{prefix}\s+comment\s+(\d+)\s+(.*)/im incident = select_incident $1 comment = $2 user = user_where name: sender if !user reply_html "Couldn't find the associated user in JIRA", :red elsif incident reply_html(*comment_on_incident(incident, comment, user)) else reply_html 'Sorry, no such incident!', :red end when /^\/#{prefix}/i reply_html 'Invalid usage', :red reply_with_help end return true end
refresh_room(jid)
click to toggle source
# File lib/bender/bot.rb, line 105 def refresh_room jid begin @this_room = @@rooms.select { |r| r.xmpp_jid == jid }.first @@hipchat[@this_room.name].get_room # test rescue @@hipchat = HipChat::Client.new(@@config[:hipchat_token]) @@rooms = @@hipchat.rooms @this_room = @@rooms.select { |r| r.xmpp_jid == jid }.first end end
reply_html(message, color=:yellow)
click to toggle source
# File lib/bender/bot.rb, line 78 def reply_html message, color=:yellow tries ||= 3 @@hipchat[@this_room.name].send(nick, message, color: color) rescue tries -= 1 if tries > 0 refresh_room @this_room.xmpp_jid log.warn reason: 'could not reply_html', remediation: 'retrying' retry end log.error reason: 'could not reply_html' end
reply_with_help()
click to toggle source
# File lib/bender/bot.rb, line 92 def reply_with_help help = COMMANDS.each.map do |cmd, opts| opts[:cmd] ||= cmd opts[:cmd] = opts[:cmd].to_s.insert(0, ' ') if opts[:cmd] opts[:fmt] ||= '' opts[:fmt] = opts[:fmt].to_s.insert(0, ' ') if opts[:fmt] "<code>/#{prefix}%{cmd}%{fmt}</code> - %{desc}" % opts end.join('<br />') reply_html help end
Private Instance Methods
allowed?()
click to toggle source
# File lib/bender/bot.rb, line 496 def allowed? user = user_where name: @sender user && store['group'].include?(user[:name]) end
close_incident(incident)
click to toggle source
# File lib/bender/bot.rb, line 432 def close_incident incident status = normalize_value incident['fields']['status'] if status =~ CLOSED_STATE return [ "#{incident_link(incident)} is already closed!", :green ] end req_path = '/rest/api/2/issue/%s/transitions?expand=transitions.fields' % [ incident['key'] ] uri = URI(config[:jira_site] + req_path) http = Net::HTTP.new uri.hostname, uri.port req = Net::HTTP::Post.new uri req.basic_auth config[:jira_user], config[:jira_pass] req['Content-Type'] = 'application/json' req['Accept'] = 'application/json' CLOSED_TRANSITIONS.each do |tid| req.body = { transition: { id: tid } }.to_json http.request req end incident = select_incident incident['key'].split('-',2).last status = normalize_value incident['fields']['status'] if status =~ CLOSED_STATE [ 'Closed ' + incident_link(incident), :green ] else [ "Failed to close #{incident_link(incident)} automatically, you might try yourself", :red ] end end
comment_on_incident(incident, comment, user)
click to toggle source
# File lib/bender/bot.rb, line 473 def comment_on_incident incident, comment, user req_path = '/rest/api/2/issue/%s/comment' % incident['key'] uri = URI(config[:jira_site] + req_path) http = Net::HTTP.new uri.hostname, uri.port req = Net::HTTP::Post.new uri req.basic_auth config[:jira_user], config[:jira_pass] req['Content-Type'] = 'application/json' req['Accept'] = 'application/json' req.body = { body: '_[~%s]_ says: %s' % [ user[:nick], comment ] }.to_json case http.request(req) when Net::HTTPCreated [ 'Added comment to ' + incident_link(incident), :green ] else [ 'Sorry, I had trouble adding your comment on' + incident_link(incident), :red ] end end
compare(name1, name2)
click to toggle source
# File lib/bender/bot.rb, line 354 def compare name1, name2 n1 = name1.gsub(/\W/, '').downcase n2 = name2.gsub(/\W/, '').downcase JARO.getDistance n1, n2 end
config()
click to toggle source
# File lib/bender/bot.rb, line 328 def config ; @@config end
file_incident(data)
click to toggle source
# File lib/bender/bot.rb, line 362 def file_incident data req_path = '/rest/api/2/issue' uri = URI(config[:jira_site] + req_path) http = Net::HTTP.new uri.hostname, uri.port req = Net::HTTP::Post.new uri req.basic_auth config[:jira_user], config[:jira_pass] req['Content-Type'] = 'application/json' req['Accept'] = 'application/json' req.body = data.to_json resp = http.request req issue = JSON.parse(resp.body) if issue.has_key? 'key' [ 'Filed ' + incident_link(issue), :green ] else log.error \ error: 'Could not file ticket', reason: 'Invalid response', request: req.inspect, response: issue [ "Sorry, I couldn't file that!", :red ] end end
log()
click to toggle source
# File lib/bender/bot.rb, line 326 def log ; @@logger end
prefix()
click to toggle source
# File lib/bender/bot.rb, line 502 def prefix ; config[:prefix] end
resolve_incident(incident)
click to toggle source
# File lib/bender/bot.rb, line 390 def resolve_incident incident status = normalize_value incident['fields']['status'] if status =~ RESOLVED_STATE return [ "#{incident_link(incident)} is already resolved!", :green ] end req_path = '/rest/api/2/issue/%s/transitions?expand=transitions.fields' % [ incident['key'] ] uri = URI(config[:jira_site] + req_path) http = Net::HTTP.new uri.hostname, uri.port req = Net::HTTP::Post.new uri req.basic_auth config[:jira_user], config[:jira_pass] req['Content-Type'] = 'application/json' req['Accept'] = 'application/json' RESOLVED_TRANSITIONS.each do |tid| req.body = { transition: { id: tid } }.to_json http.request req end incident = select_incident incident['key'].split('-',2).last status = normalize_value incident['fields']['status'] if status =~ RESOLVED_STATE [ 'Resolved ' + incident_link(incident), :green ] else [ "Failed to resolve #{incident_link(incident)} automatically, you might try yourself", :red ] end end
sev_for(i)
click to toggle source
# File lib/bender/bot.rb, line 505 def sev_for i sev = if i['fields'][SEVERITY_FIELD] i['fields'][SEVERITY_FIELD]['value'] || i['fields'][SEVERITY_FIELD]['name'] end sev.nil? ? '??' : sev end
user_where(fields, threshold=0.85)
click to toggle source
# File lib/bender/bot.rb, line 330 def user_where fields, threshold=0.85 field, value = fields.to_a.shift suggested_user = store['users'].values.sort_by do |u| compare value, u[field] end.last distance = compare value, suggested_user[field] user = distance < threshold ? nil : suggested_user log.debug \ action: 'user_where', fields: fields, threshold: threshold, field: field, value: value, suggested_user: suggested_user, distance: distance, user: user return user end