lemurheavy / coveralls-public

The public issue tracker for coveralls.io
http://coveralls.io
124 stars 7 forks source link

Webhook not working for Slack notifications #299

Closed shedd closed 9 years ago

shedd commented 10 years ago

I tried to configure a webhook for https://slack.com using their webhook integration instructions, but it's returning a 500 server error.

Slack is great - would love to see it supported for notifications.

sheluchin commented 10 years ago

+1

pjbull commented 10 years ago

+1

paradoxxjm commented 10 years ago

+1

emilenriquez commented 10 years ago

same here

isms commented 10 years ago

+1

chanko commented 10 years ago

+1

shuber commented 10 years ago

Yes, please integrate slack notifications!

fertapric commented 9 years ago

+1

fertapric commented 9 years ago

I got tired of waiting for a response, so I created my own coveralls:

Gemfile

gem 'simplecov', '~> 0.9.1', require: false, group: :test

.simplecov

require 'simplecov'
require 'json'

class SimpleCov::Formatter::SlackFormatter

  ERROR_THRESHOLD = 50.0
  WARNING_THRESHOLD = 90.0
  LINE_NUMBER_WIDTH = 5

  def format(result)
    payload = slack_payload(result)
    notify_slack(payload)

    puts 'Coverage reported to Slack'
  end

  private

  def local_filepath(filepath)
    filepath.to_s.gsub(Rails.root.to_s, '')
  end

  def notify_slack(payload)
    uri = URI.parse(slack_url)
    http = Net::HTTP.new(uri.host, uri.port)

    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER

    request = Net::HTTP::Post.new(uri.request_uri)
    request.set_form_data(payload: payload.to_json)
    response = http.request(request)
  rescue SocketError => e
    puts "#{ e.message } or slack may be down"
  end

  def slack_url
    domain = "#{ ENV['SLACK_TEAM'] }.slack.com"
    url = "https://#{ domain }/services/hooks/incoming-webhook"
    params = "token=#{ ENV['SLACK_TOKEN'] }"

    "#{ url }?#{ params }"
  end

  def slack_payload(simple_cov_result)
    covered_percent = simple_cov_result.covered_percent.round(2)
    covered_lines = simple_cov_result.covered_lines
    total_lines = simple_cov_result.total_lines

    {
      channel: ENV['SLACK_CHANNEL'],
      username: 'Simplecov',
      icon_emoji: ':mag:',
      text: "#{ covered_lines }/#{ total_lines } lines (#{ covered_percent }%)",
      attachments: slack_attachments(simple_cov_result)
    }
  end

  def slack_attachments(simple_cov_result)
    attachments = []

    simple_cov_result.files.each do |file|
      attachments << slack_attachment(file) if file.covered_percent < 100
    end

    attachments
  end

  def slack_attachment(simple_cov_file_result)
    covered_percent = simple_cov_file_result.covered_percent.round(2)
    covered_lines = simple_cov_file_result.covered_lines.count
    total_lines = simple_cov_file_result.lines_of_code
    missed_lines = simple_cov_file_result.missed_lines.map do |missed_line|
      "#{ line_number(missed_line.line_number) }.#{ missed_line.source }"
    end

    {
      color: color_for_covered_percent(covered_percent),
      mrkdwn_in: ['fields'],
      fields: [
        {
          title: 'File path',
          value: local_filepath(simple_cov_file_result.filename),
          short: false
        },
        {
          title: 'Covered percent',
          value: "#{ covered_percent }%",
          short: true
        },
        {
          title: 'Covered lines',
          value: "#{ covered_lines }/#{ total_lines }",
          short: true
        },
        {
          title: 'Missed lines',
          value: "```\n#{ missed_lines.join('') }\n```",
          short: false
        },
      ]
    }
  end

  def line_number(line_number)
    "#{ ' ' * (LINE_NUMBER_WIDTH - line_number.to_s.length)}#{ line_number }"
  end

  def color_for_covered_percent(covered_percent)
    if covered_percent > ERROR_THRESHOLD
      covered_percent > WARNING_THRESHOLD ? 'good' : 'warning'
    else
      'danger'
    end
  end

end

%w{ SLACK_TEAM SLACK_TOKEN SLACK_CHANNEL }.each do |env|
  raise "Please provide #{ env } environment variable" unless ENV[env]
end

SimpleCov.start 'rails'
SimpleCov.formatter = SimpleCov::Formatter::SlackFormatter

At the beginning of your test_helper.rb or spec_helper

require 'simplecov' if ENV['TEST_COVERAGE']

In your CI service (travis-ci, codeship): TEST_COVERAGE=true bundle exec rake test or TEST_COVERAGE=true bundle exec rspec

Results look like these: screenshot 2014-10-22 22 13 22

Hope you like it!

nickel commented 9 years ago

@fertapric really cool!

fertapric commented 9 years ago

