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 => /<title>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/title>/) WebMock.should requested.with(:body => /<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 = "<card><id type=\"integer\">#{number}</id></card>" 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 => /<card-type-name>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 = "<project><id>#{tracker.project_id}</id><name>TestProject</name></project>" @@ -14,17 +14,16 @@ describe PivotalLabsTracker do story_body = "<story><name>Test Story</name><id>#{story_id}</id></story>" 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 => /<name>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/name>/) + WebMock.should requested.with(:body => /<name>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/name>/) WebMock.should requested.with(:body => /<description>.+<\/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 = "<issue><subject>my subject</subject><id>#{number}</id></issue>" 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 => /<project-id>#{tracker.project_id}<\/project-id>/) - WebMock.should requested.with(:body => /<subject>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/subject>/) + WebMock.should requested.with(:body => /<subject>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/subject>/) WebMock.should requested.with(:body => /<description>.+<\/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(/(<a href.*?(class="resolve").*?>)/)[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(/(<a href.*?(class="resolve").*?>)/)[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(/(<a href.*?(class="resolve").*?>)/)[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