Hello community,

here is the log from the commit of package rubygem-exception_notification for 
openSUSE:Factory checked in at 2017-09-04 12:36:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rubygem-exception_notification (Old)
 and      /work/SRC/openSUSE:Factory/.rubygem-exception_notification.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rubygem-exception_notification"

Mon Sep  4 12:36:54 2017 rev:17 rq:520442 version:4.2.2

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/rubygem-exception_notification/rubygem-exception_notification.changes
    2016-07-30 00:27:30.000000000 +0200
+++ 
/work/SRC/openSUSE:Factory/.rubygem-exception_notification.new/rubygem-exception_notification.changes
       2017-09-04 12:36:55.882372182 +0200
@@ -1,0 +2,13 @@
+Sun Sep  3 09:00:32 UTC 2017 - [email protected]
+
+- updated to version 4.2.2
+ see installed CHANGELOG.rdoc
+
+  == 4.2.2
+  
+  * enhancements
+    * Error groupiong (by @Martin91)
+    * Additional fields for Slack support (by @schurig)
+    * Enterprise HipChat support (by @seanhuber)
+
+-------------------------------------------------------------------

Old:
----
  exception_notification-4.2.1.gem

New:
----
  exception_notification-4.2.2.gem

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rubygem-exception_notification.spec ++++++
--- /var/tmp/diff_new_pack.ORpWOh/_old  2017-09-04 12:36:56.786245015 +0200
+++ /var/tmp/diff_new_pack.ORpWOh/_new  2017-09-04 12:36:56.786245015 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package rubygem-exception_notification
 #
-# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -24,7 +24,7 @@
 #
 
 Name:           rubygem-exception_notification
-Version:        4.2.1
+Version:        4.2.2
 Release:        0
 %define mod_name exception_notification
 %define mod_full_name %{mod_name}-%{version}

++++++ exception_notification-4.2.1.gem -> exception_notification-4.2.2.gem 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/CHANGELOG.rdoc new/CHANGELOG.rdoc
--- old/CHANGELOG.rdoc  2016-07-18 01:26:10.000000000 +0200
+++ new/CHANGELOG.rdoc  2017-08-12 22:52:29.000000000 +0200
@@ -1,3 +1,10 @@
+== 4.2.2
+
+* enhancements
+  * Error groupiong (by @Martin91)
+  * Additional fields for Slack support (by @schurig)
+  * Enterprise HipChat support (by @seanhuber)
+
 == 4.2.1
 
 * enhancements
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/CONTRIBUTING.md new/CONTRIBUTING.md
--- old/CONTRIBUTING.md 2016-07-18 01:26:10.000000000 +0200
+++ new/CONTRIBUTING.md 2017-08-12 22:52:29.000000000 +0200
@@ -30,8 +30,9 @@
   bundle
   cd test/dummy
   bundle
-  bundle exec rake db:reset
-  bundle exec rake db:test:prepare
+  bundle exec rake db:reset db:test:prepare
+  cd ../..
+  bundle exec rake test
   ```
 * Create a topic branch from where you want to base your work.
 * Add a test for your change. Only refactoring and documentation changes
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/README.md new/README.md
--- old/README.md       2016-07-18 01:26:10.000000000 +0200
+++ new/README.md       2017-08-12 22:52:29.000000000 +0200
@@ -82,7 +82,7 @@
 
 ## Notifiers
 
-ExceptionNotification relies on notifiers to deliver notifications when errors 
occur in your applications. By default, six notifiers are available:
+ExceptionNotification relies on notifiers to deliver notifications when errors 
occur in your applications. By default, 7 notifiers are available:
 
 * [Campfire notifier](#campfire-notifier)
 * [Email notifier](#email-notifier)
@@ -279,6 +279,12 @@
 
 If enabled, remove numbers from subject so they thread as a single one. Use 
`:normalize_subject => true` to enable it.
 
+##### include_controller_and_action_names_in_subject
+
+*Boolean, default: true*
+
+If enabled, include the controller and action names in the subject. Use 
`:include_controller_and_action_names_in_subject => false` to exclude them.
+
 ##### email_format
 
 *Symbol, default: :text*
@@ -393,6 +399,12 @@
 
 Message will appear from this nickname. Default : 'Exception'.
 
+##### server_url
+
+*String, optional*
+
+Custom Server URL for self-hosted, Enterprise HipChat Server
+
 For all options & possible values see [Hipchat 
API](https://www.hipchat.com/docs/api/method/rooms/message).
 
 ### IRC notifier
@@ -590,6 +602,12 @@
 
 Contains additional payload for a message (e.g avatar, attachments, etc). See 
[slack-notifier](https://github.com/stevenosloan/slack-notifier#additional-parameters)
 for more information.. Default: '{}'
 
+##### additional_fields
+
+*Array of Hashes, optional*
+
+Contains additional fields that will be added to the attachement. See [Slack 
documentation](https://api.slack.com/docs/message-attachments).
+
 ## Mattermost notifier
 
 Post notification in a mattermost channel via [incoming 
webhook](http://docs.mattermost.com/developer/webhooks-incoming.html)
@@ -600,7 +618,7 @@
 gem 'httparty'
 ```
 
-To configure it, you **need** to set the `webhook_url` option.  
+To configure it, you **need** to set the `webhook_url` option.
 You can also specify an other channel with `channel` option.
 
 ```ruby
@@ -616,7 +634,7 @@
   }
 ```
 
-If you are using Github or Gitlab for issues tracking, you can specify 
`git_url` as follow to add a *Create issue* link in you notification.  
+If you are using Github or Gitlab for issues tracking, you can specify 
`git_url` as follow to add a *Create issue* link in you notification.
 By default this will use your Rails application name to match the git 