Thanks @nickel

spejman commented 9 years ago

Cool @fertapric !

fertapric commented 9 years ago

Thanks @spejman

fertapric commented 9 years ago

Bonus: BadgeFormatter

Creates a badge and uploads it to S3 so you can integrate it on your README

...

class SimpleCov::Formatter::SlackFormatter
  ...
end

class SimpleCov::Formatter::BadgeFormatter

  BRIGHT_GREEN_THRESHOLD = 100.0
  GREEN_THRESHOLD = 90.0
  YELLOW_GREEN_THRESHOLD = 80.0
  YELLOW_THRESHOLD = 70.0
  ORANGE_THRESHOLD = 60.0

  def format(simple_cov_result)
    covered_percent = simple_cov_result.covered_percent.round(2)
    badge = svg_for_covered_percent(covered_percent)

    upload_badge_to_s3(badge)

    puts 'Coverage badge uploaded to AWS S3'
  end

  private

  def svg_for_covered_percent(covered_percent)
    color = get_color_from_covered_percent(covered_percent)

    "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"128\" height=\"20\">
      <linearGradient id=\"a\" x2=\"0\" y2=\"100%\">
        <stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>
        <stop offset=\"1\" stop-opacity=\".1\"/>
      </linearGradient>
      <rect rx=\"3\" width=\"128\" height=\"20\" fill=\"#555\"/>
      <rect rx=\"3\" x=\"63\" width=\"65\" height=\"20\" fill=\"#{ color }\"/>
      <path fill=\"#{ color }\" d=\"M63 0h4v20h-4z\"/>
      <rect rx=\"3\" width=\"128\" height=\"20\" fill=\"url(#a)\"/>
      <g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">
        <text x=\"32.5\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">coverage</text>
        <text x=\"32.5\" y=\"14\">coverage</text>
        <text x=\"94.5\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">#{ covered_percent } %</text>
        <text x=\"94.5\" y=\"14\">#{ covered_percent } %</text>
      </g>
    </svg>"
  end

  def get_color_from_covered_percent(covered_percent)
    color = '#9f9f9f'

    if covered_percent >= BRIGHT_GREEN_THRESHOLD
      color = '#4c1'
    elsif covered_percent >= GREEN_THRESHOLD
      color = '#97ca00'
    elsif covered_percent >= YELLOW_GREEN_THRESHOLD
      color = '#a4a61d'
    elsif covered_percent >= YELLOW_THRESHOLD
      color = '#dfb317'
    elsif covered_percent >= ORANGE_THRESHOLD
      color = '#fe7d37'
    else
      color = '#e05d44'
    end

    color
  end

  def upload_badge_to_s3(badge)
    options = {
      acl: :public_read,
      content_type: 'image/svg+xml',
      cache_control: 'no-cache, no-store, must-revalidate',
      expires: Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S GMT')
    }

    s3_badge.write(badge, options)
  end

  def s3_badge
    app_bucket.objects['monitoring/coverage/app.svg']
  end

  def app_bucket
    s3_client.buckets['app']
  end

  def s3_client
    @s3_client ||= AWS::S3.new(
      access_key_id: ENV['AWS_ACCESS_KEY_ID'],
      secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
    )
  end

end

%w{
  SLACK_TEAM
  SLACK_TOKEN
  AWS_ACCESS_KEY_ID
  AWS_SECRET_ACCESS_KEY
}.each do |env|
  raise "Please provide #{ env } environment variable" unless ENV[env]
end

SimpleCov.start 'rails'
SimpleCov.formatters = [
  SimpleCov::Formatter::SlackFormatter,
  SimpleCov::Formatter::BadgeFormatter
]

And the results look like:

screenshot 2014-10-24 10 36 12

I'm using the flat version, feel free to get your own version at http://shields.io/

Hope you like it!

tobiasboyd commented 9 years ago

Any chance this will be baked into Coveralls itself?

blueplanet commented 9 years ago

+1

nickmerwin commented 9 years ago

Hi everyone, Slack is now an officially supported notification!

image

Thank you for your patience. Cheers!

blueplanet commented 9 years ago

Good job!!! :+1:

pjbull commented 9 years ago

:+1: Thanks!

iwasrobbed commented 8 years ago

@nickmerwin Getting an error when I try testing to the Slack-provided webhook: Error: wrong number of arguments (1 for 0)

partydrone commented 8 years ago

Same here: Error: wrong number of arguments (1 for 0).

partydrone commented 8 years ago

Also, for the notification boxes, it's unclear whether you mean coverage changes by a certain percent, or above/below a certain percent. I'm guessing the latter.…

ObjectiveTruth commented 8 years ago

@iwasrobbed same here, Error: wrong number of arguments (1 for 0)

ogreface commented 8 years ago

@nickmerwin Same issue as others.

Error: wrong number of arguments (1 for 0)

analytically commented 8 years ago

+1