diff --git a/app/controllers/apps_controller.rb b/app/controllers/apps_controller.rb
index 1cf9e48..2ead924 100644
--- a/app/controllers/apps_controller.rb
+++ b/app/controllers/apps_controller.rb
@@ -1,56 +1,56 @@
class AppsController < InheritedResources::Base
-
before_filter :require_admin!, :except => [:index, :show]
before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update]
respond_to :html
-
+
+
def show
respond_to do |format|
format.html do
@all_errs = !!params[:all_errs]
- @errs = resource.errs
- @errs = @errs.unresolved unless @all_errs
- @errs = @errs.in_env(params[:environment]).ordered.paginate(:page => params[:page], :per_page => current_user.per_page)
+ @problems = resource.problems
+ @problems = @problems.unresolved unless @all_errs
+ @problems = @problems.in_env(params[:environment]).ordered.paginate(:page => params[:page], :per_page => current_user.per_page)
- @selected_errs = params[:errs] || []
+ @selected_problems = params[:problems] || []
@deploys = @app.deploys.order_by(:created_at.desc).limit(5)
end
format.atom do
- @errs = resource.errs.unresolved.ordered
+ @problems = resource.problems.unresolved.ordered
end
end
end
-
+
def create
@app = App.new(params[:app])
initialize_subclassed_issue_tracker
create!
end
-
+
def update
@app = resource
initialize_subclassed_issue_tracker
update!
end
-
+
def new
plug_params(build_resource)
new!
end
-
+
def edit
plug_params(resource)
edit!
end
-
+
protected
def collection
# Sort apps by number of unresolved errs, descending.
# Caches the unresolved err counts while performing the sort.
@unresolved_counts = {}
@apps ||= end_of_association_chain.all.sort{|a,b|
- [a,b].each{|app| @unresolved_counts[app.id] ||= app.errs.unresolved.count }
+ [a,b].each{|app| @unresolved_counts[app.id] ||= app.problems.unresolved.count }
@unresolved_counts[b.id] <=> @unresolved_counts[a.id]
}
end
diff --git a/app/controllers/errs_controller.rb b/app/controllers/errs_controller.rb
index d13abaf..cdff01c 100644
--- a/app/controllers/errs_controller.rb
+++ b/app/controllers/errs_controller.rb
@@ -2,17 +2,18 @@ class ErrsController < ApplicationController
include ActionView::Helpers::TextHelper
before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several]
- before_filter :find_err, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several]
- before_filter :find_selected_errs, :only => [:destroy_several, :resolve_several, :unresolve_several]
+ before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several]
+ before_filter :find_selected_problems, :only => [:destroy_several, :resolve_several, :unresolve_several]
+
def index
app_scope = current_user.admin? ? App.all : current_user.apps
- @errs = Err.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered
- @selected_errs = params[:errs] || []
+ @problems = Problem.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered
+ @selected_problems = params[:problems] || []
respond_to do |format|
format.html do
- @errs = @errs.paginate(:page => params[:page], :per_page => current_user.per_page)
+ @problems = @problems.paginate(:page => params[:page], :per_page => current_user.per_page)
end
format.atom
end
@@ -21,15 +22,15 @@ class ErrsController < ApplicationController
def all
app_scope = current_user.admin? ? App.all : current_user.apps
- @selected_errs = params[:errs] || []
- @errs = Err.for_apps(app_scope).ordered.paginate(:page => params[:page], :per_page => current_user.per_page)
+ @problems = Problem.for_apps(app_scope).ordered.paginate(:page => params[:page], :per_page => current_user.per_page)
+ @selected_problems = params[:problems] || []
end
def show
- page = (params[:notice] || @err.notices_count)
+ page = (params[:notice] || @problem.notices_count)
page = 1 if page.to_i.zero?
- @notices = @err.notices.ordered.paginate(:page => page, :per_page => 1)
+ @notices = @problem.notices.paginate(:page => page, :per_page => 1)
@notice = @notices.first
@comment = Comment.new
end
@@ -39,32 +40,30 @@ class ErrsController < ApplicationController
set_tracker_params
if @app.issue_tracker
- @app.issue_tracker.create_issue @err
+ @app.issue_tracker.create_issue @problem
else
flash[:error] = "This app has no issue tracker setup."
end
- redirect_to app_err_path(@app, @err)
+ redirect_to app_err_path(@app, @problem)
rescue ActiveResource::ConnectionError => e
Rails.logger.error e.to_s
flash[:error] = "There was an error during issue creation. Check your tracker settings or try again later."
- redirect_to app_err_path(@app, @err)
+ redirect_to app_err_path(@app, @problem)
end
def unlink_issue
- @err.update_attribute :issue_link, nil
- redirect_to app_err_path(@app, @err)
+ @problem.update_attribute :issue_link, nil
+ redirect_to app_err_path(@app, @problem)
end
def resolve
# Deal with bug in mongoid where find is returning an Enumberable obj
- @err = @err.first if @err.respond_to?(:first)
-
- @err.resolve!
+ @problem = @problem.first if @problem.respond_to?(:first)
+ @problem.resolve!
flash[:success] = 'Great news everyone! The err has been resolved.'
-
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to app_path(@app)
@@ -74,13 +73,13 @@ class ErrsController < ApplicationController
def create_comment
@comment = Comment.new(params[:comment].merge(:user_id => current_user.id))
if @comment.valid?
- @err.comments << @comment
- @err.save
+ @problem.comments << @comment
+ @problem.save
flash[:success] = "Comment saved!"
else
flash[:error] = "I'm sorry, your comment was blank! Try again?"
end
- redirect_to app_err_path(@app, @err)
+ redirect_to app_err_path(@app, @problem)
end
@@ -91,27 +90,27 @@ class ErrsController < ApplicationController
else
flash[:error] = "Sorry, I couldn't delete your comment for some reason. I hope you don't have any sensitive information in there!"
end
- redirect_to app_err_path(@app, @err)
+ redirect_to app_err_path(@app, @problem)
end
def resolve_several
- @selected_errs.each(&:resolve!)
- flash[:success] = "Great news everyone! #{pluralize(@selected_errs.count, 'err has', 'errs have')} been resolved."
+ @selected_problems.each(&:resolve!)
+ flash[:success] = "Great news everyone! #{pluralize(@selected_problems.count, 'err has', 'errs have')} been resolved."
redirect_to :back
end
def unresolve_several
- @selected_errs.each(&:unresolve!)
- flash[:success] = "#{pluralize(@selected_errs.count, 'err has', 'errs have')} been unresolved."
+ @selected_problems.each(&:unresolve!)
+ flash[:success] = "#{pluralize(@selected_problems.count, 'err has', 'errs have')} been unresolved."
redirect_to :back
end
def destroy_several
- @selected_errs.each(&:destroy)
- flash[:notice] = "#{pluralize(@selected_errs.count, 'err has', 'errs have')} been deleted."
+ @selected_problems.each(&:destroy)
+ flash[:notice] = "#{pluralize(@selected_problems.count, 'err has', 'errs have')} been deleted."
redirect_to :back
end
@@ -128,8 +127,11 @@ protected
end
- def find_err
- @err = @app.errs.find(params[:id])
+ def find_problem
+ @problem = @app.problems.find(params[:id])
+
+ # Deal with bug in mogoid where find is returning an Enumberable obj
+ @problem = @problem.first if @problem.respond_to?(:first)
end
@@ -140,13 +142,13 @@ protected
end
- def find_selected_errs
- err_ids = (params[:errs] || []).compact
+ def find_selected_problems
+ err_ids = (params[:problems] || []).compact
if err_ids.empty?
flash[:notice] = "You have not selected any errors"
redirect_to :back
else
- @selected_errs = Array(Err.find(err_ids))
+ @selected_problems = Array(Problem.find(err_ids))
end
end
diff --git a/app/helpers/errs_helper.rb b/app/helpers/errs_helper.rb
index 93049ee..d17112a 100644
--- a/app/helpers/errs_helper.rb
+++ b/app/helpers/errs_helper.rb
@@ -1,18 +1,18 @@
module ErrsHelper
-
- def last_notice_at err
- err.last_notice_at || err.created_at
+
+ def last_notice_at(problem)
+ problem.last_notice_at || problem.created_at
end
-
+
def err_confirm
Errbit::Config.confirm_resolve_err === false ? nil : 'Seriously?'
end
-
- def link_to_github app, line, text=nil
+
+ def link_to_github(app, line, text=nil)
file_name = line['file'].split('/').last
file_path = line['file'].gsub('[PROJECT_ROOT]', '')
line_number = line['number']
link_to(text || file_name, "#{app.github_url_to_file(file_path)}#L#{line_number}", :target => '_blank')
end
-
+
end
\ No newline at end of file
diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb
index 6cb80ae..76c5a13 100644
--- a/app/mailers/mailer.rb
+++ b/app/mailers/mailer.rb
@@ -4,21 +4,21 @@ require Rails.root.join('config/routes.rb')
class Mailer < ActionMailer::Base
default :from => Errbit::Config.email_from
-
+
def err_notification(notice)
@notice = notice
- @app = notice.err.app
-
+ @app = notice.app
+
mail({
:to => @app.notification_recipients,
- :subject => "[#{@app.name}][#{@notice.err.environment}] #{@notice.err.message}"
+ :subject => "[#{@app.name}][#{@notice.environment_name}] #{@notice.message}"
})
end
-
+
def deploy_notification(deploy)
@deploy = deploy
- @app = deploy.app
-
+ @app = deploy.app
+
mail({
:to => @app.notification_recipients,
:subject => "[#{@app.name}] Deployed to #{@deploy.environment} by #{@deploy.username}"
diff --git a/app/models/app.rb b/app/models/app.rb
index 230bfbe..307fedb 100644
--- a/app/models/app.rb
+++ b/app/models/app.rb
@@ -1,7 +1,7 @@
class App
include Mongoid::Document
include Mongoid::Timestamps
-
+
field :name, :type => String
field :api_key
field :github_url
@@ -10,53 +10,65 @@ class App
field :notify_on_errs, :type => Boolean, :default => true
field :notify_on_deploys, :type => Boolean, :default => false
field :email_at_notices, :type => Array, :default => Errbit::Config.email_at_notices
-
+
# Some legacy apps may have string as key instead of BSON::ObjectID
identity :type => String
+
# There seems to be a Mongoid bug making it impossible to use String identity with references_many feature:
# https://github.com/mongoid/mongoid/issues/703
# Using 32 character string as a workaround.
before_create do |r|
r.id = ActiveSupport::SecureRandom.hex
end
-
+
embeds_many :watchers
embeds_many :deploys
embeds_one :issue_tracker
- has_many :errs, :inverse_of => :app, :dependent => :destroy
-
+ has_many :problems, :inverse_of => :app, :dependent => :destroy
+
before_validation :generate_api_key, :on => :create
before_save :normalize_github_url
-
+
validates_presence_of :name, :api_key
validates_uniqueness_of :name, :allow_blank => true
validates_uniqueness_of :api_key, :allow_blank => true
validates_associated :watchers
validate :check_issue_tracker
-
+
accepts_nested_attributes_for :watchers, :allow_destroy => true,
:reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? }
accepts_nested_attributes_for :issue_tracker, :allow_destroy => true,
:reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) }
-
+
+
+
+ def find_or_create_err!(attrs)
+ Err.where(attrs).first || problems.create!.errs.create!(attrs)
+ end
+
+
+
+ # Mongoid Bug: find(id) on association proxies returns an Enumerator
def self.find_by_id!(app_id)
find app_id
end
-
+
def self.find_by_api_key!(key)
where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key))
end
-
+
def last_deploy_at
deploys.last && deploys.last.created_at
end
-
+
+
# Legacy apps don't have notify_on_errs and notify_on_deploys params
def notify_on_errs
!(self[:notify_on_errs] == false)
end
alias :notify_on_errs? :notify_on_errs
-
+
+
def notify_on_deploys
!(self[:notify_on_deploys] == false)
end
@@ -117,4 +129,3 @@ class App
end
end
-
diff --git a/app/models/deploy.rb b/app/models/deploy.rb
index 49e0bc5..0d88d01 100644
--- a/app/models/deploy.rb
+++ b/app/models/deploy.rb
@@ -1,43 +1,43 @@
class Deploy
include Mongoid::Document
include Mongoid::Timestamps
-
+
field :username
field :repository
field :environment
field :revision
field :message
-
+
index :created_at, Mongo::DESCENDING
-
+
embedded_in :app, :inverse_of => :deploys
-
+
after_create :deliver_notification, :if => :should_notify?
after_create :resolve_app_errs, :if => :should_resolve_app_errs?
-
+
validates_presence_of :username, :environment
-
+
def deliver_notification
Mailer.deploy_notification(self).deliver
end
-
+
def resolve_app_errs
- app.errs.unresolved.in_env(environment).each {|err| err.resolve!}
+ app.problems.unresolved.in_env(environment).each {|problem| problem.resolve!}
end
-
+
def short_revision
revision.to_s[0,7]
end
-
+
protected
-
+
def should_notify?
app.notify_on_deploys? && app.watchers.any?
end
-
+
def should_resolve_app_errs?
app.resolve_errs_on_deploy?
end
-
+
end
diff --git a/app/models/err.rb b/app/models/err.rb
index dfdada6..e7af763 100644
--- a/app/models/err.rb
+++ b/app/models/err.rb
@@ -1,62 +1,25 @@
+# An Err is a group of notices that can programatically
+# be determined to be equal. (Errbit groups notices into
+# errs by a notice's fingerprint.)
+
class Err
include Mongoid::Document
include Mongoid::Timestamps
-
+
field :klass
field :component
field :action
field :environment
field :fingerprint
- field :last_notice_at, :type => DateTime
- field :resolved, :type => Boolean, :default => false
- field :issue_link, :type => String
- field :notices_count, :type => Integer, :default => 0
- field :message
-
- index :last_notice_at
- index :app_id
- index :notices
-
- belongs_to :app
- has_many :notices
- has_many :comments, :inverse_of => :err, :dependent => :destroy
-
- validates_presence_of :klass, :environment
-
- scope :resolved, where(:resolved => true)
- scope :unresolved, where(:resolved => false)
- scope :ordered, order_by(:last_notice_at.desc)
- scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))}
-
- def self.in_env(env)
- env.present? ? where(:environment => env) : scoped
- end
-
- def self.for(attrs)
- app = attrs.delete(:app)
- app.errs.where(attrs).first || app.errs.create!(attrs)
- end
-
- def resolve!
- self.update_attributes!(:resolved => true)
- end
-
- def unresolve!
- self.update_attributes!(:resolved => false)
- end
-
- def unresolved?
- !resolved?
- end
-
- def where
- where = component.dup
- where << "##{action}" if action.present?
- where
- end
-
- def message
- super || klass
- end
-
+
+ belongs_to :problem
+ has_many :notices, :inverse_of => :err, :dependent => :destroy
+
+ validates_presence_of :klass, :environment, :problem
+
+ delegate :app,
+ :resolved?,
+ :to => :problem
+
+
end
diff --git a/app/models/issue_tracker.rb b/app/models/issue_tracker.rb
index 1392312..0e78391 100644
--- a/app/models/issue_tracker.rb
+++ b/app/models/issue_tracker.rb
@@ -19,8 +19,8 @@ class IssueTracker
# Subclasses are responsible for overwriting this method.
def check_params; true; end
- def issue_title err
- "[#{ err.environment }][#{ err.where }] #{err.message.to_s.truncate(100)}"
+ def issue_title(problem)
+ "[#{ problem.environment }][#{ problem.where }] #{problem.message.to_s.truncate(100)}"
end
# Allows us to set the issue tracker class from a single form.
diff --git a/app/models/issue_trackers/fogbugz_tracker.rb b/app/models/issue_trackers/fogbugz_tracker.rb
index fffa9ef..94b0c38 100644
--- a/app/models/issue_trackers/fogbugz_tracker.rb
+++ b/app/models/issue_trackers/fogbugz_tracker.rb
@@ -22,19 +22,19 @@ class IssueTrackers::FogbugzTracker < IssueTracker
end
end
- def create_issue(err)
+ def create_issue(problem)
fogbugz = Fogbugz::Interface.new(:email => username, :password => password, :uri => "https://#{account}.fogbugz.com")
fogbugz.authenticate
issue = {}
- issue['sTitle'] = issue_title err
+ issue['sTitle'] = issue_title problem
issue['sArea'] = project_id
issue['sEvent'] = body_template.result(binding)
issue['sTags'] = ['errbit'].join(',')
issue['cols'] = ['ixBug'].join(',')
fb_resp = fogbugz.command(:new, issue)
- err.update_attribute :issue_link, "https://#{account}.fogbugz.com/default.asp?#{fb_resp['case']['ixBug']}"
+ problem.update_attribute :issue_link, "https://#{account}.fogbugz.com/default.asp?#{fb_resp['case']['ixBug']}"
end
def body_template
diff --git a/app/models/issue_trackers/lighthouse_tracker.rb b/app/models/issue_trackers/lighthouse_tracker.rb
index bc3a8c3..f574d59 100644
--- a/app/models/issue_trackers/lighthouse_tracker.rb
+++ b/app/models/issue_trackers/lighthouse_tracker.rb
@@ -18,20 +18,20 @@ class IssueTrackers::LighthouseTracker < IssueTracker
end
end
- def create_issue(err)
+ def create_issue(problem)
Lighthouse.account = account
Lighthouse.token = api_token
# updating lighthouse account
Lighthouse::Ticket.site
ticket = Lighthouse::Ticket.new(:project_id => project_id)
- ticket.title = issue_title err
+ ticket.title = issue_title problem
ticket.body = body_template.result(binding)
ticket.tags << "errbit"
ticket.save!
- err.update_attribute :issue_link, "#{Lighthouse::Ticket.site.to_s.sub(/#{Lighthouse::Ticket.site.path}$/, '')}#{Lighthouse::Ticket.element_path(ticket.id, :project_id => project_id)}".sub(/\.xml$/, '')
+ problem.update_attribute :issue_link, "#{Lighthouse::Ticket.site.to_s.sub(/#{Lighthouse::Ticket.site.path}$/, '')}#{Lighthouse::Ticket.element_path(ticket.id, :project_id => project_id)}".sub(/\.xml$/, '')
end
def body_template
diff --git a/app/models/issue_trackers/mingle_tracker.rb b/app/models/issue_trackers/mingle_tracker.rb
index 9997b57..419ad37 100644
--- a/app/models/issue_trackers/mingle_tracker.rb
+++ b/app/models/issue_trackers/mingle_tracker.rb
@@ -27,21 +27,21 @@ class IssueTrackers::MingleTracker < IssueTracker
end
end
- def create_issue(err)
+ def create_issue(problem)
properties = ticket_properties_hash
basic_auth = account.gsub(/https?:\/\//, "https://#{username}:#{password}@")
Mingle.set_site "#{basic_auth}/api/v1/projects/#{project_id}/"
card = Mingle::Card.new
card.card_type_name = properties.delete("card_type")
- card.name = issue_title(err)
+ card.name = issue_title(problem)
card.description = body_template.result(binding)
properties.each do |property, value|
card.send("cp_#{property}=", value)
end
card.save!
- err.update_attribute :issue_link, URI.parse("#{account}/projects/#{project_id}/cards/#{card.id}").to_s
+ problem.update_attribute :issue_link, URI.parse("#{account}/projects/#{project_id}/cards/#{card.id}").to_s
end
def body_template
diff --git a/app/models/issue_trackers/pivotal_labs_tracker.rb b/app/models/issue_trackers/pivotal_labs_tracker.rb
index c33dba5..c7ab6d6 100644
--- a/app/models/issue_trackers/pivotal_labs_tracker.rb
+++ b/app/models/issue_trackers/pivotal_labs_tracker.rb
@@ -13,12 +13,12 @@ class IssueTrackers::PivotalLabsTracker < IssueTracker
end
end
- def create_issue(err)
+ def create_issue(problem)
PivotalTracker::Client.token = api_token
PivotalTracker::Client.use_ssl = true
project = PivotalTracker::Project.find project_id.to_i
- story = project.stories.create :name => issue_title(err), :story_type => 'bug', :description => body_template.result(binding)
- err.update_attribute :issue_link, "https://www.pivotaltracker.com/story/show/#{story.id}"
+ story = project.stories.create :name => issue_title(problem), :story_type => 'bug', :description => body_template.result(binding)
+ problem.update_attribute :issue_link, "https://www.pivotaltracker.com/story/show/#{story.id}"
end
def body_template
diff --git a/app/models/issue_trackers/redmine_tracker.rb b/app/models/issue_trackers/redmine_tracker.rb
index 9eeb134..099badf 100644
--- a/app/models/issue_trackers/redmine_tracker.rb
+++ b/app/models/issue_trackers/redmine_tracker.rb
@@ -17,7 +17,7 @@ class IssueTrackers::RedmineTracker < IssueTracker
end
end
- def create_issue(err)
+ def create_issue(problem)
token = api_token
acc = account
RedmineClient::Base.configure do
@@ -25,10 +25,10 @@ class IssueTrackers::RedmineTracker < IssueTracker
self.site = acc
end
issue = RedmineClient::Issue.new(:project_id => project_id)
- issue.subject = issue_title err
+ issue.subject = issue_title problem
issue.description = body_template.result(binding)
issue.save!
- err.update_attribute :issue_link, "#{RedmineClient::Issue.site.to_s.sub(/#{RedmineClient::Issue.site.path}$/, '')}#{RedmineClient::Issue.element_path(issue.id, :project_id => project_id)}".sub(/\.xml\?project_id=#{project_id}$/, "\?project_id=#{project_id}")
+ problem.update_attribute :issue_link, "#{RedmineClient::Issue.site.to_s.sub(/#{RedmineClient::Issue.site.path}$/, '')}#{RedmineClient::Issue.element_path(issue.id, :project_id => project_id)}".sub(/\.xml\?project_id=#{project_id}$/, "\?project_id=#{project_id}")
end
def body_template
diff --git a/app/models/notice.rb b/app/models/notice.rb
index ccfa2bc..af10548 100644
--- a/app/models/notice.rb
+++ b/app/models/notice.rb
@@ -4,46 +4,50 @@ require 'recurse'
class Notice
include Mongoid::Document
include Mongoid::Timestamps
-
+
field :message
field :backtrace, :type => Array
field :server_environment, :type => Hash
field :request, :type => Hash
field :notifier, :type => Hash
-
+ field :klass
+
belongs_to :err
index :err_id
-
- after_create :cache_last_notice_at
+ index :created_at
+
after_create :deliver_notification, :if => :should_notify?
- before_create :increase_counter_cache, :cache_message
+ after_create :increase_counter_cache, :cache_attributes_on_problem
before_save :sanitize
before_destroy :decrease_counter_cache
-
+
validates_presence_of :backtrace, :server_environment, :notifier
-
+
scope :ordered, order_by(:created_at.asc)
- index :created_at
-
+ scope :for_errs, lambda {|errs| where(:err_id.in => errs.all.map(&:id))}
+
+ delegate :app, :problem, :to => :err
+
+
+
def self.from_xml(hoptoad_xml)
hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml)
app = App.find_by_api_key!(hoptoad_notice['api-key'])
-
+
hoptoad_notice['request'] ||= {}
hoptoad_notice['request']['component'] = 'unknown' if hoptoad_notice['request']['component'].blank?
hoptoad_notice['request']['action'] = nil if hoptoad_notice['request']['action'].blank?
-
- err = Err.for({
- :app => app,
- :klass => hoptoad_notice['error']['class'],
- :component => hoptoad_notice['request']['component'],
- :action => hoptoad_notice['request']['action'],
- :environment => hoptoad_notice['server-environment']['environment-name'],
- :fingerprint => hoptoad_notice['fingerprint']
+
+ err = app.find_or_create_err!({
+ :klass => hoptoad_notice['error']['class'],
+ :component => hoptoad_notice['request']['component'],
+ :action => hoptoad_notice['request']['action'],
+ :environment => hoptoad_notice['server-environment']['environment-name'],
+ :fingerprint => hoptoad_notice['fingerprint']
})
- err.update_attributes(:resolved => false) if err.resolved?
-
+ err.problem.update_attributes(:resolved => false) if err.problem.resolved?
err.notices.create!({
+ :klass => hoptoad_notice['error']['class'],
:message => hoptoad_notice['error']['message'],
:backtrace => [hoptoad_notice['error']['backtrace']['line']].flatten,
:server_environment => hoptoad_notice['server-environment'],
@@ -51,64 +55,97 @@ class Notice
:notifier => hoptoad_notice['notifier']
})
end
-
+
+
+
def user_agent
agent_string = env_vars['HTTP_USER_AGENT']
agent_string.blank? ? nil : UserAgent.parse(agent_string)
end
-
+
+ def environment_name
+ server_environment['server-environment'] || server_environment['environment-name']
+ end
+
+ def component
+ request['component']
+ end
+
+ def action
+ request['action']
+ end
+
+ def where
+ where = component.to_s.dup
+ where << "##{action}" if action.present?
+ where
+ end
+
+
+
def self.in_app_backtrace_line? line
!!(line['file'] =~ %r{^\[PROJECT_ROOT\]/(?!(vendor))})
end
-
+
+
+
def request
read_attribute(:request) || {}
end
-
+
def env_vars
request['cgi-data'] || {}
end
-
+
def params
request['params'] || {}
end
-
+
def session
request['session'] || {}
end
-
+
+
+
def deliver_notification
Mailer.err_notification(self).deliver
end
-
+
def cache_last_notice_at
err.update_attributes(:last_notice_at => created_at)
end
-
+
# Backtrace containing only files from the app itself (ignore gems)
def app_backtrace
backtrace.select { |l| l && l['file'] && l['file'].include?("[PROJECT_ROOT]") }
end
-
- protected
-
+
+
+
+protected
+
+
+
def should_notify?
- err.app.notify_on_errs? && (Errbit::Config.per_app_email_at_notices && err.app.email_at_notices || Errbit::Config.email_at_notices).include?(err.notices.count) && err.app.watchers.any?
+ app.notify_on_errs? && (Errbit::Config.per_app_email_at_notices && app.email_at_notices || Errbit::Config.email_at_notices).include?(err.notices.count) && app.watchers.any?
end
-
-
+
+
def increase_counter_cache
- err.inc(:notices_count,1)
+ problem.inc(:notices_count, 1)
end
-
+
+
def decrease_counter_cache
- err.inc(:notices_count,-1)
+ problem.inc(:notices_count, -1)
end
-
- def cache_message
- err.update_attribute(:message, message) if err.notices_count == 1
+
+
+ def cache_attributes_on_problem
+ problem.cache_notice_attributes(self) if problem.notices_count == 1
end
-
+
+
def sanitize
[:server_environment, :request, :notifier].each do |h|
send("#{h}=",sanitize_hash(send(h)))
@@ -116,7 +153,8 @@ class Notice
# Set unknown backtrace files
backtrace.each{|line| line['file'] = "[unknown source]" if line['file'].blank? }
end
-
+
+
def sanitize_hash(h)
h.recurse do
|h| h.inject({}) do |h,(k,v)|
@@ -129,5 +167,6 @@ class Notice
end
end
end
+
+
end
-
diff --git a/app/models/problem.rb b/app/models/problem.rb
new file mode 100644
index 0000000..7546225
--- /dev/null
+++ b/app/models/problem.rb
@@ -0,0 +1,70 @@
+# An Problem is a group of errs that the user
+# has declared to be equal.
+
+class Problem
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ field :last_notice_at, :type => DateTime
+ field :resolved, :type => Boolean, :default => false
+ field :issue_link, :type => String
+
+ # Cached fields
+ field :notices_count, :type => Integer, :default => 0
+ field :message
+ field :environment
+ field :klass
+ field :where
+
+ index :last_notice_at
+ index :app_id
+
+ belongs_to :app
+ has_many :errs, :inverse_of => :problem, :dependent => :destroy
+ has_many :comments, :inverse_of => :err, :dependent => :destroy
+
+ scope :resolved, where(:resolved => true)
+ scope :unresolved, where(:resolved => false)
+ scope :ordered, order_by(:last_notice_at.desc)
+ scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))}
+
+ def self.in_env(env)
+ env.present? ? where(:environment => env) : scoped
+ end
+
+
+
+ def notices
+ Notice.for_errs(errs).ordered
+ end
+
+
+ def resolve!
+ self.update_attributes!(:resolved => true)
+ end
+
+
+ def unresolve!
+ self.update_attributes!(:resolved => false)
+ end
+
+
+ def unresolved?
+ !resolved?
+ end
+
+
+
+ def cache_notice_attributes(notice=nil)
+ notice ||= notices.first
+ attrs = {:last_notice_at => notices.max(:created_at)}
+ attrs.merge!(
+ :message => notice.message,
+ :environment => notice.environment_name,
+ :klass => notice.klass,
+ :where => notice.where) if notice
+ update_attributes!(attrs)
+ end
+
+
+end
\ No newline at end of file
diff --git a/app/views/apps/index.html.haml b/app/views/apps/index.html.haml
index f5d476f..f21121e 100644
--- a/app/views/apps/index.html.haml
+++ b/app/views/apps/index.html.haml
@@ -14,7 +14,7 @@
%td.name= link_to app.name, app_path(app)
%td.deploy= app.last_deploy_at ? link_to( app.last_deploy_at.to_s(:micro) << " (#{app.deploys.last.short_revision})", app_deploys_path(app)) : 'n/a'
%td.count
- - if app.errs.count > 0
+ - if app.problems.count > 0
- unresolved = @unresolved_counts[app.id]
= link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil)
- else
diff --git a/app/views/apps/show.html.haml b/app/views/apps/show.html.haml
index 5ba3050..d917fcc 100644
--- a/app/views/apps/show.html.haml
+++ b/app/views/apps/show.html.haml
@@ -4,7 +4,7 @@
= javascript_include_tag 'apps.show'
- content_for :meta do
%strong Errs Caught:
- = @app.errs.count
+ = @app.problems.count
%strong Deploy Count:
= @app.deploys.count
%strong API Key:
@@ -82,9 +82,9 @@
- else
%h3 No deploys
-- if @app.errs.count > 0
+- if @app.problems.any?
%h3.clear Errs
- = render 'errs/table', :errs => @errs
+ = render 'errs/table', :errs => @problems
- else
%h3.clear No errs have been caught yet, make sure you setup your app
= render 'configuration_instructions', :app => @app
diff --git a/app/views/errs/_list.atom.builder b/app/views/errs/_list.atom.builder
index f9d8ef0..30207a6 100644
--- a/app/views/errs/_list.atom.builder
+++ b/app/views/errs/_list.atom.builder
@@ -1,12 +1,12 @@
-feed.updated(@errs.first.created_at)
+feed.updated(@problems.first.created_at)
-for err in @errs
- notice = err.notices.first
+for problem in @problems
+ notice = problem.notices.first
- feed.entry(err, :url => app_err_url(err.app, err)) do |entry|
- entry.title "[#{ err.where }] #{err.message.to_s.truncate(27)}"
+ feed.entry(problem, :url => app_err_url(problem.app, problem)) do |entry|
+ entry.title "[#{ problem.where }] #{problem.message.to_s.truncate(27)}"
entry.author do |author|
- author.name "#{ err.app.name } [#{ err.environment }]"
+ author.name "#{ problem.app.name } [#{ problem.environment }]"
end
if notice
entry.summary(notice_atom_summary(notice), :type => "html")
diff --git a/app/views/errs/_table.html.haml b/app/views/errs/_table.html.haml
index 40ffba3..aacffd0 100644
--- a/app/views/errs/_table.html.haml
+++ b/app/views/errs/_table.html.haml
@@ -10,28 +10,28 @@
%th Count
%th Resolve
%tbody
- - errs.each do |err|
- %tr{:class => err.resolved? ? 'resolved' : 'unresolved'}
+ - errs.each do |problem|
+ %tr{:class => problem.resolved? ? 'resolved' : 'unresolved'}
%td.select
- = check_box_tag "errs[]", err.id, @selected_errs.member?(err.id.to_s)
+ = check_box_tag "problems[]", problem.id, @selected_problems.member?(problem.id.to_s)
%td.app
- = link_to err.app.name, app_path(err.app)
+ = link_to problem.app.name, app_path(problem.app)
- if current_page?(:controller => 'errs')
- %span.environment= link_to err.environment, errs_path(environment: err.environment)
+ %span.environment= link_to problem.environment, errs_path(environment: problem.environment)
- else
- %span.environment= link_to err.environment, app_path(err.app, environment: err.environment)
+ %span.environment= link_to problem.environment, app_path(problem.app, environment: problem.environment)
%td.message
- = link_to err.message, app_err_path(err.app, err)
- %em= err.where
- %td.latest #{time_ago_in_words(last_notice_at err)} ago
- %td.deploy= err.app.last_deploy_at ? err.app.last_deploy_at.to_s(:micro) : 'n/a'
- %td.count= link_to err.notices.count, app_err_path(err.app, err)
- %td.resolve= link_to image_tag("thumbs-up.png"), resolve_app_err_path(err.app, err), :title => "Resolve", :method => :put, :confirm => err_confirm, :class => 'resolve' if err.unresolved?
+ = link_to problem.message, app_err_path(problem.app, problem)
+ %em= problem.where
+ %td.latest #{time_ago_in_words(last_notice_at problem)} ago
+ %td.deploy= problem.app.last_deploy_at ? problem.app.last_deploy_at.to_s(:micro) : 'n/a'
+ %td.count= link_to problem.notices.count, app_err_path(problem.app, problem)
+ %td.resolve= link_to image_tag("thumbs-up.png"), resolve_app_err_path(problem.app, problem), :title => "Resolve", :method => :put, :confirm => err_confirm, :class => 'resolve' if problem.unresolved?
- if errs.none?
%tr
%td{:colspan => (@app ? 5 : 6)}
%em No errs here
- = will_paginate @errs, :previous_label => '« Previous', :next_label => 'Next »'
+ = will_paginate @problems, :previous_label => '« Previous', :next_label => 'Next »'
.tab-bar
%ul
%li= submit_tag 'Resolve', :id => 'resolve_errs', :class => 'button', 'data-action' => resolve_several_errs_path
diff --git a/app/views/errs/all.html.haml b/app/views/errs/all.html.haml
index 22ce846..06b41cc 100644
--- a/app/views/errs/all.html.haml
+++ b/app/views/errs/all.html.haml
@@ -1,4 +1,4 @@
- content_for :title, 'All Errs'
- content_for :action_bar do
= link_to 'hide resolved', errs_path, :class => 'button'
-= render 'table', :errs => @errs
\ No newline at end of file
+= render 'table', :errs => @problems
\ No newline at end of file
diff --git a/app/views/errs/index.html.haml b/app/views/errs/index.html.haml
index 75315bd..f4db666 100644
--- a/app/views/errs/index.html.haml
+++ b/app/views/errs/index.html.haml
@@ -3,4 +3,4 @@
= auto_discovery_link_tag :atom, errs_url(User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices at #{root_url}"
- content_for :action_bar do
= link_to 'show resolved', all_errs_path, :class => 'button'
-= render 'table', :errs => @errs
\ No newline at end of file
+= render 'table', :errs => @problems
\ No newline at end of file
diff --git a/app/views/errs/show.html.haml b/app/views/errs/show.html.haml
index 7fce9a0..94edfb7 100644
--- a/app/views/errs/show.html.haml
+++ b/app/views/errs/show.html.haml
@@ -1,40 +1,40 @@
-- content_for :page_title, @err.message
-- content_for :title, @err.klass
+- content_for :page_title, @problem.message
+- content_for :title, @problem.klass
- content_for :meta do
%strong App:
= link_to @app.name, app_path(@app)
%strong Where:
- = @err.where
+ = @problem.where
%br
%strong Environment:
- = @err.environment
+ = @problem.environment
%strong Last Notice:
- = last_notice_at(@err).to_s(:micro)
+ = last_notice_at(@problem).to_s(:micro)
- content_for :action_bar do
- - if @err.app.issue_tracker_configured?
- - if @err.issue_link.blank?
- %span= link_to 'create issue', create_issue_app_err_path(@app, @err), :method => :post, :class => "#{@app.issue_tracker.class::Label}_create create-issue"
+ - if @problem.app.issue_tracker_configured?
+ - if @problem.issue_link.blank?
+ %span= link_to 'create issue', create_issue_app_err_path(@app, @problem), :method => :post, :class => "#{@app.issue_tracker.class::Label}_create create-issue"
- else
- %span= link_to 'go to issue', @err.issue_link, :target => "_blank", :class => "#{@app.issue_tracker.class::Label}_goto goto-issue"
- = link_to 'unlink issue', unlink_issue_app_err_path(@app, @err), :method => :delete, :confirm => "Unlink err issues?", :class => "unlink-issue"
- - if @err.unresolved?
- %span= link_to 'resolve', resolve_app_err_path(@app, @err), :method => :put, :confirm => err_confirm, :class => 'resolve'
+ %span= link_to 'go to issue', @problem.issue_link, :class => "#{@app.issue_tracker.class::Label}_goto goto-issue"
+ = link_to 'unlink issue', unlink_issue_app_err_path(@app, @problem), :method => :delete, :confirm => "Unlink err issues?", :class => "unlink-issue"
+ - if @problem.unresolved?
+ %span= link_to 'resolve', resolve_app_err_path(@app, @problem), :method => :put, :confirm => err_confirm, :class => 'resolve'
-- if !@app.issue_tracker_configured? || @err.comments.any?
+- if !@app.issue_tracker_configured? || @problem.comments.any?
- content_for :comments do
%h3 Comments on this Err
- - @err.comments.each do |comment|
+ - @problem.comments.each do |comment|
.window
%table.comment
%tr
%th
- %span= link_to '✘'.html_safe, destroy_comment_app_err_path(@app, @err) << "?comment_id=#{comment.id}", :method => :delete, :confirm => "Are sure you don't need this comment?", :class => "destroy-comment"
+ %span= link_to '✘'.html_safe, destroy_comment_app_err_path(@app, @problem) << "?comment_id=#{comment.id}", :method => :delete, :confirm => "Are sure you don't need this comment?", :class => "destroy-comment"
= time_ago_in_words(comment.created_at, true) << " ago by "
= link_to comment.user.email, user_path(comment.user)
%tr
%td= comment.body.gsub("\n", "
").html_safe
- unless @app.issue_tracker_configured?
- = form_for @comment, :url => create_comment_app_err_path(@app, @err) do |comment_form|
+ = form_for @comment, :url => create_comment_app_err_path(@app, @problem) do |comment_form|
%p Add a comment
= comment_form.text_area :body, :style => "width: 420px; height: 80px;"
= comment_form.submit "Save Comment"
diff --git a/app/views/issue_trackers/fogbugz_body.txt.erb b/app/views/issue_trackers/fogbugz_body.txt.erb
index d85580d..85a5364 100644
--- a/app/views/issue_trackers/fogbugz_body.txt.erb
+++ b/app/views/issue_trackers/fogbugz_body.txt.erb
@@ -1,16 +1,16 @@
-"See this exception on Errbit": <%= app_err_url(err.app, err) %>
-<% if notice = err.notices.first %>
+"See this exception on Errbit": <%= app_err_url(problem.app, problem) %>
+<% if notice = problem.notices.first %>
<%= notice.message %>
Summary
- Where
- <%= notice.err.where %>
+ <%= notice.where %>
- Occured
<%= notice.created_at.to_s(:micro) %>
- Similar
- <%= (notice.err.notices_count - 1).to_s %>
+ <%= (notice.problem.notices_count - 1).to_s %>
Params
<%= pretty_hash(notice.params) %>
diff --git a/app/views/issue_trackers/github_issues_body.txt.erb b/app/views/issue_trackers/github_issues_body.txt.erb
index 1887ca0..0d52990 100644
--- a/app/views/issue_trackers/github_issues_body.txt.erb
+++ b/app/views/issue_trackers/github_issues_body.txt.erb
@@ -7,13 +7,13 @@
[<%= notice.request['url'] %>](<%= notice.request['url'] %>)"
<% end %>
### Where ###
-<%= notice.err.where %>
+<%= notice.where %>
### Occured ###
<%= notice.created_at.to_s(:micro) %>
### Similar ###
-<%= (notice.err.notices_count - 1).to_s %>
+<%= (notice.problem.notices_count - 1).to_s %>
## Params ##
```
diff --git a/app/views/issue_trackers/lighthouseapp_body.txt.erb b/app/views/issue_trackers/lighthouseapp_body.txt.erb
index 00bb9d8..713711e 100644
--- a/app/views/issue_trackers/lighthouseapp_body.txt.erb
+++ b/app/views/issue_trackers/lighthouseapp_body.txt.erb
@@ -1,5 +1,5 @@
-[See this exception on Errbit](<%= app_err_url err.app, err %> "See this exception on Errbit")
-<% if notice = err.notices.first %>
+[See this exception on Errbit](<%= app_err_url problem.app, problem %> "See this exception on Errbit")
+<% if notice = problem.notices.first %>
# <%= notice.message %> #
## Summary ##
<% if notice.request['url'].present? %>
@@ -7,13 +7,13 @@
[<%= notice.request['url'] %>](<%= notice.request['url'] %>)"
<% end %>
### Where ###
- <%= notice.err.where %>
+ <%= notice.where %>
### Occured ###
<%= notice.created_at.to_s(:micro) %>
### Similar ###
- <%= (notice.err.notices_count - 1).to_s %>
+ <%= (notice.problem.notices_count - 1).to_s %>
## Params ##
<%= pretty_hash(notice.params) %>
diff --git a/app/views/issue_trackers/pivotal_body.txt.erb b/app/views/issue_trackers/pivotal_body.txt.erb
index ee46752..e10b497 100644
--- a/app/views/issue_trackers/pivotal_body.txt.erb
+++ b/app/views/issue_trackers/pivotal_body.txt.erb
@@ -1,9 +1,9 @@
-See this exception on Errbit: <%= app_err_url err.app, err %>
-<% if notice = err.notices.first %>
+See this exception on Errbit: <%= app_err_url problem.app, problem %>
+<% if notice = problem.notices.first %>
<% if notice.request['url'].present? %>URL: <%= notice.request['url'] %><% end %>
- Where: <%= notice.err.where %>
+ Where: <%= notice.where %>
Occurred: <%= notice.created_at.to_s :micro %>
- Similar: <%= (notice.err.notices.count - 1).to_s %>
+ Similar: <%= (notice.problem.notices.count - 1).to_s %>
Params:
<%= pretty_hash notice.params %>
diff --git a/app/views/issue_trackers/textile_body.txt.erb b/app/views/issue_trackers/textile_body.txt.erb
index 99c590e..82bd6fa 100644
--- a/app/views/issue_trackers/textile_body.txt.erb
+++ b/app/views/issue_trackers/textile_body.txt.erb
@@ -1,7 +1,7 @@
-<% if notice = err.notices.first %>
+<% if notice = problem.notices.first %>
h1. <%= notice.message %>
-h3. "See this exception on Errbit":<%= app_err_url err.app, err %>
+h3. "See this exception on Errbit":<%= app_err_url problem.app, problem %>
h2. Summary
<% if notice.request['url'].present? %>
@@ -11,7 +11,7 @@ h3. URL
<% end %>
h3. Where
-<%= notice.err.where %>
+<%= notice.where %>
h3. Occurred
@@ -19,7 +19,7 @@ h3. Occurred
h3. Similar
-<%= (notice.err.notices_count - 1).to_s %>
+<%= (notice.problem.notices_count - 1).to_s %>
h2. Params
diff --git a/app/views/mailer/err_notification.html.haml b/app/views/mailer/err_notification.html.haml
index 00bbc4a..ea0103b 100644
--- a/app/views/mailer/err_notification.html.haml
+++ b/app/views/mailer/err_notification.html.haml
@@ -9,12 +9,12 @@
An err has just occurred in
= link_to(@app.name, app_url(@app), :class => "bold") << ","
on the
- %span.bold= @notice.err.environment
+ %span.bold= @notice.environment_name
environment.
%br
- This err has occurred #{pluralize @notice.err.notices_count, 'time'}.
+ This err has occurred #{pluralize @notice.problem.notices_count, 'time'}.
%p
- = link_to("Click here to view the error on Errbit", app_err_url(@app, @notice.err), :class => "bold") << "."
+ = link_to("Click here to view the error on Errbit", app_err_url(@app, @notice.problem), :class => "bold") << "."
%tr
%td.section
%table(cellpadding="0" cellspacing="0" border="0" align="left")
@@ -23,10 +23,10 @@
%td.content(valign="top")
%div
%p.heading ERROR MESSAGE:
- %p= @notice.err.message
+ %p= @notice.message
%p.heading WHERE:
%p.monospace
- = @notice.err.where
+ = @notice.where
- if (app_backtrace = @notice.app_backtrace).any?
- app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line|
%p.backtrace= line
diff --git a/app/views/mailer/err_notification.text.erb b/app/views/mailer/err_notification.text.erb
index 5b07401..33add18 100644
--- a/app/views/mailer/err_notification.text.erb
+++ b/app/views/mailer/err_notification.text.erb
@@ -1,18 +1,18 @@
-An err has just occurred in <%= @app.name %>, on the <%= @notice.err.environment %> environment.
+An err has just occurred in <%= @notice.environment_name %>: <%= raw(@notice.message) %>
-This err has occurred <%= pluralize @notice.err.notices_count, 'time' %>.
+This err has occurred <%= pluralize @notice.problem.notices_count, 'time' %>. You should really look into it here:
-You can view it on Errbit here: <%= app_err_url(@app, @notice.err) %>
+ <%= app_err_url(@app, @notice.problem) %>
ERROR MESSAGE:
-<%= raw(@notice.err.message) %>
+<%= raw(@notice.message) %>
WHERE:
-<%= @notice.err.where %>
+<%= @notice.where %>
<% @notice.app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| %>
<%= line %>
diff --git a/app/views/notices/_atom_entry.html.haml b/app/views/notices/_atom_entry.html.haml
index b11e198..942fcf7 100644
--- a/app/views/notices/_atom_entry.html.haml
+++ b/app/views/notices/_atom_entry.html.haml
@@ -6,13 +6,13 @@
= link_to(notice.request['url'], notice.request['url'])
%p
%strong Where:
- = notice.err.where
+ = notice.where
%p
%strong Occured:
= notice.created_at.to_s(:micro)
%p
%strong Similar:
- = notice.err.notices_count - 1
+ = notice.problem.notices_count - 1
%h3 Params
%p= pretty_hash(notice.params)
diff --git a/app/views/notices/_summary.html.haml b/app/views/notices/_summary.html.haml
index 904b486..91214d1 100644
--- a/app/views/notices/_summary.html.haml
+++ b/app/views/notices/_summary.html.haml
@@ -9,13 +9,13 @@
%td.nowrap= link_to notice.request['url'], notice.request['url']
%tr
%th Where
- %td= notice.err.where
+ %td= notice.where
%tr
%th Occurred
%td= notice.created_at.to_s(:micro)
%tr
%th Similar
- %td= notice.err.notices.count - 1
+ %td= notice.problem.notices.count - 1
%tr
%th Browser
- %td= user_agent_graph(notice.err)
+ %td= user_agent_graph(notice.problem)
diff --git a/db/migrate/20110422152027_move_notices_to_separate_collection.rb b/db/migrate/20110422152027_move_notices_to_separate_collection.rb
index 32e914c..d04953e 100644
--- a/db/migrate/20110422152027_move_notices_to_separate_collection.rb
+++ b/db/migrate/20110422152027_move_notices_to_separate_collection.rb
@@ -17,10 +17,9 @@ class MoveNoticesToSeparateCollection < Mongoid::Migration
mongo_db.collection("errs").update({ "_id" => err['_id']}, { "$unset" => { "notices" => 1}})
end
Rake::Task["errbit:db:update_notices_count"].invoke
- Rake::Task["errbit:db:update_err_message"].invoke
+ Rake::Task["errbit:db:update_problem_attrs"].invoke
end
-
+
def self.down
end
-
end
diff --git a/db/migrate/20110905134638_link_errs_to_problems.rb b/db/migrate/20110905134638_link_errs_to_problems.rb
new file mode 100644
index 0000000..b81fa77
--- /dev/null
+++ b/db/migrate/20110905134638_link_errs_to_problems.rb
@@ -0,0 +1,27 @@
+class LinkErrsToProblems < Mongoid::Migration
+ def self.up
+
+ # Copy err.klass to notice.klass
+ Notice.all.each do |notice|
+ if notice.err && (klass = notice.err['klass'])
+ notice.update_attribute(:klass, klass)
+ end
+ end
+
+ # Create a Problem for each Err
+ Err.all.each do |err|
+ app_id = err['app_id']
+ app = app_id && App.find(app_id)
+ if app
+ err.problem = app.problems.create
+ err.save
+ end
+ end
+
+ Rake::Task["errbit:db:update_notices_count"].invoke
+ Rake::Task["errbit:db:update_problem_attrs"].invoke
+ end
+
+ def self.down
+ end
+end
\ No newline at end of file
diff --git a/lib/tasks/errbit/database.rake b/lib/tasks/errbit/database.rake
index 37bb603..8f0b287 100644
--- a/lib/tasks/errbit/database.rake
+++ b/lib/tasks/errbit/database.rake
@@ -1,27 +1,25 @@
namespace :errbit do
namespace :db do
- desc "Updates Err#notices_count"
- task :update_err_message => :environment do
- puts "Updating err.message"
- Err.all.each do |e|
- e.update_attributes(:message => e.notices.first.message) if e.notices.first
- end
+
+ desc "Updates cached attributes on Problem"
+ task :update_problem_attrs => :environment do
+ puts "Updating problems"
+ Problem.all.each(&:cache_notice_attributes)
end
-
- desc "Updates Err#notices_count"
+
+ desc "Updates Problem#notices_count"
task :update_notices_count => :environment do
- puts "Updating err.notices_count"
- Err.all.each do |e|
- e.update_attributes(:notices_count => e.notices.count)
+ puts "Updating problem.notices_count"
+ Problem.all.each do |p|
+ p.update_attributes(:notices_count => p.notices.count)
end
end
-
+
desc "Delete resolved errors from the database. (Useful for limited heroku databases)"
task :clear_resolved => :environment do
- count = Err.resolved.count
- Err.resolved.each {|err| err.destroy }
+ count = Problem.resolved.count
+ Problem.resolved.each {|problem| problem.destroy }
puts "=== Cleared #{count} resolved errors from the database." if count > 0
end
end
end
-
diff --git a/lib/tasks/errbit/demo.rake b/lib/tasks/errbit/demo.rake
index 13400aa..dfcb3ff 100644
--- a/lib/tasks/errbit/demo.rake
+++ b/lib/tasks/errbit/demo.rake
@@ -4,7 +4,7 @@ namespace :errbit do
require 'factory_girl_rails'
Dir.glob(File.join(Rails.root,'spec/factories/*.rb')).each {|f| require f }
app = Factory(:app, :name => "Demo App #{Time.now.strftime("%N")}")
- Factory(:notice, :err => Factory(:err, :app => app))
+ Factory(:notice, :err => Factory(:err, :problem => Factory(:problem, :app => app)))
puts "=== Created demo app: '#{app.name}', with an example error."
end
end
diff --git a/public/javascripts/application.js b/public/javascripts/application.js
index 1879954..fe792f2 100644
--- a/public/javascripts/application.js
+++ b/public/javascripts/application.js
@@ -62,7 +62,7 @@ $(function() {
function activateSelectableRows() {
$('.selectable tr').click(function(event) {
if(!_.include(['A', 'INPUT', 'BUTTON', 'TEXTAREA'], event.target.nodeName)) {
- var checkbox = $(this).find('input[name="errs[]"]');
+ var checkbox = $(this).find('input[name="problems[]"]');
checkbox.attr('checked', !checkbox.is(':checked'));
}
})
diff --git a/spec/controllers/apps_controller_spec.rb b/spec/controllers/apps_controller_spec.rb
index 9a5f845..208febe 100644
--- a/spec/controllers/apps_controller_spec.rb
+++ b/spec/controllers/apps_controller_spec.rb
@@ -2,10 +2,11 @@ require 'spec_helper'
describe AppsController do
render_views
-
+
it_requires_authentication
it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete}
-
+
+
describe "GET /apps" do
context 'when logged in as an admin' do
it 'finds all apps' do
@@ -16,7 +17,7 @@ describe AppsController do
assigns(:apps).should == apps
end
end
-
+
context 'when logged in as a regular user' do
it 'finds apps the user is watching' do
sign_in(user = Factory(:user))
@@ -31,116 +32,116 @@ describe AppsController do
end
end
end
-
+
describe "GET /apps/:id" do
context 'logged in as an admin' do
before(:each) do
@user = Factory(:admin)
sign_in @user
@app = Factory(:app)
- @err = Factory :err, :app => @app
- @notice = Factory :notice, :err => @err
+ @problem = Factory(:notice, :err => Factory(:err, :problem => Factory(:problem, :app => @app))).problem
end
-
+
it 'finds the app' do
get :show, :id => @app.id
assigns(:app).should == @app
end
-
+
it "should not raise errors for app with err without notices" do
- Factory :err, :app => @app
+ Factory(:err, :problem => Factory(:problem, :app => @app))
lambda { get :show, :id => @app.id }.should_not raise_error
end
-
+
it "should list atom feed successfully" do
get :show, :id => @app.id, :format => "atom"
response.should be_success
- response.body.should match(@err.message)
+ response.body.should match(@problem.message)
end
-
+
context "pagination" do
before(:each) do
- 35.times { Factory :err, :app => @app }
+ 35.times { Factory(:err, :problem => Factory(:problem, :app => @app)) }
end
-
+
it "should have default per_page value for user" do
get :show, :id => @app.id
- assigns(:errs).size.should == User::PER_PAGE
+ assigns(:problems).size.should == User::PER_PAGE
end
-
+
it "should be able to override default per_page value" do
@user.update_attribute :per_page, 10
get :show, :id => @app.id
- assigns(:errs).size.should == 10
+ assigns(:problems).size.should == 10
end
end
-
+
context 'with resolved errors' do
before(:each) do
- resolved_err = Factory.create(:err, :app => @app, :resolved => true)
- Factory.create(:notice, :err => resolved_err)
+ resolved_problem = Factory(:problem, :app => @app)
+ Factory(:notice, :err => Factory(:err, :problem => resolved_problem))
+ resolved_problem.resolve!
end
-
+
context 'and no params' do
- it 'shows only unresolved errs' do
+ it 'shows only unresolved problems' do
get :show, :id => @app.id
- assigns(:errs).size.should == 1
+ assigns(:problems).size.should == 1
end
end
-
- context 'and all_errs=true params' do
+
+ context 'and all_problems=true params' do
it 'shows all errors' do
get :show, :id => @app.id, :all_errs => true
- assigns(:errs).size.should == 2
+ assigns(:problems).size.should == 2
end
end
end
-
+
context 'with environment filters' do
before(:each) do
environments = ['production', 'test', 'development', 'staging']
20.times do |i|
- Factory.create(:err, :app => @app, :environment => environments[i % environments.length])
+ Factory.create(:problem, :app => @app, :environment => environments[i % environments.length])
end
end
-
+
context 'no params' do
it 'shows errs for all environments' do
get :show, :id => @app.id
- assigns(:errs).size.should == 21
+ assigns(:problems).size.should == 21
end
end
-
+
context 'environment production' do
it 'shows errs for just production' do
- get :show, :id => @app.id, :environment => :production
- assigns(:errs).size.should == 6
+ get :show, :id => @app.id, :environment => 'production'
+ assigns(:problems).size.should == 6
end
end
-
+
context 'environment staging' do
it 'shows errs for just staging' do
- get :show, :id => @app.id, :environment => :staging
- assigns(:errs).size.should == 5
+ get :show, :id => @app.id, :environment => 'staging'
+ assigns(:problems).size.should == 5
end
end
-
+
context 'environment development' do
it 'shows errs for just development' do
- get :show, :id => @app.id, :environment => :development
- assigns(:errs).size.should == 5
+ get :show, :id => @app.id, :environment => 'development'
+ assigns(:problems).size.should == 5
end
end
-
+
context 'environment test' do
it 'shows errs for just test' do
- get :show, :id => @app.id, :environment => :test
- assigns(:errs).size.should == 5
+ get :show, :id => @app.id, :environment => 'test'
+ assigns(:problems).size.should == 5
end
end
end
end
-
+
context 'logged in as a user' do
it 'finds the app if the user is watching it' do
user = Factory(:user)
@@ -150,7 +151,7 @@ describe AppsController do
get :show, :id => app.id
assigns(:app).should == app
end
-
+
it 'does not find the app if the user is not watching it' do
sign_in Factory(:user)
app = Factory(:app)
@@ -160,12 +161,12 @@ describe AppsController do
end
end
end
-
+
context 'logged in as an admin' do
before do
sign_in Factory(:admin)
end
-
+
describe "GET /apps/new" do
it 'instantiates a new app with a prebuilt watcher' do
get :new
@@ -184,7 +185,7 @@ describe AppsController do
assigns(:app).github_url.should == "github.com/test/example"
end
end
-
+
describe "GET /apps/:id/edit" do
it 'finds the correct app' do
app = Factory(:app)
@@ -192,47 +193,47 @@ describe AppsController do
assigns(:app).should == app
end
end
-
+
describe "POST /apps" do
before do
@app = Factory(:app)
App.stub(:new).and_return(@app)
end
-
+
context "when the create is successful" do
before do
@app.should_receive(:save).and_return(true)
end
-
+
it "should redirect to the app page" do
post :create, :app => {}
response.should redirect_to(app_path(@app))
end
-
+
it "should display a message" do
post :create, :app => {}
request.flash[:success].should match(/success/)
end
end
end
-
+
describe "PUT /apps/:id" do
before do
@app = Factory(:app)
end
-
+
context "when the update is successful" do
it "should redirect to the app page" do
put :update, :id => @app.id, :app => {}
response.should redirect_to(app_path(@app))
end
-
+
it "should display a message" do
put :update, :id => @app.id, :app => {}
request.flash[:success].should match(/success/)
end
end
-
+
context "changing name" do
it "should redirect to app page" do
id = @app.id
@@ -240,14 +241,14 @@ describe AppsController do
response.should redirect_to(app_path(id))
end
end
-
+
context "when the update is unsuccessful" do
it "should render the edit page" do
put :update, :id => @app.id, :app => { :name => '' }
response.should render_template(:edit)
end
end
-
+
context "changing email_at_notices" do
it "should parse legal csv values" do
put :update, :id => @app.id, :app => { :email_at_notices => '1, 4, 7,8, 10' }
@@ -261,14 +262,14 @@ describe AppsController do
@app.reload
@app.email_at_notices.should == Errbit::Config.email_at_notices
end
-
+
it "should display a message" do
put :update, :id => @app.id, :app => { :email_at_notices => 'qwertyuiop' }
request.flash[:error].should match(/Couldn't parse/)
end
end
end
-
+
context "setting up issue tracker", :cur => true do
context "unknown tracker type" do
before(:each) do
@@ -277,12 +278,12 @@ describe AppsController do
} }
@app.reload
end
-
+
it "should not create issue tracker" do
@app.issue_tracker_configured?.should == false
end
end
-
+
IssueTracker.subclasses.each do |tracker_klass|
context tracker_klass do
it "should save tracker params" do
@@ -290,7 +291,7 @@ describe AppsController do
params['ticket_properties'] = "card_type = defect" if tracker_klass == MingleTracker
params['type'] = tracker_klass.to_s
put :update, :id => @app.id, :app => {:issue_tracker_attributes => params}
-
+
@app.reload
tracker = @app.issue_tracker
tracker.should be_a(tracker_klass)
@@ -301,13 +302,13 @@ describe AppsController do
end
end
end
-
+
it "should show validation notice when sufficient params are not present" do
# Leave out one required param
params = tracker_klass::Fields[1..-1].inject({}){|hash,f| hash[f[0]] = "test_value"; hash }
params['type'] = tracker_klass.to_s
put :update, :id => @app.id, :app => {:issue_tracker_attributes => params}
-
+
@app.reload
@app.issue_tracker_configured?.should == false
response.body.should match(/You must specify your/)
@@ -316,34 +317,34 @@ describe AppsController do
end
end
end
-
+
describe "DELETE /apps/:id" do
before do
@app = Factory(:app)
App.stub(:find).with(@app.id).and_return(@app)
end
-
+
it "should find the app" do
delete :destroy, :id => @app.id
assigns(:app).should == @app
end
-
+
it "should destroy the app" do
@app.should_receive(:destroy)
delete :destroy, :id => @app.id
end
-
+
it "should display a message" do
delete :destroy, :id => @app.id
request.flash[:success].should match(/success/)
end
-
+
it "should redirect to the apps page" do
delete :destroy, :id => @app.id
response.should redirect_to(apps_path)
end
end
end
-
+
+
end
-
diff --git a/spec/controllers/errs_controller_spec.rb b/spec/controllers/errs_controller_spec.rb
index b437570..9885abd 100644
--- a/spec/controllers/errs_controller_spec.rb
+++ b/spec/controllers/errs_controller_spec.rb
@@ -6,443 +6,443 @@ describe ErrsController do
:index => :get, :all => :get, :show => :get, :resolve => :put
},
:params => {:app_id => 'dummyid', :id => 'dummyid'}
-
+
let(:app) { Factory(:app) }
- let(:err) { Factory(:err, :app => app) }
-
+ let(:err) { Factory(:err, :problem => Factory(:problem, :app => app, :environment => "production")) }
+
+
describe "GET /errs" do
render_views
context 'when logged in as an admin' do
before(:each) do
@user = Factory(:admin)
sign_in @user
- @notice = Factory :notice
- @err = @notice.err
+ @problem = Factory(:notice, :err => Factory(:err, :problem => Factory(:problem, :app => app, :environment => "production"))).problem
end
-
+
it "should successfully list errs" do
get :index
response.should be_success
- response.body.should match(@err.message)
+ response.body.should match(@problem.message)
end
-
+
it "should list atom feed successfully" do
get :index, :format => "atom"
response.should be_success
- response.body.should match(@err.message)
+ response.body.should match(@problem.message)
end
-
+
context "pagination" do
before(:each) do
35.times { Factory :err }
end
-
+
it "should have default per_page value for user" do
get :index
- assigns(:errs).size.should == User::PER_PAGE
+ assigns(:problems).size.should == User::PER_PAGE
end
-
+
it "should be able to override default per_page value" do
@user.update_attribute :per_page, 10
get :index
- assigns(:errs).size.should == 10
+ assigns(:problems).size.should == 10
end
end
-
+
context 'with environment filters' do
before(:each) do
environments = ['production', 'test', 'development', 'staging']
20.times do |i|
- Factory.create(:err, :environment => environments[i % environments.length])
+ Factory(:problem, :environment => environments[i % environments.length])
end
end
-
+
context 'no params' do
it 'shows errs for all environments' do
get :index
- assigns(:errs).size.should == 21
+ assigns(:problems).size.should == 21
end
end
-
+
context 'environment production' do
it 'shows errs for just production' do
- get :index, :environment => :production
- assigns(:errs).size.should == 6
+ get :index, :environment => 'production'
+ assigns(:problems).size.should == 6
end
end
-
+
context 'environment staging' do
it 'shows errs for just staging' do
- get :index, :environment => :staging
- assigns(:errs).size.should == 5
+ get :index, :environment => 'staging'
+ assigns(:problems).size.should == 5
end
end
-
+
context 'environment development' do
it 'shows errs for just development' do
- get :index, :environment => :development
- assigns(:errs).size.should == 5
+ get :index, :environment => 'development'
+ assigns(:problems).size.should == 5
end
end
-
+
context 'environment test' do
it 'shows errs for just test' do
- get :index, :environment => :test
- assigns(:errs).size.should == 5
+ get :index, :environment => 'test'
+ assigns(:problems).size.should == 5
end
end
end
end
-
+
context 'when logged in as a user' do
it 'gets a paginated list of unresolved errs for the users apps' do
sign_in(user = Factory(:user))
unwatched_err = Factory(:err)
- watched_unresolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => false)
- watched_resolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => true)
+ watched_unresolved_err = Factory(:err, :problem => Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => false))
+ watched_resolved_err = Factory(:err, :problem => Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => true))
get :index
- assigns(:errs).should include(watched_unresolved_err)
- assigns(:errs).should_not include(unwatched_err, watched_resolved_err)
+ assigns(:problems).should include(watched_unresolved_err.problem)
+ assigns(:problems).should_not include(unwatched_err.problem, watched_resolved_err.problem)
end
end
end
-
+
describe "GET /errs/all" do
context 'when logged in as an admin' do
it "gets a paginated list of all errs" do
sign_in Factory(:admin)
errs = WillPaginate::Collection.new(1,30)
- 3.times { errs << Factory(:err) }
- 3.times { errs << Factory(:err, :resolved => true)}
- Err.should_receive(:ordered).and_return(
+ 3.times { errs << Factory(:err).problem }
+ 3.times { errs << Factory(:err, :problem => Factory(:problem, :resolved => true)).problem }
+ Problem.should_receive(:ordered).and_return(
mock('proxy', :paginate => errs)
)
get :all
- assigns(:errs).should == errs
+ assigns(:problems).should == errs
end
end
-
+
context 'when logged in as a user' do
it 'gets a paginated list of all errs for the users apps' do
sign_in(user = Factory(:user))
- unwatched_err = Factory(:err)
- watched_unresolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => false)
- watched_resolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => true)
+ unwatched_err = Factory(:problem)
+ watched_unresolved_err = Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => false)
+ watched_resolved_err = Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => true)
get :all
- assigns(:errs).should include(watched_resolved_err, watched_unresolved_err)
- assigns(:errs).should_not include(unwatched_err)
+ assigns(:problems).should include(watched_resolved_err, watched_unresolved_err)
+ assigns(:problems).should_not include(unwatched_err)
end
end
end
-
+
describe "GET /apps/:app_id/errs/:id" do
render_views
-
+
before do
3.times { Factory(:notice, :err => err)}
end
-
+
context 'when logged in as an admin' do
before do
sign_in Factory(:admin)
end
-
+
it "finds the app" do
- get :show, :app_id => app.id, :id => err.id
+ get :show, :app_id => app.id, :id => err.problem.id
assigns(:app).should == app
end
-
+
it "finds the err" do
- get :show, :app_id => app.id, :id => err.id
- assigns(:err).should == err
+ get :show, :app_id => app.id, :id => err.problem.id
+ assigns(:problem).should == err.problem
end
-
+
it "successfully render page" do
- get :show, :app_id => app.id, :id => err.id
+ get :show, :app_id => app.id, :id => err.problem.id
response.should be_success
end
-
+
context "create issue button" do
let(:button_matcher) { match(/create issue/) }
-
+
it "should not exist for err's app without issue tracker" do
err = Factory :err
- get :show, :app_id => err.app.id, :id => err.id
-
+ get :show, :app_id => err.app.id, :id => err.problem.id
+
response.body.should_not button_matcher
end
-
+
it "should exist for err's app with issue tracker" do
tracker = Factory(:lighthouse_tracker)
- err = Factory(:err, :app => tracker.app)
- get :show, :app_id => err.app.id, :id => err.id
-
+ err = Factory(:err, :problem => Factory(:problem, :app => tracker.app))
+ get :show, :app_id => err.app.id, :id => err.problem.id
+
response.body.should button_matcher
end
-
+
it "should not exist for err with issue_link" do
tracker = Factory(:lighthouse_tracker)
- err = Factory(:err, :app => tracker.app, :issue_link => "http://some.host")
- get :show, :app_id => err.app.id, :id => err.id
-
+ err = Factory(:err, :problem => Factory(:problem, :app => tracker.app, :issue_link => "http://some.host"))
+ get :show, :app_id => err.app.id, :id => err.problem.id
+
response.body.should_not button_matcher
end
end
end
-
+
context 'when logged in as a user' do
before do
sign_in(@user = Factory(:user))
@unwatched_err = Factory(:err)
@watched_app = Factory(:app)
@watcher = Factory(:user_watcher, :user => @user, :app => @watched_app)
- @watched_err = Factory(:err, :app => @watched_app)
+ @watched_err = Factory(:err, :problem => Factory(:problem, :app => @watched_app))
end
-
+
it 'finds the err if the user is watching the app' do
- get :show, :app_id => @watched_app.to_param, :id => @watched_err.id
- assigns(:err).should == @watched_err
+ get :show, :app_id => @watched_app.to_param, :id => @watched_err.problem.id
+ assigns(:problem).should == @watched_err.problem
end
-
+
it 'raises a DocumentNotFound error if the user is not watching the app' do
lambda {
- get :show, :app_id => @unwatched_err.app_id, :id => @unwatched_err.id
+ get :show, :app_id => @unwatched_err.problem.app_id, :id => @unwatched_err.problem.id
}.should raise_error(Mongoid::Errors::DocumentNotFound)
end
end
end
-
+
describe "PUT /apps/:app_id/errs/:id/resolve" do
before do
sign_in Factory(:admin)
-
- @err = Factory(:err)
- App.stub(:find).with(@err.app.id).and_return(@err.app)
- @err.app.errs.stub(:find).and_return(@err)
- @err.stub(:resolve!)
+
+ @problem = Factory(:err)
+ App.stub(:find).with(@problem.app.id).and_return(@problem.app)
+ @problem.app.problems.stub(:find).and_return(@problem.problem)
+ @problem.problem.stub(:resolve!)
end
-
+
it 'finds the app and the err' do
- App.should_receive(:find).with(@err.app.id).and_return(@err.app)
- @err.app.errs.should_receive(:find).and_return(@err)
- put :resolve, :app_id => @err.app.id, :id => @err.id
- assigns(:app).should == @err.app
- assigns(:err).should == @err
+ App.should_receive(:find).with(@problem.app.id).and_return(@problem.app)
+ @problem.app.problems.should_receive(:find).and_return(@problem.problem)
+ put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
+ assigns(:app).should == @problem.app
+ assigns(:problem).should == @problem.problem
end
-
+
it "should resolve the issue" do
- @err.should_receive(:resolve!).and_return(true)
- put :resolve, :app_id => @err.app.id, :id => @err.id
+ @problem.problem.should_receive(:resolve!).and_return(true)
+ put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
end
-
+
it "should display a message" do
- put :resolve, :app_id => @err.app.id, :id => @err.id
+ put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
request.flash[:success].should match(/Great news/)
end
-
+
it "should redirect to the app page" do
- put :resolve, :app_id => @err.app.id, :id => @err.id
- response.should redirect_to(app_path(@err.app))
+ put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
+ response.should redirect_to(app_path(@problem.app))
end
-
+
it "should redirect back to errs page" do
request.env["Referer"] = errs_path
- put :resolve, :app_id => @err.app.id, :id => @err.id
+ put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
response.should redirect_to(errs_path)
end
end
-
+
describe "POST /apps/:app_id/errs/:id/create_issue" do
render_views
-
+
before(:each) do
sign_in Factory(:admin)
end
-
+
context "successful issue creation" do
context "lighthouseapp tracker" do
let(:notice) { Factory :notice }
- let(:tracker) { Factory :lighthouse_tracker, :app => notice.err.app }
- let(:err) { notice.err }
-
+ let(:tracker) { Factory :lighthouse_tracker, :app => notice.app }
+ let(:problem) { notice.problem }
+
before(:each) do
number = 5
@issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"
body = "#{number}"
stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").
to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
-
- post :create_issue, :app_id => err.app.id, :id => err.id
- err.reload
+
+ post :create_issue, :app_id => problem.app.id, :id => problem.id
+ problem.reload
end
-
- it "should redirect to err page" do
- response.should redirect_to( app_err_path(err.app, err) )
+
+ it "should redirect to problem page" do
+ response.should redirect_to( app_err_path(problem.app, problem) )
end
end
end
-
+
context "absent issue tracker" do
- let(:err) { Factory :err }
-
+ let(:problem) { Factory :problem }
+
before(:each) do
- post :create_issue, :app_id => err.app.id, :id => err.id
+ post :create_issue, :app_id => problem.app.id, :id => problem.id
end
-
- it "should redirect to err page" do
- response.should redirect_to( app_err_path(err.app, err) )
+
+ it "should redirect to problem page" do
+ response.should redirect_to( app_err_path(problem.app, problem) )
end
-
+
it "should set flash error message telling issue tracker of the app doesn't exist" do
flash[:error].should == "This app has no issue tracker setup."
end
end
-
+
context "error during request to a tracker" do
context "lighthouseapp tracker" do
let(:tracker) { Factory :lighthouse_tracker }
- let(:err) { Factory :err, :app => tracker.app }
-
+ let(:err) { Factory(:err, :problem => Factory(:problem, :app => tracker.app)) }
+
before(:each) do
stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 500)
-
- post :create_issue, :app_id => err.app.id, :id => err.id
+
+ post :create_issue, :app_id => err.app.id, :id => err.problem.id
end
-
+
it "should redirect to err page" do
- response.should redirect_to( app_err_path(err.app, err) )
+ response.should redirect_to( app_err_path(err.app, err.problem) )
end
-
+
it "should notify of connection error" do
flash[:error].should == "There was an error during issue creation. Check your tracker settings or try again later."
end
end
end
end
-
+
describe "DELETE /apps/:app_id/errs/:id/unlink_issue" do
before(:each) do
sign_in Factory(:admin)
end
-
+
context "err with issue" do
- let(:err) { Factory :err, :issue_link => "http://some.host" }
-
+ let(:err) { Factory(:err, :problem => Factory(:problem, :issue_link => "http://some.host")) }
+
before(:each) do
- delete :unlink_issue, :app_id => err.app.id, :id => err.id
- err.reload
+ delete :unlink_issue, :app_id => err.app.id, :id => err.problem.id
+ err.problem.reload
end
-
+
it "should redirect to err page" do
- response.should redirect_to( app_err_path(err.app, err) )
+ response.should redirect_to( app_err_path(err.app, err.problem) )
end
-
+
it "should clear issue link" do
- err.issue_link.should be_nil
+ err.problem.issue_link.should be_nil
end
end
-
+
context "err without issue" do
let(:err) { Factory :err }
-
+
before(:each) do
- delete :unlink_issue, :app_id => err.app.id, :id => err.id
- err.reload
+ delete :unlink_issue, :app_id => err.app.id, :id => err.problem.id
+ err.problem.reload
end
-
+
it "should redirect to err page" do
- response.should redirect_to( app_err_path(err.app, err) )
+ response.should redirect_to( app_err_path(err.app, err.problem) )
end
end
end
-
-
+
+
describe "POST /apps/:app_id/errs/:id/create_comment" do
render_views
-
+
before(:each) do
sign_in Factory(:admin)
end
-
+
context "successful comment creation" do
- let(:err) { Factory(:err) }
+ let(:problem) { Factory(:problem) }
let(:user) { Factory(:user) }
-
+
before(:each) do
- post :create_comment, :app_id => err.app.id, :id => err.id,
+ post :create_comment, :app_id => problem.app.id, :id => problem.id,
:comment => { :body => "One test comment", :user_id => user.id }
- err.reload
+ problem.reload
end
-
+
it "should create the comment" do
- err.comments.size.should == 1
+ problem.comments.size.should == 1
end
-
- it "should redirect to err page" do
- response.should redirect_to( app_err_path(err.app, err) )
+
+ it "should redirect to problem page" do
+ response.should redirect_to( app_err_path(problem.app, problem) )
end
end
end
-
+
describe "DELETE /apps/:app_id/errs/:id/destroy_comment" do
render_views
-
+
before(:each) do
sign_in Factory(:admin)
end
-
+
context "successful comment deletion" do
- let(:err) { Factory :err_with_comments }
- let(:comment) { err.comments.first }
-
+ let(:problem) { Factory(:problem_with_comments) }
+ let(:comment) { problem.comments.first }
+
before(:each) do
- delete :destroy_comment, :app_id => err.app.id, :id => err.id, :comment_id => comment.id
- err.reload
+ delete :destroy_comment, :app_id => problem.app.id, :id => problem.id, :comment_id => comment.id
+ problem.reload
end
-
+
it "should delete the comment" do
- err.comments.detect{|c| c.id.to_s == comment.id }.should == nil
+ problem.comments.detect{|c| c.id.to_s == comment.id }.should == nil
end
-
- it "should redirect to err page" do
- response.should redirect_to( app_err_path(err.app, err) )
+
+ it "should redirect to problem page" do
+ response.should redirect_to( app_err_path(problem.app, problem) )
end
end
end
-
+
describe "Bulk Actions" do
before(:each) do
sign_in Factory(:admin)
- @err1 = Factory(:err, :resolved => true)
- @err2 = Factory(:err, :resolved => false)
+ @problem1 = Factory(:problem, :resolved => true)
+ @problem2 = Factory(:problem, :resolved => false)
end
- it "should apply to multiple errs" do
- post :resolve_several, :errs => [@err1.id.to_s, @err2.id.to_s]
- assigns(:selected_errs).should == [@err1, @err2]
+ it "should apply to multiple problems" do
+ post :resolve_several, :problems => [@problem1.id.to_s, @problem2.id.to_s]
+ assigns(:selected_problems).should == [@problem1, @problem2]
end
context "POST /errs/resolve_several" do
it "should resolve the issue" do
- post :resolve_several, :errs => [@err2.id.to_s]
- @err2.reload.resolved?.should == true
+ post :resolve_several, :problems => [@problem2.id.to_s]
+ @problem2.reload.resolved?.should == true
end
end
context "POST /errs/unresolve_several" do
it "should unresolve the issue" do
- post :unresolve_several, :errs => [@err1.id.to_s]
- @err1.reload.resolved?.should == false
+ post :unresolve_several, :problems => [@problem1.id.to_s]
+ @problem1.reload.resolved?.should == false
end
end
context "POST /errs/destroy_several" do
it "should delete the errs" do
lambda {
- post :destroy_several, :errs => [@err1.id.to_s]
- }.should change(Err, :count).by(-1)
+ post :destroy_several, :problems => [@problem1.id.to_s]
+ }.should change(Problem, :count).by(-1)
end
end
end
+
end
-
diff --git a/spec/controllers/notices_controller_spec.rb b/spec/controllers/notices_controller_spec.rb
index 2de1e69..de509bc 100644
--- a/spec/controllers/notices_controller_spec.rb
+++ b/spec/controllers/notices_controller_spec.rb
@@ -29,9 +29,9 @@ describe NoticesController do
post :create
email = ActionMailer::Base.deliveries.last
email.to.should include(@app.watchers.first.email)
- email.subject.should include(@notice.err.message)
+ email.subject.should include(@notice.message)
email.subject.should include("[#{@app.name}]")
- email.subject.should include("[#{@notice.err.environment}]")
+ email.subject.should include("[#{@notice.environment_name}]")
end
end
diff --git a/spec/factories/app_factories.rb b/spec/factories/app_factories.rb
index 1f7a7a3..2939023 100644
--- a/spec/factories/app_factories.rb
+++ b/spec/factories/app_factories.rb
@@ -20,7 +20,7 @@ Factory.define(:user_watcher, :parent => :watcher) do |w|
end
Factory.define(:deploy) do |d|
- d.app {|p| p.association :app}
+ d.app {|p| p.association :app}
d.username 'clyde.frog'
d.repository 'git@github.com/errbit/errbit.git'
d.environment 'production'
diff --git a/spec/factories/err_factories.rb b/spec/factories/err_factories.rb
index cb8de23..c35d32a 100644
--- a/spec/factories/err_factories.rb
+++ b/spec/factories/err_factories.rb
@@ -1,21 +1,29 @@
+Factory.define :problem do |p|
+ p.app {|a| a.association :app}
+ p.comments []
+end
+
+Factory.define(:problem_with_comments, :parent => :problem) do |ec|
+ ec.comments { (1..3).map { Factory(:comment) } }
+end
+
+
+
Factory.define :err do |e|
- e.app {|p| p.association :app }
+ e.problem {|p| p.association :problem}
e.klass 'FooError'
e.component 'foo'
e.action 'bar'
e.environment 'production'
- e.comments []
end
-Factory.define(:err_with_comments, :parent => :err) do |ec|
- ec.comments { (1..3).map{Factory(:comment)} }
-end
+
Factory.define :notice do |n|
n.err {|e| e.association :err}
n.message 'FooError: Too Much Bar'
n.backtrace { random_backtrace }
- n.server_environment 'server-environment' => 'production'
+ n.server_environment 'environment-name' => 'production'
n.notifier 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com'
end
@@ -28,4 +36,3 @@ def random_backtrace
}}
backtrace
end
-
diff --git a/spec/models/app_spec.rb b/spec/models/app_spec.rb
index a845c86..4e0e397 100644
--- a/spec/models/app_spec.rb
+++ b/spec/models/app_spec.rb
@@ -1,21 +1,22 @@
require 'spec_helper'
describe App do
-
+
+
context 'validations' do
it 'requires a name' do
app = Factory.build(:app, :name => nil)
app.should_not be_valid
app.errors[:name].should include("can't be blank")
end
-
+
it 'requires unique names' do
Factory(:app, :name => 'Errbit')
app = Factory.build(:app, :name => 'Errbit')
app.should_not be_valid
app.errors[:name].should include('is already taken')
end
-
+
it 'requires unique api_keys' do
Factory(:app, :api_key => 'APIKEY')
app = Factory.build(:app, :api_key => 'APIKEY')
@@ -23,7 +24,8 @@ describe App do
app.errors[:api_key].should include('is already taken')
end
end
-
+
+
context 'being created' do
it 'generates a new api-key' do
app = Factory.build(:app)
@@ -31,62 +33,62 @@ describe App do
app.save
app.api_key.should_not be_nil
end
-
+
it 'generates a correct api-key' do
app = Factory(:app)
app.api_key.should match(/^[a-f0-9]{32}$/)
end
-
+
it 'is fine with blank github urls' do
app = Factory.build(:app, :github_url => "")
app.save
app.github_url.should == ""
end
-
+
it 'does not touch https github urls' do
app = Factory.build(:app, :github_url => "https://github.com/errbit/errbit")
app.save
app.github_url.should == "https://github.com/errbit/errbit"
end
-
+
it 'normalizes http github urls' do
app = Factory.build(:app, :github_url => "http://github.com/errbit/errbit")
app.save
app.github_url.should == "https://github.com/errbit/errbit"
end
-
+
it 'normalizes public git repo as a github url' do
app = Factory.build(:app, :github_url => "https://github.com/errbit/errbit.git")
app.save
app.github_url.should == "https://github.com/errbit/errbit"
end
-
+
it 'normalizes private git repo as a github url' do
app = Factory.build(:app, :github_url => "git@github.com:errbit/errbit.git")
app.save
app.github_url.should == "https://github.com/errbit/errbit"
end
end
-
+
context '#github_url_to_file' do
it 'resolves to full path to file' do
app = Factory(:app, :github_url => "https://github.com/errbit/errbit")
app.github_url_to_file('/path/to/file').should == "https://github.com/errbit/errbit/blob/master/path/to/file"
end
end
-
+
context '#github_url?' do
it 'is true when there is a github_url' do
app = Factory(:app, :github_url => "https://github.com/errbit/errbit")
app.github_url?.should be_true
end
-
+
it 'is false when no github_url' do
app = Factory(:app)
app.github_url?.should be_false
end
end
-
+
context "notification recipients" do
it "should send notices to either all users, or the configured watchers" do
@app = Factory(:app)
@@ -98,7 +100,8 @@ describe App do
@app.notification_recipients.size.should == 5
end
end
-
+
+
context "copying attributes from existing app" do
it "should only copy the necessary fields" do
@app, @copy_app = Factory(:app, :name => "app", :github_url => "url"),
@@ -110,5 +113,36 @@ describe App do
@app.watchers.first.email.should == "copywatcher@example.com"
end
end
+
+
+ context '#find_or_create_err!' do
+ before do
+ @app = Factory(:app)
+ @conditions = {
+ :klass => 'Whoops',
+ :component => 'Foo',
+ :action => 'bar',
+ :environment => 'production'
+ }
+ end
+
+ it 'returns the correct err if one already exists' do
+ existing = Factory(:err, @conditions.merge(:problem => Factory(:problem, :app => @app)))
+ Err.where(@conditions).first.should == existing
+ @app.find_or_create_err!(@conditions).should == existing
+ end
+
+ it 'assigns the returned err to the given app' do
+ @app.find_or_create_err!(@conditions).app.should == @app
+ end
+
+ it 'creates a new problem if a matching one does not already exist' do
+ Err.where(@conditions).first.should be_nil
+ lambda {
+ @app.find_or_create_err!(@conditions)
+ }.should change(Problem,:count).by(1)
+ end
+ end
+
+
end
-
diff --git a/spec/models/deploy_spec.rb b/spec/models/deploy_spec.rb
index 762e3b8..7f01a5c 100644
--- a/spec/models/deploy_spec.rb
+++ b/spec/models/deploy_spec.rb
@@ -26,20 +26,20 @@ describe Deploy do
context 'when the app has resolve_errs_on_deploy set to false' do
it 'should not resolve the apps errs' do
app = Factory(:app, :resolve_errs_on_deploy => false)
- @errs = 3.times.inject([]) {|errs,_| errs << Factory(:err, :resolved => false, :app => app)}
+ @problems = 3.times.map{Factory(:err, :problem => Factory(:problem, :resolved => false, :app => app))}
Factory(:deploy, :app => app)
- app.reload.errs.none?{|err| err.resolved?}.should == true
+ app.reload.problems.none?{|problem| problem.resolved?}.should == true
end
end
context 'when the app has resolve_errs_on_deploy set to true' do
it 'should resolve the apps errs that were in the same environment' do
app = Factory(:app, :resolve_errs_on_deploy => true)
- @prod_errs = 3.times.inject([]) {|errs,_| errs << Factory(:err, :resolved => false, :app => app, :environment => 'production')}
- @staging_errs = 3.times.inject([]) {|errs,_| errs << Factory(:err, :resolved => false, :app => app, :environment => 'staging')}
+ @prod_errs = 3.times.map{Factory(:problem, :resolved => false, :app => app, :environment => 'production')}
+ @staging_errs = 3.times.map{Factory(:problem, :resolved => false, :app => app, :environment => 'staging')}
Factory(:deploy, :app => app, :environment => 'production')
- @prod_errs.all?{|err| err.reload.resolved?}.should == true
- @staging_errs.all?{|err| err.reload.resolved?}.should == false
+ @prod_errs.all?{|problem| problem.reload.resolved?}.should == true
+ @staging_errs.all?{|problem| problem.reload.resolved?}.should == false
end
end
diff --git a/spec/models/err_spec.rb b/spec/models/err_spec.rb
index 3a80394..b9d1385 100644
--- a/spec/models/err_spec.rb
+++ b/spec/models/err_spec.rb
@@ -1,172 +1,19 @@
require 'spec_helper'
describe Err do
-
+
context 'validations' do
it 'requires a klass' do
err = Factory.build(:err, :klass => nil)
err.should_not be_valid
err.errors[:klass].should include("can't be blank")
end
-
+
it 'requires an environment' do
err = Factory.build(:err, :environment => nil)
err.should_not be_valid
err.errors[:environment].should include("can't be blank")
end
end
-
- context '#for' do
- before do
- @app = Factory(:app)
- @conditions = {
- :app => @app,
- :klass => 'Whoops',
- :component => 'Foo',
- :action => 'bar',
- :environment => 'production'
- }
- end
-
- it 'returns the correct err if one already exists' do
- existing = Err.create(@conditions)
- Err.for(@conditions).should == existing
- end
-
- it 'assigns the returned err to the given app' do
- Err.for(@conditions).app.should == @app
- end
-
- it 'creates a new err if a matching one does not already exist' do
- Err.where(@conditions.except(:app)).exists?.should == false
- lambda {
- Err.for(@conditions)
- }.should change(Err,:count).by(1)
- end
- end
-
- context '#last_notice_at' do
- it "returns the created_at timestamp of the latest notice" do
- err = Factory(:err)
- err.last_notice_at.should be_nil
-
- notice1 = Factory(:notice, :err => err)
- err.last_notice_at.should == notice1.created_at
-
- notice2 = Factory(:notice, :err => err)
- err.last_notice_at.should == notice2.created_at
- end
- end
-
- context '#message' do
- it "returns klass by default" do
- err = Factory(:err)
- err.message.should == err.klass
- end
-
- it 'returns the message from the first notice' do
- err = Factory(:err)
- notice1 = Factory(:notice, :err => err, :message => 'ERR 1')
- notice2 = Factory(:notice, :err => err, :message => 'ERR 2')
- err.message.should == notice1.message
- end
-
- it "adding a notice caches its message" do
- err = Factory(:err)
- lambda {
- notice1 = Factory(:notice, :err => err, :message => 'ERR 1')}.should change(err, :message).from(err.klass).to('ERR 1')
- end
- end
-
- context "#resolved?" do
- it "should start out as unresolved" do
- err = Err.new
- err.should_not be_resolved
- err.should be_unresolved
- end
-
- it "should be able to be resolved" do
- err = Factory(:err)
- err.should_not be_resolved
- err.resolve!
- err.reload.should be_resolved
- end
- end
-
- context "resolve!" do
- it "marks the err as resolved" do
- err = Factory(:err)
- err.should_not be_resolved
- err.resolve!
- err.should be_resolved
- end
-
- it "should throw an err if it's not successful" do
- err = Factory(:err)
- err.should_not be_resolved
- err.klass = nil
- err.should_not be_valid
- lambda {
- err.resolve!
- }.should raise_error(Mongoid::Errors::Validations)
- end
- end
-
- context "Scopes" do
- context "resolved" do
- it 'only finds resolved Errs' do
- resolved = Factory(:err, :resolved => true)
- unresolved = Factory(:err, :resolved => false)
- Err.resolved.all.should include(resolved)
- Err.resolved.all.should_not include(unresolved)
- end
- end
-
- context "unresolved" do
- it 'only finds unresolved Errs' do
- resolved = Factory(:err, :resolved => true)
- unresolved = Factory(:err, :resolved => false)
- Err.unresolved.all.should_not include(resolved)
- Err.unresolved.all.should include(unresolved)
- end
- end
- end
-
- context 'being created' do
- context 'when the app has err notifications set to false' do
- it 'should not send an email notification' do
- app = Factory(:app_with_watcher, :notify_on_errs => false)
- Mailer.should_not_receive(:err_notification)
- Factory(:err, :app => app)
- end
- end
- end
-
- context "notice counter cache" do
-
- before do
- @app = Factory(:app)
- @err = Factory(:err, :app => @app)
- end
-
- it "#notices_count returns 0 by default" do
- @err.notices_count.should == 0
- end
-
- it "adding a notice increases #notices_count by 1" do
- lambda {
- notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')}.should change(@err, :notices_count).from(0).to(1)
- end
-
- it "removing a notice decreases #notices_count by 1" do
- notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')
- lambda {
- @err.notices.first.destroy
- @err.reload
- }.should change(@err, :notices_count).from(1).to(0)
- end
- end
-
-
+
end
-
diff --git a/spec/models/issue_trackers/fogbugz_tracker_spec.rb b/spec/models/issue_trackers/fogbugz_tracker_spec.rb
index e44baf4..07f6a1b 100644
--- a/spec/models/issue_trackers/fogbugz_tracker_spec.rb
+++ b/spec/models/issue_trackers/fogbugz_tracker_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
describe FogbugzTracker do
- it "should create an issue on Fogbugz with err params, and set issue link for err" do
+ it "should create an issue on Fogbugz with problem params, and set issue link for problem" do
notice = Factory :notice
- tracker = Factory :fogbugz_tracker, :app => notice.err.app
- err = notice.err
-
+ tracker = Factory :fogbugz_tracker, :app => notice.app
+ problem = notice.problem
+
number = 123
@issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}"
response = "12345123"
@@ -13,11 +13,10 @@ describe FogbugzTracker do
http_mock.should_receive(:new).and_return(http_mock)
http_mock.should_receive(:request).twice.and_return(response)
Fogbugz.adapter[:http] = http_mock
-
- err.app.issue_tracker.create_issue(err)
- err.reload
-
- err.issue_link.should == @issue_link
+
+ problem.app.issue_tracker.create_issue(problem)
+ problem.reload
+
+ problem.issue_link.should == @issue_link
end
end
-
diff --git a/spec/models/issue_trackers/github_issues_tracker_spec.rb b/spec/models/issue_trackers/github_issues_tracker_spec.rb
index 2e13e74..71c84b0 100644
--- a/spec/models/issue_trackers/github_issues_tracker_spec.rb
+++ b/spec/models/issue_trackers/github_issues_tracker_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
describe GithubIssuesTracker do
- it "should create an issue on Github Issues with err params, and set issue link for err" do
+ it "should create an issue on Github Issues with problem params, and set issue link for problem" do
notice = Factory :notice
- tracker = Factory :github_issues_tracker, :app => notice.err.app
- err = notice.err
-
+ tracker = Factory :github_issues_tracker, :app => notice.app
+ problem = notice.problem
+
number = 5
@issue_link = "https://github.com/#{tracker.project_id}/issues/#{number}"
body = < 201, :headers => {'Location' => @issue_link}, :body => body )
-
- err.app.issue_tracker.create_issue(err)
- err.reload
-
+
+ problem.app.issue_tracker.create_issue(problem)
+ problem.reload
+
requested = have_requested(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}")
WebMock.should requested.with(:headers => {'Content-Type' => 'application/x-www-form-urlencoded'})
WebMock.should requested.with(:body => /title=%5Bproduction%5D%5Bfoo%23bar%5D%20FooError%3A%20Too%20Much%20Bar/)
WebMock.should requested.with(:body => /See%20this%20exception%20on%20Errbit/)
-
- err.issue_link.should == @issue_link
+
+ problem.issue_link.should == @issue_link
end
end
-
diff --git a/spec/models/issue_trackers/lighthouse_tracker_spec.rb b/spec/models/issue_trackers/lighthouse_tracker_spec.rb
index 50fda38..8f1a7d4 100644
--- a/spec/models/issue_trackers/lighthouse_tracker_spec.rb
+++ b/spec/models/issue_trackers/lighthouse_tracker_spec.rb
@@ -1,27 +1,26 @@
require 'spec_helper'
describe LighthouseTracker do
- it "should create an issue on Lighthouse with err params, and set issue link for err" do
+ it "should create an issue on Lighthouse with problem params, and set issue link for problem" do
notice = Factory :notice
- tracker = Factory :lighthouse_tracker, :app => notice.err.app
- err = notice.err
-
+ tracker = Factory :lighthouse_tracker, :app => notice.app
+ problem = notice.problem
+
number = 5
@issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"
body = "#{number}"
stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").
to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
-
- err.app.issue_tracker.create_issue(err)
- err.reload
-
+
+ problem.app.issue_tracker.create_issue(problem)
+ problem.reload
+
requested = have_requested(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml")
WebMock.should requested.with(:headers => {'X-Lighthousetoken' => tracker.api_token})
WebMock.should requested.with(:body => /errbit<\/tag>/)
- WebMock.should requested.with(:body => /\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/title>/)
+ WebMock.should requested.with(:body => /\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/title>/)
WebMock.should requested.with(:body => /.+<\/body>/m)
-
- err.issue_link.should == @issue_link.sub(/\.xml$/, '')
+
+ problem.issue_link.should == @issue_link.sub(/\.xml$/, '')
end
end
-
diff --git a/spec/models/issue_trackers/mingle_tracker_spec.rb b/spec/models/issue_trackers/mingle_tracker_spec.rb
index e5619fa..b192745 100644
--- a/spec/models/issue_trackers/mingle_tracker_spec.rb
+++ b/spec/models/issue_trackers/mingle_tracker_spec.rb
@@ -1,28 +1,27 @@
require 'spec_helper'
describe MingleTracker do
- it "should create an issue on Mingle with err params, and set issue link for err" do
+ it "should create an issue on Mingle with problem params, and set issue link for problem" do
notice = Factory :notice
- tracker = Factory :mingle_tracker, :app => notice.err.app
- err = notice.err
-
+ tracker = Factory :mingle_tracker, :app => notice.app
+ problem = notice.problem
+
number = 5
@issue_link = "#{tracker.account}/projects/#{tracker.project_id}/cards/#{number}.xml"
@basic_auth = tracker.account.gsub("://", "://#{tracker.username}:#{tracker.password}@")
body = "#{number}"
stub_request(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml").
to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
-
- err.app.issue_tracker.create_issue(err)
- err.reload
-
+
+ problem.app.issue_tracker.create_issue(problem)
+ problem.reload
+
requested = have_requested(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml")
WebMock.should requested.with(:headers => {'Content-Type' => 'application/xml'})
WebMock.should requested.with(:body => /FooError: Too Much Bar/)
WebMock.should requested.with(:body => /See this exception on Errbit/)
WebMock.should requested.with(:body => /Defect<\/card-type-name>/)
-
- err.issue_link.should == @issue_link.sub(/\.xml$/, '')
+
+ problem.issue_link.should == @issue_link.sub(/\.xml$/, '')
end
end
-
diff --git a/spec/models/issue_trackers/pivotal_labs_tracker_spec.rb b/spec/models/issue_trackers/pivotal_labs_tracker_spec.rb
index 5b9d221..67f7092 100644
--- a/spec/models/issue_trackers/pivotal_labs_tracker_spec.rb
+++ b/spec/models/issue_trackers/pivotal_labs_tracker_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
describe PivotalLabsTracker do
- it "should create an issue on Pivotal Tracker with err params, and set issue link for err" do
+ it "should create an issue on Pivotal Tracker with problem params, and set issue link for problem" do
notice = Factory :notice
- tracker = Factory :pivotal_labs_tracker, :app => notice.err.app, :project_id => 10
- err = notice.err
-
+ tracker = Factory :pivotal_labs_tracker, :app => notice.app, :project_id => 10
+ problem = notice.problem
+
story_id = 5
@issue_link = "https://www.pivotaltracker.com/story/show/#{story_id}"
project_body = "#{tracker.project_id}TestProject"
@@ -14,17 +14,16 @@ describe PivotalLabsTracker do
story_body = "Test Story#{story_id}"
stub_request(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories").
to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => story_body )
-
- err.app.issue_tracker.create_issue(err)
- err.reload
-
+
+ problem.app.issue_tracker.create_issue(problem)
+ problem.reload
+
requested = have_requested(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories")
WebMock.should requested.with(:headers => {'X-Trackertoken' => tracker.api_token})
WebMock.should requested.with(:body => /See this exception on Errbit/)
- WebMock.should requested.with(:body => /\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/name>/)
+ WebMock.should requested.with(:body => /\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/name>/)
WebMock.should requested.with(:body => /.+<\/description>/m)
-
- err.issue_link.should == @issue_link
+
+ problem.issue_link.should == @issue_link
end
end
-
diff --git a/spec/models/issue_trackers/redmine_tracker_spec.rb b/spec/models/issue_trackers/redmine_tracker_spec.rb
index 0b9f4b9..aafe685 100644
--- a/spec/models/issue_trackers/redmine_tracker_spec.rb
+++ b/spec/models/issue_trackers/redmine_tracker_spec.rb
@@ -1,26 +1,25 @@
require 'spec_helper'
describe RedmineTracker do
- it "should create an issue on Redmine with err params, and set issue link for err" do
+ it "should create an issue on Redmine with problem params, and set issue link for problem" do
notice = Factory :notice
- tracker = Factory :redmine_tracker, :app => notice.err.app, :project_id => 10
- err = notice.err
+ tracker = Factory :redmine_tracker, :app => notice.app, :project_id => 10
+ problem = notice.problem
number = 5
@issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}"
body = "my subject#{number}"
stub_request(:post, "#{tracker.account}/issues.xml").
to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
-
- err.app.issue_tracker.create_issue(err)
- err.reload
-
+
+ problem.app.issue_tracker.create_issue(problem)
+ problem.reload
+
requested = have_requested(:post, "#{tracker.account}/issues.xml")
WebMock.should requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token})
WebMock.should requested.with(:body => /#{tracker.project_id}<\/project-id>/)
- WebMock.should requested.with(:body => /\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/subject>/)
+ WebMock.should requested.with(:body => /\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/subject>/)
WebMock.should requested.with(:body => /.+<\/description>/m)
-
- err.issue_link.should == @issue_link.sub(/\.xml/, '')
+
+ problem.issue_link.should == @issue_link.sub(/\.xml/, '')
end
end
-
diff --git a/spec/models/notice_spec.rb b/spec/models/notice_spec.rb
index a786dd2..84e461e 100644
--- a/spec/models/notice_spec.rb
+++ b/spec/models/notice_spec.rb
@@ -1,27 +1,29 @@
require 'spec_helper'
describe Notice do
-
+
+
context 'validations' do
it 'requires a backtrace' do
notice = Factory.build(:notice, :backtrace => nil)
notice.should_not be_valid
notice.errors[:backtrace].should include("can't be blank")
end
-
+
it 'requires the server_environment' do
notice = Factory.build(:notice, :server_environment => nil)
notice.should_not be_valid
notice.errors[:server_environment].should include("can't be blank")
end
-
+
it 'requires the notifier' do
notice = Factory.build(:notice, :notifier => nil)
notice.should_not be_valid
notice.errors[:notifier].should include("can't be blank")
end
end
-
+
+
context '.in_app_backtrace_line?' do
let(:backtrace) do [{
'number' => rand(999),
@@ -50,22 +52,23 @@ describe Notice do
Notice.in_app_backtrace_line?(backtrace[2]).should == true
end
end
-
+
+
context '#from_xml' do
before do
@xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
@app = Factory(:app, :api_key => 'APIKEY')
Digest::MD5.stub(:hexdigest).and_return('fingerprintdigest')
end
-
+
it 'finds the correct app' do
@notice = Notice.from_xml(@xml)
@notice.err.app.should == @app
end
-
+
it 'finds the correct err for the notice' do
- Err.should_receive(:for).with({
- :app => @app,
+ App.should_receive(:find_by_api_key!).and_return(@app)
+ @app.should_receive(:find_or_create_err!).with({
:klass => 'HoptoadTestingException',
:component => 'application',
:action => 'verify',
@@ -75,70 +78,72 @@ describe Notice do
err.notices.stub(:create!)
@notice = Notice.from_xml(@xml)
end
-
- it 'marks the err as unresolve if it was previously resolved' do
- Err.should_receive(:for).with({
- :app => @app,
+
+
+ it 'marks the err as unresolved if it was previously resolved' do
+ App.should_receive(:find_by_api_key!).and_return(@app)
+ @app.should_receive(:find_or_create_err!).with({
:klass => 'HoptoadTestingException',
:component => 'application',
:action => 'verify',
:environment => 'development',
:fingerprint => 'fingerprintdigest'
- }).and_return(err = Factory(:err, :resolved => true))
+ }).and_return(err = Factory(:err, :problem => Factory(:problem, :resolved => true)))
err.should be_resolved
@notice = Notice.from_xml(@xml)
@notice.err.should == err
@notice.err.should_not be_resolved
end
-
+
it 'should create a new notice' do
@notice = Notice.from_xml(@xml)
@notice.should be_persisted
end
-
+
it 'assigns an err to the notice' do
@notice = Notice.from_xml(@xml)
@notice.err.should be_a(Err)
end
-
+
it 'captures the err message' do
@notice = Notice.from_xml(@xml)
@notice.message.should == 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.'
end
-
+
it 'captures the backtrace' do
@notice = Notice.from_xml(@xml)
@notice.backtrace.size.should == 73
@notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake'
end
-
+
it 'captures the server_environment' do
@notice = Notice.from_xml(@xml)
@notice.server_environment['environment-name'].should == 'development'
end
-
+
it 'captures the request' do
@notice = Notice.from_xml(@xml)
@notice.request['url'].should == 'http://example.org/verify'
@notice.request['params']['controller'].should == 'application'
end
-
+
it 'captures the notifier' do
@notice = Notice.from_xml(@xml)
@notice.notifier['name'].should == 'Hoptoad Notifier'
end
-
- it "should handle params without 'request' section" do
+
+ it "should handle params withour 'request' section" do
@xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read
lambda { Notice.from_xml(@xml) }.should_not raise_error
end
-
+
it "should raise ApiVersionError" do
@xml = Rails.root.join('spec', 'fixtures', 'hoptoad_test_notice_with_wrong_version.xml').read
expect { Notice.from_xml(@xml) }.to raise_error(Hoptoad::V2::ApiVersionError)
end
end
-
+
+
describe "key sanitization" do
before do
@hash = { "some.key" => { "$nested.key" => {"$Path" => "/", "some$key" => "key"}}}
@@ -152,42 +157,44 @@ describe Notice do
end
end
end
-
+
+
describe "user agent" do
it "should be parsed and human-readable" do
notice = Factory.build(:notice, :request => {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16'}})
notice.user_agent.browser.should == 'Chrome'
notice.user_agent.version.to_s.should =~ /^10\.0/
end
-
+
it "should be nil if HTTP_USER_AGENT is blank" do
notice = Factory.build(:notice)
notice.user_agent.should == nil
end
end
-
+
+
describe "email notifications (configured individually for each app)" do
custom_thresholds = [2, 4, 8, 16, 32, 64]
-
+
before do
Errbit::Config.per_app_email_at_notices = true
@app = Factory(:app_with_watcher, :email_at_notices => custom_thresholds)
- @err = Factory(:err, :app => @app)
+ @problem = Factory(:err, :problem => @app.problems.create!)
end
-
+
after do
Errbit::Config.per_app_email_at_notices = false
end
-
+
custom_thresholds.each do |threshold|
it "sends an email notification after #{threshold} notice(s)" do
- @err.notices.stub(:count).and_return(threshold)
+ @problem.notices.stub(:count).and_return(threshold)
Mailer.should_receive(:err_notification).
and_return(mock('email', :deliver => true))
- Factory(:notice, :err => @err)
+ Factory(:notice, :err => @problem)
end
end
end
-
+
+
end
-
diff --git a/spec/models/problem_spec.rb b/spec/models/problem_spec.rb
new file mode 100644
index 0000000..effb977
--- /dev/null
+++ b/spec/models/problem_spec.rb
@@ -0,0 +1,130 @@
+require 'spec_helper'
+
+describe Problem do
+
+
+ context '#last_notice_at' do
+ it "returns the created_at timestamp of the latest notice" do
+ err = Factory(:err)
+ problem = err.problem
+ problem.should_not be_nil
+
+ problem.last_notice_at.should be_nil
+
+ notice1 = Factory(:notice, :err => err)
+ problem.last_notice_at.should == notice1.created_at
+
+ notice2 = Factory(:notice, :err => err)
+ problem.last_notice_at.should == notice2.created_at
+ end
+ end
+
+
+ context '#message' do
+ it "adding a notice caches its message" do
+ err = Factory(:err)
+ problem = err.problem
+ lambda {
+ Factory(:notice, :err => err, :message => 'ERR 1')
+ }.should change(problem, :message).from(nil).to('ERR 1')
+ end
+ end
+
+
+ context 'being created' do
+ context 'when the app has err notifications set to false' do
+ it 'should not send an email notification' do
+ app = Factory(:app_with_watcher, :notify_on_errs => false)
+ Mailer.should_not_receive(:err_notification)
+ Factory(:problem, :app => app)
+ end
+ end
+ end
+
+
+ context "#resolved?" do
+ it "should start out as unresolved" do
+ problem = Problem.new
+ problem.should_not be_resolved
+ problem.should be_unresolved
+ end
+
+ it "should be able to be resolved" do
+ problem = Factory(:problem)
+ problem.should_not be_resolved
+ problem.resolve!
+ problem.reload.should be_resolved
+ end
+ end
+
+
+ context "resolve!" do
+ it "marks the problem as resolved" do
+ problem = Factory(:problem)
+ problem.should_not be_resolved
+ problem.resolve!
+ problem.should be_resolved
+ end
+
+ it "should throw an err if it's not successful" do
+ problem = Factory(:problem)
+ problem.should_not be_resolved
+ problem.stub!(:valid?).and_return(false)
+ problem.should_not be_valid
+ lambda {
+ problem.resolve!
+ }.should raise_error(Mongoid::Errors::Validations)
+ end
+ end
+
+
+ context "Scopes" do
+ context "resolved" do
+ it 'only finds resolved Problems' do
+ resolved = Factory(:problem, :resolved => true)
+ unresolved = Factory(:problem, :resolved => false)
+ Problem.resolved.all.should include(resolved)
+ Problem.resolved.all.should_not include(unresolved)
+ end
+ end
+
+ context "unresolved" do
+ it 'only finds unresolved Problems' do
+ resolved = Factory(:problem, :resolved => true)
+ unresolved = Factory(:problem, :resolved => false)
+ Problem.unresolved.all.should_not include(resolved)
+ Problem.unresolved.all.should include(unresolved)
+ end
+ end
+ end
+
+
+ context "notice counter cache" do
+
+ before do
+ @app = Factory(:app)
+ @problem = Factory(:problem, :app => @app)
+ @err = Factory(:err, :problem => @problem)
+ end
+
+ it "#notices_count returns 0 by default" do
+ @problem.notices_count.should == 0
+ end
+
+ it "adding a notice increases #notices_count by 1" do
+ lambda {
+ Factory(:notice, :err => @err, :message => 'ERR 1')
+ }.should change(@problem, :notices_count).from(0).to(1)
+ end
+
+ it "removing a notice decreases #notices_count by 1" do
+ notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')
+ lambda {
+ @err.notices.first.destroy
+ @problem.reload
+ }.should change(@problem, :notices_count).from(1).to(0)
+ end
+ end
+
+
+end
diff --git a/spec/views/errs/show.html.haml_spec.rb b/spec/views/errs/show.html.haml_spec.rb
index 7b5618f..78a036f 100644
--- a/spec/views/errs/show.html.haml_spec.rb
+++ b/spec/views/errs/show.html.haml_spec.rb
@@ -3,23 +3,24 @@ require 'spec_helper'
describe "errs/show.html.haml" do
before do
err = Factory(:err)
+ problem = err.problem
comment = Factory(:comment)
- assign :err, err
+ assign :problem, problem
assign :comment, comment
- assign :app, err.app
- assign :notices, err.notices.ordered.paginate(:page => 1, :per_page => 1)
+ assign :app, problem.app
+ assign :notices, err.notices.paginate(:page => 1, :per_page => 1)
assign :notice, err.notices.first
end
-
+
describe "content_for :action_bar" do
-
+
it "should confirm the 'resolve' link by default" do
render
action_bar = String.new(view.instance_variable_get(:@_content_for)[:action_bar])
resolve_link = action_bar.match(/()/)[0]
resolve_link.should =~ /data-confirm="Seriously\?"/
end
-
+
it "should confirm the 'resolve' link if configuration is unset" do
Errbit::Config.stub(:confirm_resolve_err).and_return(nil)
render
@@ -27,7 +28,7 @@ describe "errs/show.html.haml" do
resolve_link = action_bar.match(/()/)[0]
resolve_link.should =~ /data-confirm="Seriously\?"/
end
-
+
it "should not confirm the 'resolve' link if configured not to" do
Errbit::Config.stub(:confirm_resolve_err).and_return(false)
render
@@ -35,35 +36,37 @@ describe "errs/show.html.haml" do
resolve_link = action_bar.match(/()/)[0]
resolve_link.should_not =~ /data-confirm=/
end
-
+
end
-
+
describe "content_for :comments" do
it 'should display comments and new comment form when no issue tracker' do
- err = Factory(:err_with_comments)
- assign :err, err
- assign :app, err.app
+ problem = Factory(:problem_with_comments)
+ assign :problem, problem
+ assign :app, problem.app
render
comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments])
comments_section.should =~ /Test comment/
comments_section.should =~ /Add a comment/
end
-
+
context "with issue tracker" do
- def with_issue_tracker(err)
- err.app.issue_tracker = PivotalLabsTracker.new :api_token => "token token token", :project_id => "1234"
- assign :err, err
- assign :app, err.app
+ def with_issue_tracker(problem)
+ problem.app.issue_tracker = PivotalLabsTracker.new :api_token => "token token token", :project_id => "1234"
+ assign :problem, problem
+ assign :app, problem.app
end
-
+
it 'should not display the comments section' do
- with_issue_tracker(Factory(:err))
+ problem = Factory(:problem)
+ with_issue_tracker(problem)
render
view.instance_variable_get(:@_content_for)[:comments].should be_blank
end
-
+
it 'should display existing comments' do
- with_issue_tracker(Factory(:err_with_comments))
+ problem = Factory(:problem_with_comments)
+ with_issue_tracker(problem)
render
comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments])
comments_section.should =~ /Test comment/
@@ -72,4 +75,3 @@ describe "errs/show.html.haml" do
end
end
end
-
--
libgit2 0.21.2