repository. If yours differ you can specify `app_name`.
 
 
@@ -810,6 +828,33 @@
   }
 ```
 
+## Error Grouping
+In general, exception notification will send every notification when an error 
occured, which may result in a problem: if your site has a high throughput and 
an same error raised frequently, you will receive too many notifications during 
a short period time, your mail box may be full of thousands of exception mails 
or even your mail server will be slow. To prevent this, you can choose to error 
errors by using `:error_grouping` option and set it to `true`.
+
+Error grouping has a default formula `log2(errors_count)` to determine if it 
is needed to send the notification based on the accumulated errors count for 
specified exception, this makes the notifier only send notification when count 
is: 1, 2, 4, 8, 16, 32, 64, 128, ... (2**n). You can use 
`:notification_trigger` to override this default formula.
+
+The below shows options used to enable error grouping:
+
+```ruby
+Rails.application.config.middleware.use ExceptionNotification::Rack,
+  :ignore_exceptions => ['ActionView::TemplateError'] + 
ExceptionNotifier.ignored_exceptions,
+  :email => {
+    :email_prefix         => "[PREFIX] ",
+    :sender_address       => %{"notifier" <[email protected]>},
+    :exception_recipients => %w{[email protected]}
+  },
+  :error_grouping => true,
+  # :error_grouping_period => 5.minutes,    # the time before an error is 
regarded as fixed
+  # :error_grouping_cache => Rails.cache,   # for other applications such as 
Sinatra, use one instance of ActiveSupport::Cache::Store
+  #
+  # notification_trigger: specify a callback to determine when a notification 
should be sent,
+  #   the callback will be invoked with two arguments:
+  #     exception: the exception raised
+  #     count: accumulated errors count for this exception
+  #
+  # :notification_trigger => lambda { |exception, count| count % 10 == 0 }
+```
+
 ## Ignore Exceptions
 
 You can choose to ignore certain exceptions, which will make 
ExceptionNotification avoid sending notifications for those specified. There 
are three ways of specifying which exceptions to ignore:
Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/exception_notification.gemspec 
new/exception_notification.gemspec
--- old/exception_notification.gemspec  2016-07-18 01:26:10.000000000 +0200
+++ new/exception_notification.gemspec  2017-08-12 22:52:29.000000000 +0200
@@ -1,8 +1,8 @@
 Gem::Specification.new do |s|
   s.name = 'exception_notification'
-  s.version = '4.2.1'
+  s.version = '4.2.2'
   s.authors = ["Jamis Buck", "Josh Peek"]
-  s.date = %q{2016-07-17}
+  s.date = %q{2017-08-12}
   s.summary = "Exception notification for Rails apps"
   s.homepage = "https://smartinez87.github.io/exception_notification/";
   s.email = "[email protected]"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notification/rack.rb 
new/lib/exception_notification/rack.rb
--- old/lib/exception_notification/rack.rb      2016-07-18 01:26:10.000000000 
+0200
+++ new/lib/exception_notification/rack.rb      2017-08-12 22:52:29.000000000 
+0200
@@ -6,6 +6,15 @@
       @app = app
 
       ExceptionNotifier.ignored_exceptions = 
options.delete(:ignore_exceptions) if options.key?(:ignore_exceptions)
+      ExceptionNotifier.error_grouping = options.delete(:error_grouping) if 
options.key?(:error_grouping)
+      ExceptionNotifier.error_grouping_period = 
options.delete(:error_grouping_period) if options.key?(:error_grouping_period)
+      ExceptionNotifier.notification_trigger = 
options.delete(:notification_trigger) if options.key?(:notification_trigger)
+
+      if options.key?(:error_grouping_cache)
+        ExceptionNotifier.error_grouping_cache = 
options.delete(:error_grouping_cache)
+      elsif defined?(Rails)
+        ExceptionNotifier.error_grouping_cache = Rails.cache
+      end
 
       if options.key?(:ignore_if)
         rack_ignore = options.delete(:ignore_if)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notification/rails.rb 
new/lib/exception_notification/rails.rb
--- old/lib/exception_notification/rails.rb     2016-07-18 01:26:10.000000000 
+0200
+++ new/lib/exception_notification/rails.rb     2017-08-12 22:52:29.000000000 
+0200
@@ -2,6 +2,7 @@
   class Engine < ::Rails::Engine
     config.exception_notification = ExceptionNotifier
     config.exception_notification.logger = Rails.logger
+    config.exception_notification.error_grouping_cache = Rails.cache
 
     config.app_middleware.use ExceptionNotification::Rack
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notifier/campfire_notifier.rb 
new/lib/exception_notifier/campfire_notifier.rb
--- old/lib/exception_notifier/campfire_notifier.rb     2016-07-18 
01:26:10.000000000 +0200
+++ new/lib/exception_notifier/campfire_notifier.rb     2017-08-12 
22:52:29.000000000 +0200
@@ -19,7 +19,11 @@
 
     def call(exception, options={})
       if active?
-        message = "A new exception occurred: '#{exception.message}'"
+        message = if options[:accumulated_errors_count].to_i > 1
+          "The exception occurred #{options[:accumulated_errors_count]} times: 
'#{exception.message}'"
+        else
+          "A new exception occurred: '#{exception.message}'"
+        end
         message += " on '#{exception.backtrace.first}'" if exception.backtrace
         send_notice(exception, options, message) do |msg, _|
           @room.paste msg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notifier/email_notifier.rb 
new/lib/exception_notifier/email_notifier.rb
--- old/lib/exception_notifier/email_notifier.rb        2016-07-18 
01:26:10.000000000 +0200
+++ new/lib/exception_notifier/email_notifier.rb        2017-08-12 
22:52:29.000000000 +0200
@@ -9,8 +9,8 @@
     attr_accessor(:sender_address, :exception_recipients,
     :pre_callback, :post_callback,
     :email_prefix, :email_format, :sections, :background_sections,
-    :verbose_subject, :normalize_subject, :delivery_method, :mailer_settings,
-    :email_headers, :mailer_parent, :template_path, :deliver_with)
+    :verbose_subject, :normalize_subject, 
:include_controller_and_action_names_in_subject,
+    :delivery_method, :mailer_settings, :email_headers, :mailer_parent, 
:template_path, :deliver_with)
 
     module Mailer
       class MissingController
@@ -46,7 +46,7 @@
             load_custom_views
 
             @exception = exception
-            @options   = options.reverse_merge(default_options)
+            @options   = options.reverse_merge(default_options).symbolize_keys
             @backtrace = exception.backtrace || []
             @timestamp = Time.current
             @sections  = @options[:background_sections]
@@ -60,7 +60,8 @@
 
           def compose_subject
             subject = "#{@options[:email_prefix]}"
-            subject << 
"#{@kontroller.controller_name}##{@kontroller.action_name}" if @kontroller
+            subject << "(#{@options[:accumulated_errors_count]} times) " if 
@options[:accumulated_errors_count].to_i > 1
+            subject << 
"#{@kontroller.controller_name}##{@kontroller.action_name}" if @kontroller && 
@options[:include_controller_and_action_names_in_subject]
             subject << " (#{@exception.class})"
             subject << " #{@exception.message.inspect}" if 
@options[:verbose_subject]
             subject = EmailNotifier.normalize_digits(subject) if 
@options[:normalize_subject]
@@ -74,13 +75,17 @@
           end
 
           helper_method :inspect_object
-
+          
+          def truncate(string, max)
+            string.length > max ? "#{string[0...max]}..." : string
+          end
+          
           def inspect_object(object)
             case object
               when Hash, Array
-                object.inspect
+                truncate(object.inspect, 300)
               else
-                object.to_s
+                  object.to_s 
             end
           end
 
@@ -138,10 +143,10 @@
       options[:mailer_settings] = options.delete(mailer_settings_key)
 
       options.reverse_merge(EmailNotifier.default_options).select{|k,v|[
-        :sender_address, :exception_recipients,
-        :pre_callback, :post_callback,
-        :email_prefix, :email_format, :sections, :background_sections,
-        :verbose_subject, :normalize_subject, :delivery_method, 
:mailer_settings,
+        :sender_address, :exception_recipients, :pre_callback,
+        :post_callback, :email_prefix, :email_format,
+        :sections, :background_sections, :verbose_subject, :normalize_subject,
+        :include_controller_and_action_names_in_subject, :delivery_method, 
:mailer_settings,
         :email_headers, :mailer_parent, :template_path, 
:deliver_with].include?(k)}.each{|k,v| send("#{k}=", v)}
     end
 
@@ -197,6 +202,7 @@
         :background_sections => %w(backtrace data),
         :verbose_subject => true,
         :normalize_subject => false,
+        :include_controller_and_action_names_in_subject => true,
         :delivery_method => nil,
         :mailer_settings => nil,
         :email_headers => {},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notifier/hipchat_notifier.rb 
new/lib/exception_notifier/hipchat_notifier.rb
--- old/lib/exception_notifier/hipchat_notifier.rb      2016-07-18 
01:26:10.000000000 +0200
+++ new/lib/exception_notifier/hipchat_notifier.rb      2017-08-12 
22:52:29.000000000 +0200
@@ -13,10 +13,15 @@
         opts              = {
                               :api_version => options.delete(:api_version) || 
'v1'
                             }
+        opts[:server_url] = options.delete(:server_url) if options[:server_url]
         @from             = options.delete(:from) || 'Exception'
         @room             = HipChat::Client.new(api_token, opts)[room_name]
-        @message_template = options.delete(:message_template) || ->(exception) 
{
-          msg = "A new exception occurred: 
'#{Rack::Utils.escape_html(exception.message)}'"
+        @message_template = options.delete(:message_template) || ->(exception, 
errors_count) {
+          msg = if errors_count > 1
+            "The exception occurred #{errors_count} times: 
'#{Rack::Utils.escape_html(exception.message)}'"
+          else
+            "A new exception occurred: 
'#{Rack::Utils.escape_html(exception.message)}'"
+          end
           msg += " on '#{exception.backtrace.first}'" if exception.backtrace
           msg
         }
@@ -30,7 +35,7 @@
     def call(exception, options={})
       return if !active?
 
-      message = @message_template.call(exception)
+      message = @message_template.call(exception, 
options[:accumulated_errors_count].to_i)
       send_notice(exception, options, message, @message_options) do |msg, 
message_opts|
         @room.send(@from, msg, message_opts)
       end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notifier/irc_notifier.rb 
new/lib/exception_notifier/irc_notifier.rb
--- old/lib/exception_notifier/irc_notifier.rb  2016-07-18 01:26:10.000000000 
+0200
+++ new/lib/exception_notifier/irc_notifier.rb  2017-08-12 22:52:29.000000000 
+0200
@@ -7,7 +7,11 @@
     end
 
     def call(exception, options={})
+      errors_count = options[:accumulated_errors_count].to_i
+
       message = "'#{exception.message}'"
+      message.prepend("(#{errors_count} times)") if errors_count > 1
+
       message += " on '#{exception.backtrace.first}'" if exception.backtrace
       if active?
         send_notice(exception, options, message) do |msg, _|
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notifier/mattermost_notifier.rb 
new/lib/exception_notifier/mattermost_notifier.rb
--- old/lib/exception_notifier/mattermost_notifier.rb   2016-07-18 
01:26:10.000000000 +0200
+++ new/lib/exception_notifier/mattermost_notifier.rb   2017-08-12 
22:52:29.000000000 +0200
@@ -91,8 +91,9 @@
       def message_header
         text = []
 
+        errors_count = @options[:accumulated_errors_count].to_i
         text << "### :warning: Error 500 in #{Rails.env} :warning:"
-        text << "An *#{@exception.class}* occured" + if @controller then " in 
*#{controller_and_method}*." else "." end
+        text << "#{errors_count > 1 ? errors_count : 'An'} 
*#{@exception.class}* occured" + if @controller then " in 
*#{controller_and_method}*." else "." end
         text << "*#{@exception.message}*"
 
         text
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notifier/modules/error_grouping.rb 
new/lib/exception_notifier/modules/error_grouping.rb
--- old/lib/exception_notifier/modules/error_grouping.rb        1970-01-01 
01:00:00.000000000 +0100
+++ new/lib/exception_notifier/modules/error_grouping.rb        2017-08-12 
22:52:29.000000000 +0200
@@ -0,0 +1,77 @@
+require 'active_support/core_ext/numeric/time'
+require 'active_support/concern'
+
+module ExceptionNotifier
+  module ErrorGrouping
+    extend ActiveSupport::Concern
+
+    included do
+      mattr_accessor :error_grouping
+      self.error_grouping = false
+
+      mattr_accessor :error_grouping_period
+      self.error_grouping_period = 5.minutes
+
+      mattr_accessor :notification_trigger
+
+      mattr_accessor :error_grouping_cache
+    end
+
+    module ClassMethods
+      # Fallback to the memory store while the specified cache store doesn't 
work
+      #
+      def fallback_cache_store
+        @fallback_cache_store ||= ActiveSupport::Cache::MemoryStore.new
+      end
+
+      def error_count(error_key)
+        count = begin
+          error_grouping_cache.read(error_key)
+        rescue => e
+          ExceptionNotifier.logger.warn("#{error_grouping_cache.inspect} 
failed to read, reason: #{e.message}. Falling back to memory cache store.")
+          fallback_cache_store.read(error_key)
+        end
+
+        count.to_i if count
+      end
+
+      def save_error_count(error_key, count)
+        error_grouping_cache.write(error_key, count, expires_in: 
error_grouping_period)
+      rescue => e
+        ExceptionNotifier.logger.warn("#{error_grouping_cache.inspect} failed 
to write, reason: #{e.message}. Falling back to memory cache store.")
+        fallback_cache_store.write(error_key, count, expires_in: 
error_grouping_period)
+      end
+
+      def group_error!(exception, options)
+        message_based_key = 
"exception:#{Zlib.crc32("#{exception.class.name}\nmessage:#{exception.message}")}"
+        accumulated_errors_count = 1
+
+        if count = error_count(message_based_key)
+          accumulated_errors_count = count + 1
+          save_error_count(message_based_key, accumulated_errors_count)
+        else
+          backtrace_based_key = 
"exception:#{Zlib.crc32("#{exception.class.name}\npath:#{exception.backtrace.try(:first)}")}"
+
+          if count = Rails.cache.read(backtrace_based_key)
+            accumulated_errors_count = count + 1
+            save_error_count(backtrace_based_key, accumulated_errors_count)
+          else
+            save_error_count(backtrace_based_key, accumulated_errors_count)
+            save_error_count(message_based_key, accumulated_errors_count)
+          end
+        end
+
+        options[:accumulated_errors_count] = accumulated_errors_count
+      end
+
+      def send_notification?(exception, count)
+        if notification_trigger.respond_to?(:call)
+          notification_trigger.call(exception, count)
+        else
+          factor = Math.log2(count)
+          factor.to_i == factor
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notifier/slack_notifier.rb 
new/lib/exception_notifier/slack_notifier.rb
--- old/lib/exception_notifier/slack_notifier.rb        2016-07-18 
01:26:10.000000000 +0200
+++ new/lib/exception_notifier/slack_notifier.rb        2017-08-12 
22:52:29.000000000 +0200
@@ -8,10 +8,12 @@
       super
       begin
         @ignore_data_if = options[:ignore_data_if]
-        @backtrace_lines = options[:backtrace_lines]
+        @backtrace_lines = options.fetch(:backtrace_lines, 10)
+        @additional_fields = options[:additional_fields]
 
         webhook_url = options.fetch(:webhook_url)
         @message_opts = options.fetch(:additional_parameters, {})
+        @color = @message_opts.delete(:color) { 'danger' }
         @notifier = Slack::Notifier.new webhook_url, options
       rescue
         @notifier = nil
@@ -19,7 +21,9 @@
     end
 
     def call(exception, options={})
-      exception_name = "*#{exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'}* 
`#{exception.class.to_s}`"
+      errors_count = options[:accumulated_errors_count].to_i
+      measure_word = errors_count > 1 ? errors_count : (exception.class.to_s 
=~ /^[aeiou]/i ? 'An' : 'A')
+      exception_name = "*#{measure_word}* `#{exception.class.to_s}`"
 
       if options[:env].nil?
         data = options[:data] || {}
