module FerrisBueller::Replies

Public Instance Methods

reply_close(inc_num, params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 118
def reply_close inc_num, params
  return { response_type: 'in_channel' }, lambda do
    return { response_type: 'in_channel', text: "You're not allowed to do that" }, params[:response_url] unless allowed? params

    incident = select_incident inc_num
    return { response_type: 'in_channel', text: 'Could not list incidents' }, params[:response_url] unless incident

    resolution = close_incident incident
    return { response_type: 'in_channel', text: 'Could not close incident' }, params[:response_url] unless resolution

    return {
      response_type: 'in_channel',
      text: 'Closed incident',
      attachments: [
        attach_incident(incident)
      ]
    }, params[:response_url]
  end
end
reply_comment(inc_num, message, params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 166
def reply_comment inc_num, message, params
  incident = select_incident inc_num
  return { response_type: 'in_channel', text: 'Could not list incidents' } unless incident

  comment = construct_comment message, params
  annotation = comment_on_incident incident, comment
  return { response_type: 'in_channel', text: 'Could not comment on incident' } unless annotation

  {
    response_type: 'in_channel',
    text: 'Commented on incident',
    attachments: [
      attach_incident(incident)
    ]
  }
end
reply_dunno(params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 39
def reply_dunno params
  { text: "Invalid usage. Try the `help` command" }
end
reply_help(params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 34
def reply_help params
  { text: HELP_TEXT }
end
reply_list(params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 44
def reply_list params
  incidents = open_incidents
  return { text: 'Could not list incidents' } if incidents.nil?
  return { text: 'No open incidents at the moment' } if incidents.empty?

  attachments = incidents.map do |i|
    attach_incident(i)
  end
  {
    text: 'Found %d open incidents' % attachments.size,
    attachments: attachments
  }
end
reply_open(sev_num, summary, params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 139
def reply_open sev_num, summary, params
  return { response_type: 'in_channel' }, lambda do
    return { response_type: 'in_channel', text: "You're not allowed to do that" }, params[:response_url] unless allowed? params

    new_incident = construct_incident sev_num, summary, params
    incident = open_incident new_incident, summary
    return { response_type: 'in_channel', text: 'Could not open incident' }, params[:response_url] unless incident

    incident = new_incident.merge incident
    log.debug \
      event: 'created incident',
      incident: incident
    return {
      response_type: 'in_channel',
      text: 'Opened incident',
      attachments: [
        {
           title: incident[:key],
           title_link: File.join(settings.jira_url, 'browse', incident[:key]),
           text: incident[:fields][:summary]
        }
      ]
    }, params[:response_url]
  end
end
reply_resolve(inc_num, params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 96
def reply_resolve inc_num, params
  return { response_type: 'in_channel' }, lambda do
    return { response_type: 'in_channel', text: "You're not allowed to do that" }, params[:response_url]  unless allowed? params

    incident = select_incident inc_num
    return { response_type: 'in_channel', text: 'Could not list incidents' }, params[:response_url]  unless incident

    resolution = resolve_incident incident
    return { response_type: 'in_channel', text: 'Could not resolve incident' }, params[:response_url]  if resolution.nil?
    return { response_type: 'in_channel', text: 'Already resolved' } if resolution == false

    return {
      response_type: 'in_channel',
      text: 'Resolved incident',
      attachments: [
        attach_incident(incident)
      ]
    }, params[:response_url]
  end
end
reply_show(inc_num, params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 83
def reply_show inc_num, params
  incident = select_incident inc_num
  return { text: 'Could not list incidents' } unless incident

  {
    text: 'Incident info',
    attachments: [
      attach_incident(incident, true)
    ]
  }
end
reply_summary(params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 59
def reply_summary params
  incidents = recent_incidents
  return { response_type: 'in_channel', text: 'Could not list incidents' } if incidents.nil?
  return { response_type: 'in_channel', text: 'No recent incidents' } if incidents.empty?

  incidents = incidents.sort { |i| i[:id].to_i }.reverse

  partitioned_incidents = []
  severities = incidents.map { |i| i[:fields][:customfield_11250][:value] }.sort.uniq
  severities.each do |severity|
    partitioned_incidents += incidents.select { |i| i[:fields][:customfield_11250][:value] == severity }
  end

  attachments = partitioned_incidents.map do |i|
    attach_incident i
  end
  {
    response_type: 'in_channel',
    text: 'Found %d recent incidents' % attachments.size,
    attachments: attachments
  }
end
reply_whoami(params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 9
def reply_whoami params
  u = user_lookup(params)
  if u
    { text: "You're <@#{params[:user_id]}>",
      attachments: [
        {
          title: 'Slack User',
          text: "```#{JSON.pretty_generate(u[:slack])}```",
          mrkdwn_in: %w[ text pretext ]
        },
        {
          title: 'Jira User',
          text: "```#{JSON.pretty_generate(u[:jira])}```",
          mrkdwn_in: %w[ text pretext ]
        }
      ]
    }
  else
    {
      text: "You're <@#{params[:user_id]}>, but I can't say much more than that"
    }
  end
end

Private Instance Methods

allowed?(params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 397
def allowed? params
  u = user_lookup params
  u && store[:jira_members] \
    && store[:jira_members].include?(u[:jira][:nick])
end
attach_incident(i, detailed=false) click to toggle source
# File lib/ferris-bueller/replies.rb, line 187
def attach_incident i, detailed=false
  severity_colors = {
    '1' => '#e60000',
    '2' => '#e60000',
    '3' => '#ff6600',
    '4' => '#ffff00',
    '5' => '#ccff33'
  }
  severity = i[:fields][:customfield_11250][:value] rescue 'Unknown'
  severity_color = severity_colors[severity[3]] rescue '#cccccc'

  additional_fields = if detailed
    [
      {
        title: 'Description',
        value: i[:fields][:description]
      }
    ]
  end

  {
    title: i[:key],
    title_link: File.join(settings.jira_url, 'browse', i[:key]),
    text: "*#{severity}*\n_#{i[:fields][:summary]}_",
    fields: [
      {
        title: 'Created',
        value: (DateTime.parse(i[:fields][:created]).iso8601 rescue nil),
        short: true
      },
      {
        title: 'Status',
        value: (i[:fields][:status][:name] rescue nil),
        short: true
      },
      {
        title: 'Updated',
        value: (DateTime.parse(i[:fields][:updated]).iso8601 rescue nil),
        short: true
      },
      *additional_fields
    ].reject { |f| f[:value].nil? || f[:value].empty? || f[:value] == 'Unknown' },
    color: severity_color,
    mrkdwn_in: %w[ text pretext ]
  }
end
close_incident(i) click to toggle source
# File lib/ferris-bueller/replies.rb, line 264
def close_incident i
  status = normalize_value i[:fields][:status]
  return false if status =~ CLOSED_STATE

  log.debug \
    event: 'closing incident',
    incident: i

  CLOSED_TRANSITIONS.map do |tid|
    Thread.new do
      jira.send "/issue/#{i[:key]}/transitions?expand=transitions.fields", \
        transition: { id: tid }
    end
  end.join

  sleep 1.5 * settings.refresh_rate

  incident = select_incident i[:key].split('-',2).last
  status   = normalize_value incident[:fields][:status]

  log.debug \
    event: 'transitioned incident for close',
    incident: i,
    status: status

  return incident if status =~ CLOSED_STATE
end
comment_on_incident(i, c) click to toggle source
# File lib/ferris-bueller/replies.rb, line 330
def comment_on_incident i, c
  return unless i
  return unless c
  resp = jira.send "/issue/#{i[:key]}/comment", c
  return unless resp.include? :id
  log.info \
    event: 'comment on incident',
    incident: i,
    comment: c
  resp
end
construct_comment(message, params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 321
def construct_comment message, params
  u = user_lookup params
  return unless u
  {
    body: '_[~%s]_ says: %s' % [ u[:jira][:nick], message ]
  }
end
construct_incident(sev_num, summary, params) click to toggle source
# File lib/ferris-bueller/replies.rb, line 293
def construct_incident sev_num, summary, params
  u = user_lookup params
  return unless u
  {
    fields: {
      project: { key: settings.jira_project },
      issuetype: { name: settings.jira_type },
      reporter: { name: u[:jira][:nick] },
      summary: summary,
      SHOW_FIELDS.key('Severity') => {
        id: SEVERITIES[sev_num.to_i]
      }
    }
  }
end
friendly_date(val) click to toggle source
# File lib/ferris-bueller/replies.rb, line 387
def friendly_date val
  Time.parse(val).strftime('%Y-%m-%d %H:%M %Z')
end
normalize_date(val) click to toggle source
# File lib/ferris-bueller/replies.rb, line 382
def normalize_date val
  Time.parse(val).utc.iso8601(0).sub(/Z$/, 'UTC')
end
normalize_value(val) click to toggle source
# File lib/ferris-bueller/replies.rb, line 368
def normalize_value val
  case val
  when Hash
    val[:name] || val[:value] || val
  when Array
    val.map { |v| v[:value] }.join(', ')
  when /^\d{4}\-\d{2}\-\d{2}/
    '%s (%s)' % [ val, normalize_date(val) ]
  else
    val
  end
end
one_day() click to toggle source
# File lib/ferris-bueller/replies.rb, line 392
def one_day
  24 * 60 * 60 # seconds/day
end
open_incident(i, summary) click to toggle source
# File lib/ferris-bueller/replies.rb, line 310
def open_incident i, summary
  return unless i
  incident = jira.send 'issue', i
  return unless incident.include? :key
  log.info \
    event: 'opened incident',
    incident: incident
  incident
end
open_incidents() click to toggle source
# File lib/ferris-bueller/replies.rb, line 351
def open_incidents
  return if store[:jira_incidents].nil?
  store[:jira_incidents].select do |i|
    status = normalize_value i[:fields][:status]
    !(status =~ /resolved|closed/i)
  end
end
recent_incidents() click to toggle source
# File lib/ferris-bueller/replies.rb, line 343
def recent_incidents
  return if store[:jira_incidents].nil?
  store[:jira_incidents].select do |i|
    Time.now - Time.parse(i[:fields][:created]) < one_day
  end
end
resolve_incident(i) click to toggle source
# File lib/ferris-bueller/replies.rb, line 235
def resolve_incident i
  status = normalize_value i[:fields][:status]
  return false if status =~ RESOLVED_STATE

  log.debug \
    event: 'resolving incident',
    incident: i

  RESOLVED_TRANSITIONS.map do |tid|
    Thread.new do
      jira.send "/issue/#{i[:key]}/transitions?expand=transitions.fields", \
        transition: { id: tid }
    end
  end.join

  sleep 1.5 * settings.refresh_rate

  incident = select_incident i[:key].split('-',2).last
  status   = normalize_value incident[:fields][:status]

  log.debug \
    event: 'transitioned incident for resolve',
    incident: i,
    status: status

  return incident if status =~ RESOLVED_STATE
end
select_incident(num) click to toggle source
# File lib/ferris-bueller/replies.rb, line 360
def select_incident num
  return if store[:jira_incidents].nil?
  store[:jira_incidents].select do |i|
    i[:key] =~ /-#{num}$/
  end.shift
end