@@ -30,18 +34,18 @@
 
         kontroller = env['action_controller.instance']
         request = "#{env['REQUEST_METHOD']} <#{env['REQUEST_URI']}>"
-        text = "#{exception_name} *occurred while* `#{env['REQUEST_METHOD']} 
<#{env['REQUEST_URI']}>`"
+        text = "#{exception_name} *occurred while* `#{request}`"
         text += " *was processed by* 
`#{kontroller.controller_name}##{kontroller.action_name}`" if kontroller
         text += "\n"
       end
 
       clean_message = exception.message.gsub("`", "'")
-      fields = [ { title: 'Exception', value: clean_message} ]
+      fields = [ { title: 'Exception', value: clean_message } ]
 
       fields.push({ title: 'Hostname', value: Socket.gethostname })
 
       if exception.backtrace
-        formatted_backtrace = @backtrace_lines ? 
"```#{exception.backtrace.first(@backtrace_lines).join("\n")}```" : 
"```#{exception.backtrace.join("\n")}```"
+        formatted_backtrace = 
"```#{exception.backtrace.first(@backtrace_lines).join("\n")}```"
         fields.push({ title: 'Backtrace', value: formatted_backtrace })
       end
 
@@ -51,7 +55,9 @@
         fields.push({ title: 'Data', value: "```#{data_string}```" })
       end
 
-      attchs = [color: 'danger', text: text, fields: fields, mrkdwn_in: 
%w(text fields)]
+      fields.concat(@additional_fields) if @additional_fields
+
+      attchs = [color: @color, text: text, fields: fields, mrkdwn_in: %w(text 
fields)]
 
       if valid?
         send_notice(exception, options, clean_message, 
@message_opts.merge(attachments: attchs)) do |msg, message_opts|
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb
 
new/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb
--- 
old/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb
 2016-07-18 01:26:10.000000000 +0200
+++ 
new/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb
 2017-08-12 22:52:29.000000000 +0200
@@ -14,11 +14,9 @@
         title = render("title", :title => section).strip
         [title, summary]
       end
-
     rescue Exception => e
       title = render("title", :title => section).strip
       summary = ["ERROR: Failed to generate exception summary:", 
[e.class.to_s, e.message].join(": "), e.backtrace && 
e.backtrace.join("\n")].compact.join("\n\n")
-
       [title, summary]
     end
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/exception_notifier.rb 
new/lib/exception_notifier.rb
--- old/lib/exception_notifier.rb       2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notifier.rb       2017-08-12 22:52:29.000000000 +0200
@@ -2,8 +2,10 @@
 require 'active_support/core_ext/string/inflections'
 require 'active_support/core_ext/module/attribute_accessors'
 require 'exception_notifier/base_notifier'
+require 'exception_notifier/modules/error_grouping'
 
 module ExceptionNotifier
+  include ErrorGrouping
 
   autoload :BacktraceCleaner, 'exception_notifier/modules/backtrace_cleaner'
 
@@ -43,6 +45,11 @@
     def notify_exception(exception, options={})
       return false if ignored_exception?(options[:ignore_exceptions], 
exception)
       return false if ignored?(exception, options)
+      if error_grouping
+        errors_count = group_error!(exception, options)
+        return false unless send_notification?(exception, errors_count)
+      end
+
       selected_notifiers = options.delete(:notifiers) || notifiers
       [*selected_notifiers].each do |notifier|
         fire_notification(notifier, exception, options.dup)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/metadata new/metadata
--- old/metadata        2016-07-18 01:26:10.000000000 +0200
+++ new/metadata        2017-08-12 22:52:29.000000000 +0200
@@ -1,7 +1,7 @@
 --- !ruby/object:Gem::Specification
 name: exception_notification
 version: !ruby/object:Gem::Version
-  version: 4.2.1
+  version: 4.2.2
 platform: ruby
 authors:
 - Jamis Buck
@@ -9,7 +9,7 @@
 autorequire: 
 bindir: bin
 cert_chain: []
-date: 2016-07-17 00:00:00.000000000 Z
+date: 2017-08-12 00:00:00.000000000 Z
 dependencies:
 - !ruby/object:Gem::Dependency
   name: actionmailer
@@ -269,6 +269,7 @@
 - lib/exception_notifier/irc_notifier.rb
 - lib/exception_notifier/mattermost_notifier.rb
 - lib/exception_notifier/modules/backtrace_cleaner.rb
+- lib/exception_notifier/modules/error_grouping.rb
 - lib/exception_notifier/notifier.rb
 - lib/exception_notifier/slack_notifier.rb
 - lib/exception_notifier/views/exception_notifier/_backtrace.html.erb
@@ -348,6 +349,7 @@
 - test/exception_notifier/hipchat_notifier_test.rb
 - test/exception_notifier/irc_notifier_test.rb
 - test/exception_notifier/mattermost_notifier_test.rb
+- test/exception_notifier/modules/error_grouping_test.rb
 - test/exception_notifier/sidekiq_test.rb
 - test/exception_notifier/slack_notifier_test.rb
 - test/exception_notifier/webhook_notifier_test.rb
@@ -436,6 +438,7 @@
 - test/exception_notifier/hipchat_notifier_test.rb
 - test/exception_notifier/irc_notifier_test.rb
 - test/exception_notifier/mattermost_notifier_test.rb
+- test/exception_notifier/modules/error_grouping_test.rb
 - test/exception_notifier/sidekiq_test.rb
 - test/exception_notifier/slack_notifier_test.rb
 - test/exception_notifier/webhook_notifier_test.rb
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/dummy/test/functional/posts_controller_test.rb 
new/test/dummy/test/functional/posts_controller_test.rb
--- old/test/dummy/test/functional/posts_controller_test.rb     2016-07-18 
01:26:10.000000000 +0200
+++ new/test/dummy/test/functional/posts_controller_test.rb     2017-08-12 
22:52:29.000000000 +0200
@@ -35,7 +35,7 @@
   test "mail subject should have the proper prefix" do
     assert_includes @mail.subject, "[Dummy ERROR]"
   end
-
+  
   test "mail subject should include descriptive error message" do
     assert_includes @mail.subject, "(NoMethodError) \"undefined method `nw'"
   end
@@ -146,6 +146,25 @@
   end
 end
 
+class PostsControllerTestWithoutControllerAndActionNames < 
ActionController::TestCase
+  tests PostsController
+  setup do
+    @email_notifier = 
ExceptionNotifier::EmailNotifier.new(:include_controller_and_action_names_in_subject
 => false)
+    begin
+      post :create, method: :post
+    rescue => e
+      @exception = e
+      @mail = @email_notifier.create_email(@exception, {:env => request.env})
+    end
+  end
+
+  test "should include controller and action names in subject" do
+    assert_includes @mail.subject, '[ERROR]'
+    assert_includes @mail.subject, '(NoMethodError)'
+    refute_includes @mail.subject, 'posts#create'
+  end
+end
+
 class PostsControllerTestWithSmtpSettings < ActionController::TestCase
   tests PostsController
   setup do
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/exception_notification/rack_test.rb 
new/test/exception_notification/rack_test.rb
--- old/test/exception_notification/rack_test.rb        2016-07-18 
01:26:10.000000000 +0200
+++ new/test/exception_notification/rack_test.rb        2017-08-12 
22:52:29.000000000 +0200
@@ -5,6 +5,14 @@
   setup do
     @pass_app = Object.new
     @pass_app.stubs(:call).returns([nil, { 'X-Cascade' => 'pass' }, nil])
+
+    @normal_app = Object.new
+    @normal_app.stubs(:call).returns([nil, { }, nil])
+  end
+
+  teardown do
+    ExceptionNotifier.error_grouping = false
+    ExceptionNotifier.notification_trigger = nil
   end
 
   test "should ignore \"X-Cascade\" header by default" do
@@ -17,4 +25,20 @@
     ExceptionNotification::Rack.new(@pass_app, :ignore_cascade_pass => 
false).call({})
   end
 
+  test "should assign error_grouping if error_grouping is specified" do
+    refute ExceptionNotifier.error_grouping
+    ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
+    assert ExceptionNotifier.error_grouping
+  end
+
+  test "should assign notification_trigger if notification_trigger is 
specified" do
+    assert_nil ExceptionNotifier.notification_trigger
+    ExceptionNotification::Rack.new(@normal_app, notification_trigger: lambda 
{|i| true}).call({})
+    assert_respond_to ExceptionNotifier.notification_trigger, :call
+  end
+
+  test "should set default cache to Rails cache" do
+    ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
+    assert_equal Rails.cache, ExceptionNotifier.error_grouping_cache
+  end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/exception_notifier/campfire_notifier_test.rb 
new/test/exception_notifier/campfire_notifier_test.rb
--- old/test/exception_notifier/campfire_notifier_test.rb       2016-07-18 
01:26:10.000000000 +0200
+++ new/test/exception_notifier/campfire_notifier_test.rb       2017-08-12 
22:52:29.000000000 +0200
@@ -52,6 +52,20 @@
     assert_nil campfire.call(fake_exception)
   end
 
+  test "should send the new exception message if no :accumulated_errors_count 
option" do
+    campfire = ExceptionNotifier::CampfireNotifier.new({})
+    campfire.stubs(:active?).returns(true)
+    campfire.expects(:send_notice).with{ |_, _, message| 
message.start_with?("A new exception occurred") }.once
+    campfire.call(fake_exception)
+  end
+
+  test "shoud send the exception message if :accumulated_errors_count option 
greater than 1" do
+    campfire = ExceptionNotifier::CampfireNotifier.new({})
+    campfire.stubs(:active?).returns(true)
+    campfire.expects(:send_notice).with{ |_, _, message| 
message.start_with?("The exception occurred 3 times:") }.once
+    campfire.call(fake_exception, accumulated_errors_count: 3)
+  end
+
   test "should call pre/post_callback if specified" do 
     pre_callback_called, post_callback_called = 0,0
     Tinder::Campfire.stubs(:new).returns(Object.new)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/exception_notifier/email_notifier_test.rb 
new/test/exception_notifier/email_notifier_test.rb
--- old/test/exception_notifier/email_notifier_test.rb  2016-07-18 
01:26:10.000000000 +0200
+++ new/test/exception_notifier/email_notifier_test.rb  2017-08-12 
22:52:29.000000000 +0200
@@ -218,4 +218,18 @@
     mail = email_notifier.call(@exception)
     assert_equal %w{[email protected]}, mail.to
   end
+
+  test "should prepend accumulated_errors_count in email subject if 
accumulated_errors_count larger than 1" do
+    ActionMailer::Base.deliveries.clear
+
+    email_notifier = ExceptionNotifier::EmailNotifier.new(
+      :email_prefix => '[Dummy ERROR] ',
+      :sender_address => %{"Dummy Notifier" <[email protected]>},
+      :exception_recipients => %w{[email protected]},
+      :delivery_method => :test
+    )
+
+    mail = email_notifier.call(@exception, { accumulated_errors_count: 3 })
+    assert mail.subject.start_with?("[Dummy ERROR] (3 times) 
(ZeroDivisionError)")
+  end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/exception_notifier/hipchat_notifier_test.rb 
new/test/exception_notifier/hipchat_notifier_test.rb
--- old/test/exception_notifier/hipchat_notifier_test.rb        2016-07-18 
01:26:10.000000000 +0200
+++ new/test/exception_notifier/hipchat_notifier_test.rb        2017-08-12 
22:52:29.000000000 +0200
@@ -101,7 +101,7 @@
       :api_token => 'good_token',
       :room_name => 'room_name',
       :color     => 'yellow',
-      :message_template => ->(exception) { "This is custom message: 
'#{exception.message}'" }
+      :message_template => ->(exception, _) { "This is custom message: 
'#{exception.message}'" }
     }
 
     HipChat::Room.any_instance.expects(:send).with('Exception', "This is 
custom message: '#{fake_exception.message}'", { :color => 'yellow' })
@@ -110,6 +110,30 @@
     hipchat.call(fake_exception)
   end
 
+  test "should send hipchat notification exclude accumulated errors count" do
+    options = {
+      :api_token => 'good_token',
+      :room_name => 'room_name',
+      :color     => 'yellow'
+    }
+
+    HipChat::Room.any_instance.expects(:send).with{ |_, msg, _| 
msg.start_with?("A new exception occurred:") }
+    hipchat = ExceptionNotifier::HipchatNotifier.new(options)
+    hipchat.call(fake_exception)
+  end
+
+  test "should send hipchat notification include accumulated errors count" do
+    options = {
+      :api_token => 'good_token',
+      :room_name => 'room_name',
+      :color     => 'yellow'
+    }
+
+    HipChat::Room.any_instance.expects(:send).with{ |_, msg, _| 
msg.start_with?("The exception occurred 3 times:") }
+    hipchat = ExceptionNotifier::HipchatNotifier.new(options)
+    hipchat.call(fake_exception, { accumulated_errors_count: 3 })
+  end
+
   test "should send hipchat notification with HTML-escaped meessage if using 
default message_template" do
     options = {
       :api_token => 'good_token',
@@ -149,6 +173,20 @@
 
     hipchat = ExceptionNotifier::HipchatNotifier.new(options)
     hipchat.call(fake_exception)
+  end
+
+  test "should allow server_url value (for a self-hosted HipChat Server) if 
set" do
+    options = {
+      :api_token   => 'good_token',
+      :room_name   => 'room_name',
+      :api_version => 'v2',
+      :server_url  => 'https://domain.com',
+    }
+
+    HipChat::Client.stubs(:new).with('good_token', {:api_version => 'v2', 
:server_url => 'https://domain.com'}).returns({})
+
+    hipchat = ExceptionNotifier::HipchatNotifier.new(options)
+    hipchat.call(fake_exception)
   end
 
   private
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/exception_notifier/irc_notifier_test.rb 
new/test/exception_notifier/irc_notifier_test.rb
--- old/test/exception_notifier/irc_notifier_test.rb    2016-07-18 
01:26:10.000000000 +0200
+++ new/test/exception_notifier/irc_notifier_test.rb    2017-08-12 
22:52:29.000000000 +0200
@@ -16,6 +16,22 @@
     irc.call(fake_exception)
   end
 
+  test "should exclude errors count in message if :accumulated_errors_count 
nil" do
+    irc = ExceptionNotifier::IrcNotifier.new({})
+    irc.stubs(:active?).returns(true)
+
+    irc.expects(:send_message).with{ |message| message.include?("divided by 
0") }.once
+    irc.call(fake_exception)
+  end
+
+  test "should include errors count in message if :accumulated_errors_count is 
3" do
+    irc = ExceptionNotifier::IrcNotifier.new({})
+    irc.stubs(:active?).returns(true)
+
+    irc.expects(:send_message).with{ |message| message.include?("(3 
times)'divided by 0'") }.once
+    irc.call(fake_exception, accumulated_errors_count: 3)
+  end
+
   test "should call pre/post_callback if specified" do
     pre_callback_called, post_callback_called = 0,0
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/exception_notifier/mattermost_notifier_test.rb 
new/test/exception_notifier/mattermost_notifier_test.rb
--- old/test/exception_notifier/mattermost_notifier_test.rb     2016-07-18 
01:26:10.000000000 +0200
+++ new/test/exception_notifier/mattermost_notifier_test.rb     2017-08-12 
22:52:29.000000000 +0200
@@ -77,6 +77,23 @@
     assert 'password', options[:basic_auth][:password]
   end
 
+  test "should use 'An' for exceptions count if :accumulated_errors_count 
option is nil" do
+    mattermost_notifier = ExceptionNotifier::MattermostNotifier.new
+    exception = ArgumentError.new("foo")
+    mattermost_notifier.instance_variable_set(:@exception, exception)
+    mattermost_notifier.instance_variable_set(:@options, {})
+
+    assert_includes mattermost_notifier.send(:message_header), "An 
*ArgumentError* occured."
+  end
+
+  test "shoud use direct errors count if :accumulated_errors_count option is 
5" do
+    mattermost_notifier = ExceptionNotifier::MattermostNotifier.new
+    exception = ArgumentError.new("foo")
+    mattermost_notifier.instance_variable_set(:@exception, exception)
+    mattermost_notifier.instance_variable_set(:@options, { 
accumulated_errors_count: 5 })
+
+    assert_includes mattermost_notifier.send(:message_header), "5 
*ArgumentError* occured."
+  end
 end
 
 class FakeHTTParty
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/test/exception_notifier/modules/error_grouping_test.rb 
new/test/exception_notifier/modules/error_grouping_test.rb
--- old/test/exception_notifier/modules/error_grouping_test.rb  1970-01-01 
01:00:00.000000000 +0100
+++ new/test/exception_notifier/modules/error_grouping_test.rb  2017-08-12 
22:52:29.000000000 +0200
@@ -0,0 +1,166 @@
+require 'test_helper'
+
+class ErrorGroupTest < ActiveSupport::TestCase
+
+  setup do
+    module TestModule
+      include ExceptionNotifier::ErrorGrouping
+      @@error_grouping_cache = 
ActiveSupport::Cache::FileStore.new("test/dummy/tmp/cache")
+    end
+
+    @exception = RuntimeError.new("ERROR")
+    @exception.stubs(:backtrace).returns(["/path/where/error/raised:1"])
+
+    @exception2 = RuntimeError.new("ERROR2")
+    @exception2.stubs(:backtrace).returns(["/path/where/error/found:2"])
+  end
+
+  teardown do
+    TestModule.error_grouping_cache.clear
+    TestModule.fallback_cache_store.clear
+  end
+
+  test "should add additional option: error_grouping" do
+    assert_respond_to TestModule, :error_grouping
+    assert_respond_to TestModule, :error_grouping=
+  end
+
+  test "should set error_grouping to false default" do
+    assert_equal false, TestModule.error_grouping
+  end
+
+  test "should add additional option: error_grouping_cache" do
+    assert_respond_to TestModule, :error_grouping_cache
+    assert_respond_to TestModule, :error_grouping_cache=
+  end
+
+  test "should add additional option: error_grouping_period" do
+    assert_respond_to TestModule, :error_grouping_period
+    assert_respond_to TestModule, :error_grouping_period=
+  end
+
+  test "shoud set error_grouping_period to 5.minutes default" do
+    assert_equal 300, TestModule.error_grouping_period
+  end
+
+  test "should add additional option: notification_trigger" do
+    assert_respond_to TestModule, :notification_trigger
+    assert_respond_to TestModule, :notification_trigger=
+  end
+
+  test "should return errors count nil when not same error for .error_count" do
+    assert_nil TestModule.error_count("something")
+  end
+
+  test "should return errors count when same error for .error_count" do
+    TestModule.error_grouping_cache.write("error_key", 13)
+    assert_equal 13, TestModule.error_count("error_key")
+  end
+
+  test "should fallback to memory store cache if specified cache store failed 
to read" do
+    TestModule.error_grouping_cache.stubs(:read).raises(RuntimeError.new 
"Failed to read")
+    original_fallback = TestModule.fallback_cache_store
+    
TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
+
+    assert_nil TestModule.error_count("something_to_read")
+  end
+
+  test "should save error with count for .save_error_count" do
+    count = rand(1..10)
+
+    TestModule.save_error_count("error_key", count)
+    assert_equal count, TestModule.error_grouping_cache.read("error_key")
+  end
+
+  test "should fallback to memory store cache if specified cache store failed 
to write" do
+    TestModule.error_grouping_cache.stubs(:write).raises(RuntimeError.new 
"Failed to write")
+    original_fallback = TestModule.fallback_cache_store
+    
TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
+
+    assert TestModule.save_error_count("something_to_cache", rand(1..10))
+  end
+
+  test "should save accumulated_errors_count into options" do
+    options = {}
+    TestModule.group_error!(@exception, options)
+
+    assert_equal 1, options[:accumulated_errors_count]
+  end
+
+  test "should not group error if different exception in .group_error!" do
+    options1 = {}
+    TestModule.expects(:save_error_count).with{|key, count| key.is_a?(String) 
&& count == 1}.times(4).returns(true)
+    TestModule.group_error!(@exception, options1)
+
+    options2 = {}
+    TestModule.group_error!(NoMethodError.new("method not found"), options2)
+
+    assert_equal 1, options1[:accumulated_errors_count]
+    assert_equal 1, options2[:accumulated_errors_count]
+  end
+
+  test "should not group error is same exception but different message or 
backtrace" do
+    options1 = {}
+    TestModule.expects(:save_error_count).with{|key, count| key.is_a?(String) 
&& count == 1}.times(4).returns(true)
+    TestModule.group_error!(@exception, options1)
+
+    options2 = {}
+    TestModule.group_error!(@exception2, options2)
+
+    assert_equal 1, options1[:accumulated_errors_count]
+    assert_equal 1, options2[:accumulated_errors_count]
+  end
+
+  test "should group error if same exception and message" do
+    options = {}
+
+    10.times do |i|
+      @exception2.stubs(:backtrace).returns(["/path:#{i}"])
+      TestModule.group_error!(@exception2, options)
+    end
+
+    assert_equal 10, options[:accumulated_errors_count]
+  end
+
+  test "should group error if same exception and backtrace" do
+    options = {}
+
+    10.times do |i|
+      @exception2.stubs(:message).returns("ERRORS#{i}")
+      TestModule.group_error!(@exception2, options)
+    end
+
+    assert_equal 10, options[:accumulated_errors_count]
+  end
+
+  test "should group error by that message have high priority" do
+    message_based_key = 
"exception:#{Zlib.crc32("RuntimeError\nmessage:ERROR")}"
+    backtrace_based_key = 
"exception:#{Zlib.crc32("RuntimeError\n/path/where/error/raised:1")}"
+
+    TestModule.save_error_count(message_based_key, 1)
+    TestModule.save_error_count(backtrace_based_key, 1)
+
+    TestModule.expects(:save_error_count).with(message_based_key, 2).once
+    TestModule.expects(:save_error_count).with(backtrace_based_key, 2).never
+
+    TestModule.group_error!(@exception, {})
+  end
+
+  test "use default formula if not specify notification_trigger in 
.send_notification?" do
+    TestModule.stubs(:notification_trigger).returns(nil)
+
+    count = 16
+    Math.expects(:log2).with(count).returns(4)
+
+    assert TestModule.send_notification?(@exception, count)
+  end
+
+  test "use specified trigger in .send_notification?" do
+    trigger = Proc.new { |exception, count| count % 4 == 0 }
+    TestModule.stubs(:notification_trigger).returns(trigger)
+
+    count = 16
+    trigger.expects(:call).with(@exception, count).returns(true)
+    assert TestModule.send_notification?(@exception, count)
+  end
+end
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/exception_notifier/slack_notifier_test.rb 
new/test/exception_notifier/slack_notifier_test.rb
--- old/test/exception_notifier/slack_notifier_test.rb  2016-07-18 
01:26:10.000000000 +0200
+++ new/test/exception_notifier/slack_notifier_test.rb  2017-08-12 
22:52:29.000000000 +0200
@@ -43,7 +43,8 @@
     slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
     slack_notifier.call(@exception)
 
-    assert_equal slack_notifier.notifier.channel, options[:channel]
+    channel = slack_notifier.notifier.config.defaults[:channel]
+    assert_equal channel, options[:channel]
   end
 
   test "should send the notification to the specified username" do
@@ -57,7 +58,8 @@
     slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
     slack_notifier.call(@exception)
 
-    assert_equal slack_notifier.notifier.username, options[:username]
+    username = slack_notifier.notifier.config.defaults[:username]
+    assert_equal username, options[:username]
   end
 
   test "should send the notification with specific backtrace lines" do
@@ -72,6 +74,22 @@
     slack_notifier.call(@exception)
   end
 
+  test "should send the notification with additional fields" do
+    field = {title: "Branch", value: "master", short: true}
+    options = {
+      webhook_url: "http://slack.webhook.url";,
+      additional_fields: [field]
+    }
+
+    Slack::Notifier.any_instance.expects(:ping).with('', 
fake_notification(@exception, {}, nil, 10, [field]))
+
+    slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
+    slack_notifier.call(@exception)
+
+    additional_fields = 
slack_notifier.notifier.config.defaults[:additional_fields]
+    assert_equal additional_fields, options[:additional_fields]
+  end
+
   test "should pass the additional parameters to Slack::Notifier.ping" do
     options = {
       webhook_url: "http://slack.webhook.url";,
@@ -177,7 +195,7 @@
     ]
   end
 
-  def fake_notification(exception = @exception, notification_options = {}, 
data_string = nil, expected_backtrace_lines = nil)
+  def fake_notification(exception = @exception, notification_options = {}, 
data_string = nil, expected_backtrace_lines = 10, additional_fields = [])
     exception_name = "*#{exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'}* 
`#{exception.class.to_s}`"
     if notification_options[:env].nil?
       text = "#{exception_name} *occured in background*"
@@ -196,10 +214,11 @@
     fields = [ { title: 'Exception', value: exception.message} ]
     fields.push({ title: 'Hostname', value: 'example.com' })
     if exception.backtrace
-      formatted_backtrace = expected_backtrace_lines ? 
"```#{exception.backtrace.first(expected_backtrace_lines).join("\n")}```" : 
"```#{exception.backtrace.join("\n")}```"
+      formatted_backtrace = 
"```#{exception.backtrace.first(expected_backtrace_lines).join("\n")}```"
       fields.push({ title: 'Backtrace', value: formatted_backtrace })
     end
     fields.push({ title: 'Data', value: "```#{data_string}```" }) if 
data_string
+    additional_fields.each { |f| fields.push(f) }
 
     { attachments: [ color: 'danger', text: text, fields: fields, mrkdwn_in: 
%w(text fields) ] }
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/exception_notifier_test.rb 
new/test/exception_notifier_test.rb
--- old/test/exception_notifier_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/exception_notifier_test.rb 2017-08-12 22:52:30.000000000 +0200
@@ -1,6 +1,21 @@
 require 'test_helper'
 
+class ExceptionOne < StandardError;end
+class ExceptionTwo < StandardError;end
+
 class ExceptionNotifierTest < ActiveSupport::TestCase
+  setup do
+    @notifier_calls = 0
+    @test_notifier = lambda { |exception, options| @notifier_calls += 1 }
+  end
+
+  teardown do
+    ExceptionNotifier.error_grouping = false
+    ExceptionNotifier.notification_trigger = nil
+    ExceptionNotifier.class_eval("@@notifiers.delete_if { |k, _| k.to_s != 
\"email\"}")  # reset notifiers
+    Rails.cache.clear
+  end
+
   test "should have default ignored exceptions" do
     assert_equal ExceptionNotifier.ignored_exceptions,
       ['ActiveRecord::RecordNotFound', 'Mongoid::Errors::DocumentNotFound', 
'AbstractController::ActionNotFound',
@@ -69,37 +84,67 @@
       env != "production"
     end
 
-    notifier_calls = 0
-    test_notifier = lambda { |exception, options| notifier_calls += 1 }
-    ExceptionNotifier.register_exception_notifier(:test, test_notifier)
+    ExceptionNotifier.register_exception_notifier(:test, @test_notifier)
 
     exception = StandardError.new
 
     ExceptionNotifier.notify_exception(exception, {:notifiers => :test})
-    assert_equal notifier_calls, 1
+    assert_equal @notifier_calls, 1
 
     env = "development"
     ExceptionNotifier.notify_exception(exception, {:notifiers => :test})
-    assert_equal notifier_calls, 1
+    assert_equal @notifier_calls, 1
 
     ExceptionNotifier.clear_ignore_conditions!
-    ExceptionNotifier.unregister_exception_notifier(:test)
   end
 
   test "should not send notification if one of ignored exceptions" do
-    notifier_calls = 0
-    test_notifier = lambda { |exception, options| notifier_calls += 1 }
-    ExceptionNotifier.register_exception_notifier(:test, test_notifier)
+    ExceptionNotifier.register_exception_notifier(:test, @test_notifier)
 
     exception = StandardError.new
 
     ExceptionNotifier.notify_exception(exception, {:notifiers => :test})
-    assert_equal notifier_calls, 1
+    assert_equal @notifier_calls, 1
 
     ExceptionNotifier.notify_exception(exception, {:notifiers => :test, 
:ignore_exceptions => 'StandardError' })
-    assert_equal notifier_calls, 1
+    assert_equal @notifier_calls, 1
+  end
+
+  test "should not call group_error! or send_notification? if error_grouping 
false" do
+    exception = StandardError.new
+    ExceptionNotifier.expects(:group_error!).never
+    ExceptionNotifier.expects(:send_notification?).never
+
+    ExceptionNotifier.notify_exception(exception)
+  end
+
+  test "should call group_error! and send_notification? if error_grouping 
true" do
+    ExceptionNotifier.error_grouping = true
 
-    ExceptionNotifier.unregister_exception_notifier(:test)
+    exception = StandardError.new
+    ExceptionNotifier.expects(:group_error!).once
+    ExceptionNotifier.expects(:send_notification?).once
+
+    ExceptionNotifier.notify_exception(exception)
+  end
+
+  test "should skip notification if send_notification? is false" do
+    ExceptionNotifier.error_grouping = true
+
+    exception = StandardError.new
+    ExceptionNotifier.expects(:group_error!).once.returns(1)
+    ExceptionNotifier.expects(:send_notification?).with(exception, 
1).once.returns(false)
+
+    refute ExceptionNotifier.notify_exception(exception)
   end
 
+  test "should send notification if send_notification? is true" do
+    ExceptionNotifier.error_grouping = true
+
+    exception = StandardError.new
+    ExceptionNotifier.expects(:group_error!).once.returns(1)
+    ExceptionNotifier.expects(:send_notification?).with(exception, 
1).once.returns(true)
+
+    assert ExceptionNotifier.notify_exception(exception)
+  end
 end


Reply via email to