Commit 4d5a1bf84d42431b434ace49aca1272f6418375f

Authored by Bob Lail
1 parent 00791678
Exists in master and in 1 other branch production

refactored model to embed Errs into Problems so that they can be merged

Showing 53 changed files with 989 additions and 858 deletions   Show diff stats
app/controllers/apps_controller.rb
1 class AppsController < InheritedResources::Base 1 class AppsController < InheritedResources::Base
2 -  
3 before_filter :require_admin!, :except => [:index, :show] 2 before_filter :require_admin!, :except => [:index, :show]
4 before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update] 3 before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update]
5 respond_to :html 4 respond_to :html
6 - 5 +
  6 +
7 def show 7 def show
8 respond_to do |format| 8 respond_to do |format|
9 format.html do 9 format.html do
10 @all_errs = !!params[:all_errs] 10 @all_errs = !!params[:all_errs]
11 11
12 - @errs = resource.errs  
13 - @errs = @errs.unresolved unless @all_errs  
14 - @errs = @errs.in_env(params[:environment]).ordered.paginate(:page => params[:page], :per_page => current_user.per_page) 12 + @problems = resource.problems
  13 + @problems = @problems.unresolved unless @all_errs
  14 + @problems = @problems.in_env(params[:environment]).ordered.paginate(:page => params[:page], :per_page => current_user.per_page)
15 15
16 - @selected_errs = params[:errs] || [] 16 + @selected_problems = params[:problems] || []
17 @deploys = @app.deploys.order_by(:created_at.desc).limit(5) 17 @deploys = @app.deploys.order_by(:created_at.desc).limit(5)
18 end 18 end
19 format.atom do 19 format.atom do
20 - @errs = resource.errs.unresolved.ordered 20 + @problems = resource.problems.unresolved.ordered
21 end 21 end
22 end 22 end
23 end 23 end
24 - 24 +
25 def create 25 def create
26 @app = App.new(params[:app]) 26 @app = App.new(params[:app])
27 initialize_subclassed_issue_tracker 27 initialize_subclassed_issue_tracker
28 create! 28 create!
29 end 29 end
30 - 30 +
31 def update 31 def update
32 @app = resource 32 @app = resource
33 initialize_subclassed_issue_tracker 33 initialize_subclassed_issue_tracker
34 update! 34 update!
35 end 35 end
36 - 36 +
37 def new 37 def new
38 plug_params(build_resource) 38 plug_params(build_resource)
39 new! 39 new!
40 end 40 end
41 - 41 +
42 def edit 42 def edit
43 plug_params(resource) 43 plug_params(resource)
44 edit! 44 edit!
45 end 45 end
46 - 46 +
47 protected 47 protected
48 def collection 48 def collection
49 # Sort apps by number of unresolved errs, descending. 49 # Sort apps by number of unresolved errs, descending.
50 # Caches the unresolved err counts while performing the sort. 50 # Caches the unresolved err counts while performing the sort.
51 @unresolved_counts = {} 51 @unresolved_counts = {}
52 @apps ||= end_of_association_chain.all.sort{|a,b| 52 @apps ||= end_of_association_chain.all.sort{|a,b|
53 - [a,b].each{|app| @unresolved_counts[app.id] ||= app.errs.unresolved.count } 53 + [a,b].each{|app| @unresolved_counts[app.id] ||= app.problems.unresolved.count }
54 @unresolved_counts[b.id] <=> @unresolved_counts[a.id] 54 @unresolved_counts[b.id] <=> @unresolved_counts[a.id]
55 } 55 }
56 end 56 end
app/controllers/errs_controller.rb
@@ -2,17 +2,18 @@ class ErrsController &lt; ApplicationController @@ -2,17 +2,18 @@ class ErrsController &lt; ApplicationController
2 include ActionView::Helpers::TextHelper 2 include ActionView::Helpers::TextHelper
3 3
4 before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several] 4 before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several]
5 - before_filter :find_err, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several]  
6 - before_filter :find_selected_errs, :only => [:destroy_several, :resolve_several, :unresolve_several] 5 + before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several]
  6 + before_filter :find_selected_problems, :only => [:destroy_several, :resolve_several, :unresolve_several]
  7 +
7 8
8 9
9 def index 10 def index
10 app_scope = current_user.admin? ? App.all : current_user.apps 11 app_scope = current_user.admin? ? App.all : current_user.apps
11 - @errs = Err.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered  
12 - @selected_errs = params[:errs] || [] 12 + @problems = Problem.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered
  13 + @selected_problems = params[:problems] || []
13 respond_to do |format| 14 respond_to do |format|
14 format.html do 15 format.html do
15 - @errs = @errs.paginate(:page => params[:page], :per_page => current_user.per_page) 16 + @problems = @problems.paginate(:page => params[:page], :per_page => current_user.per_page)
16 end 17 end
17 format.atom 18 format.atom
18 end 19 end
@@ -21,15 +22,15 @@ class ErrsController &lt; ApplicationController @@ -21,15 +22,15 @@ class ErrsController &lt; ApplicationController
21 22
22 def all 23 def all
23 app_scope = current_user.admin? ? App.all : current_user.apps 24 app_scope = current_user.admin? ? App.all : current_user.apps
24 - @selected_errs = params[:errs] || []  
25 - @errs = Err.for_apps(app_scope).ordered.paginate(:page => params[:page], :per_page => current_user.per_page) 25 + @problems = Problem.for_apps(app_scope).ordered.paginate(:page => params[:page], :per_page => current_user.per_page)
  26 + @selected_problems = params[:problems] || []
26 end 27 end
27 28
28 29
29 def show 30 def show
30 - page = (params[:notice] || @err.notices_count) 31 + page = (params[:notice] || @problem.notices_count)
31 page = 1 if page.to_i.zero? 32 page = 1 if page.to_i.zero?
32 - @notices = @err.notices.ordered.paginate(:page => page, :per_page => 1) 33 + @notices = @problem.notices.paginate(:page => page, :per_page => 1)
33 @notice = @notices.first 34 @notice = @notices.first
34 @comment = Comment.new 35 @comment = Comment.new
35 end 36 end
@@ -39,32 +40,30 @@ class ErrsController &lt; ApplicationController @@ -39,32 +40,30 @@ class ErrsController &lt; ApplicationController
39 set_tracker_params 40 set_tracker_params
40 41
41 if @app.issue_tracker 42 if @app.issue_tracker
42 - @app.issue_tracker.create_issue @err 43 + @app.issue_tracker.create_issue @problem
43 else 44 else
44 flash[:error] = "This app has no issue tracker setup." 45 flash[:error] = "This app has no issue tracker setup."
45 end 46 end
46 - redirect_to app_err_path(@app, @err) 47 + redirect_to app_err_path(@app, @problem)
47 rescue ActiveResource::ConnectionError => e 48 rescue ActiveResource::ConnectionError => e
48 Rails.logger.error e.to_s 49 Rails.logger.error e.to_s
49 flash[:error] = "There was an error during issue creation. Check your tracker settings or try again later." 50 flash[:error] = "There was an error during issue creation. Check your tracker settings or try again later."
50 - redirect_to app_err_path(@app, @err) 51 + redirect_to app_err_path(@app, @problem)
51 end 52 end
52 53
53 54
54 def unlink_issue 55 def unlink_issue
55 - @err.update_attribute :issue_link, nil  
56 - redirect_to app_err_path(@app, @err) 56 + @problem.update_attribute :issue_link, nil
  57 + redirect_to app_err_path(@app, @problem)
57 end 58 end
58 59
59 60
60 def resolve 61 def resolve
61 # Deal with bug in mongoid where find is returning an Enumberable obj 62 # Deal with bug in mongoid where find is returning an Enumberable obj
62 - @err = @err.first if @err.respond_to?(:first)  
63 -  
64 - @err.resolve! 63 + @problem = @problem.first if @problem.respond_to?(:first)
65 64
  65 + @problem.resolve!
66 flash[:success] = 'Great news everyone! The err has been resolved.' 66 flash[:success] = 'Great news everyone! The err has been resolved.'
67 -  
68 redirect_to :back 67 redirect_to :back
69 rescue ActionController::RedirectBackError 68 rescue ActionController::RedirectBackError
70 redirect_to app_path(@app) 69 redirect_to app_path(@app)
@@ -74,13 +73,13 @@ class ErrsController &lt; ApplicationController @@ -74,13 +73,13 @@ class ErrsController &lt; ApplicationController
74 def create_comment 73 def create_comment
75 @comment = Comment.new(params[:comment].merge(:user_id => current_user.id)) 74 @comment = Comment.new(params[:comment].merge(:user_id => current_user.id))
76 if @comment.valid? 75 if @comment.valid?
77 - @err.comments << @comment  
78 - @err.save 76 + @problem.comments << @comment
  77 + @problem.save
79 flash[:success] = "Comment saved!" 78 flash[:success] = "Comment saved!"
80 else 79 else
81 flash[:error] = "I'm sorry, your comment was blank! Try again?" 80 flash[:error] = "I'm sorry, your comment was blank! Try again?"
82 end 81 end
83 - redirect_to app_err_path(@app, @err) 82 + redirect_to app_err_path(@app, @problem)
84 end 83 end
85 84
86 85
@@ -91,27 +90,27 @@ class ErrsController &lt; ApplicationController @@ -91,27 +90,27 @@ class ErrsController &lt; ApplicationController
91 else 90 else
92 flash[:error] = "Sorry, I couldn't delete your comment for some reason. I hope you don't have any sensitive information in there!" 91 flash[:error] = "Sorry, I couldn't delete your comment for some reason. I hope you don't have any sensitive information in there!"
93 end 92 end
94 - redirect_to app_err_path(@app, @err) 93 + redirect_to app_err_path(@app, @problem)
95 end 94 end
96 95
97 96
98 def resolve_several 97 def resolve_several
99 - @selected_errs.each(&:resolve!)  
100 - flash[:success] = "Great news everyone! #{pluralize(@selected_errs.count, 'err has', 'errs have')} been resolved." 98 + @selected_problems.each(&:resolve!)
  99 + flash[:success] = "Great news everyone! #{pluralize(@selected_problems.count, 'err has', 'errs have')} been resolved."
101 redirect_to :back 100 redirect_to :back
102 end 101 end
103 102
104 103
105 def unresolve_several 104 def unresolve_several
106 - @selected_errs.each(&:unresolve!)  
107 - flash[:success] = "#{pluralize(@selected_errs.count, 'err has', 'errs have')} been unresolved." 105 + @selected_problems.each(&:unresolve!)
  106 + flash[:success] = "#{pluralize(@selected_problems.count, 'err has', 'errs have')} been unresolved."
108 redirect_to :back 107 redirect_to :back
109 end 108 end
110 109
111 110
112 def destroy_several 111 def destroy_several
113 - @selected_errs.each(&:destroy)  
114 - flash[:notice] = "#{pluralize(@selected_errs.count, 'err has', 'errs have')} been deleted." 112 + @selected_problems.each(&:destroy)
  113 + flash[:notice] = "#{pluralize(@selected_problems.count, 'err has', 'errs have')} been deleted."
115 redirect_to :back 114 redirect_to :back
116 end 115 end
117 116
@@ -128,8 +127,11 @@ protected @@ -128,8 +127,11 @@ protected
128 end 127 end
129 128
130 129
131 - def find_err  
132 - @err = @app.errs.find(params[:id]) 130 + def find_problem
  131 + @problem = @app.problems.find(params[:id])
  132 +
  133 + # Deal with bug in mogoid where find is returning an Enumberable obj
  134 + @problem = @problem.first if @problem.respond_to?(:first)
133 end 135 end
134 136
135 137
@@ -140,13 +142,13 @@ protected @@ -140,13 +142,13 @@ protected
140 end 142 end
141 143
142 144
143 - def find_selected_errs  
144 - err_ids = (params[:errs] || []).compact 145 + def find_selected_problems
  146 + err_ids = (params[:problems] || []).compact
145 if err_ids.empty? 147 if err_ids.empty?
146 flash[:notice] = "You have not selected any errors" 148 flash[:notice] = "You have not selected any errors"
147 redirect_to :back 149 redirect_to :back
148 else 150 else
149 - @selected_errs = Array(Err.find(err_ids)) 151 + @selected_problems = Array(Problem.find(err_ids))
150 end 152 end
151 end 153 end
152 154
app/helpers/errs_helper.rb
1 module ErrsHelper 1 module ErrsHelper
2 -  
3 - def last_notice_at err  
4 - err.last_notice_at || err.created_at 2 +
  3 + def last_notice_at(problem)
  4 + problem.last_notice_at || problem.created_at
5 end 5 end
6 - 6 +
7 def err_confirm 7 def err_confirm
8 Errbit::Config.confirm_resolve_err === false ? nil : 'Seriously?' 8 Errbit::Config.confirm_resolve_err === false ? nil : 'Seriously?'
9 end 9 end
10 -  
11 - def link_to_github app, line, text=nil 10 +
  11 + def link_to_github(app, line, text=nil)
12 file_name = line['file'].split('/').last 12 file_name = line['file'].split('/').last
13 file_path = line['file'].gsub('[PROJECT_ROOT]', '') 13 file_path = line['file'].gsub('[PROJECT_ROOT]', '')
14 line_number = line['number'] 14 line_number = line['number']
15 link_to(text || file_name, "#{app.github_url_to_file(file_path)}#L#{line_number}", :target => '_blank') 15 link_to(text || file_name, "#{app.github_url_to_file(file_path)}#L#{line_number}", :target => '_blank')
16 end 16 end
17 - 17 +
18 end 18 end
19 \ No newline at end of file 19 \ No newline at end of file
app/mailers/mailer.rb
@@ -4,21 +4,21 @@ require Rails.root.join(&#39;config/routes.rb&#39;) @@ -4,21 +4,21 @@ require Rails.root.join(&#39;config/routes.rb&#39;)
4 4
5 class Mailer < ActionMailer::Base 5 class Mailer < ActionMailer::Base
6 default :from => Errbit::Config.email_from 6 default :from => Errbit::Config.email_from
7 - 7 +
8 def err_notification(notice) 8 def err_notification(notice)
9 @notice = notice 9 @notice = notice
10 - @app = notice.err.app  
11 - 10 + @app = notice.app
  11 +
12 mail({ 12 mail({
13 :to => @app.notification_recipients, 13 :to => @app.notification_recipients,
14 - :subject => "[#{@app.name}][#{@notice.err.environment}] #{@notice.err.message}" 14 + :subject => "[#{@app.name}][#{@notice.environment_name}] #{@notice.message}"
15 }) 15 })
16 end 16 end
17 - 17 +
18 def deploy_notification(deploy) 18 def deploy_notification(deploy)
19 @deploy = deploy 19 @deploy = deploy
20 - @app = deploy.app  
21 - 20 + @app = deploy.app
  21 +
22 mail({ 22 mail({
23 :to => @app.notification_recipients, 23 :to => @app.notification_recipients,
24 :subject => "[#{@app.name}] Deployed to #{@deploy.environment} by #{@deploy.username}" 24 :subject => "[#{@app.name}] Deployed to #{@deploy.environment} by #{@deploy.username}"
app/models/app.rb
1 class App 1 class App
2 include Mongoid::Document 2 include Mongoid::Document
3 include Mongoid::Timestamps 3 include Mongoid::Timestamps
4 - 4 +
5 field :name, :type => String 5 field :name, :type => String
6 field :api_key 6 field :api_key
7 field :github_url 7 field :github_url
@@ -10,53 +10,65 @@ class App @@ -10,53 +10,65 @@ class App
10 field :notify_on_errs, :type => Boolean, :default => true 10 field :notify_on_errs, :type => Boolean, :default => true
11 field :notify_on_deploys, :type => Boolean, :default => false 11 field :notify_on_deploys, :type => Boolean, :default => false
12 field :email_at_notices, :type => Array, :default => Errbit::Config.email_at_notices 12 field :email_at_notices, :type => Array, :default => Errbit::Config.email_at_notices
13 - 13 +
14 # Some legacy apps may have string as key instead of BSON::ObjectID 14 # Some legacy apps may have string as key instead of BSON::ObjectID
15 identity :type => String 15 identity :type => String
  16 +
16 # There seems to be a Mongoid bug making it impossible to use String identity with references_many feature: 17 # There seems to be a Mongoid bug making it impossible to use String identity with references_many feature:
17 # https://github.com/mongoid/mongoid/issues/703 18 # https://github.com/mongoid/mongoid/issues/703
18 # Using 32 character string as a workaround. 19 # Using 32 character string as a workaround.
19 before_create do |r| 20 before_create do |r|
20 r.id = ActiveSupport::SecureRandom.hex 21 r.id = ActiveSupport::SecureRandom.hex
21 end 22 end
22 - 23 +
23 embeds_many :watchers 24 embeds_many :watchers
24 embeds_many :deploys 25 embeds_many :deploys
25 embeds_one :issue_tracker 26 embeds_one :issue_tracker
26 - has_many :errs, :inverse_of => :app, :dependent => :destroy  
27 - 27 + has_many :problems, :inverse_of => :app, :dependent => :destroy
  28 +
28 before_validation :generate_api_key, :on => :create 29 before_validation :generate_api_key, :on => :create
29 before_save :normalize_github_url 30 before_save :normalize_github_url
30 - 31 +
31 validates_presence_of :name, :api_key 32 validates_presence_of :name, :api_key
32 validates_uniqueness_of :name, :allow_blank => true 33 validates_uniqueness_of :name, :allow_blank => true
33 validates_uniqueness_of :api_key, :allow_blank => true 34 validates_uniqueness_of :api_key, :allow_blank => true
34 validates_associated :watchers 35 validates_associated :watchers
35 validate :check_issue_tracker 36 validate :check_issue_tracker
36 - 37 +
37 accepts_nested_attributes_for :watchers, :allow_destroy => true, 38 accepts_nested_attributes_for :watchers, :allow_destroy => true,
38 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } 39 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? }
39 accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, 40 accepts_nested_attributes_for :issue_tracker, :allow_destroy => true,
40 :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) } 41 :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) }
41 - 42 +
  43 +
  44 +
  45 + def find_or_create_err!(attrs)
  46 + Err.where(attrs).first || problems.create!.errs.create!(attrs)
  47 + end
  48 +
  49 +
  50 +
  51 + # Mongoid Bug: find(id) on association proxies returns an Enumerator
42 def self.find_by_id!(app_id) 52 def self.find_by_id!(app_id)
43 find app_id 53 find app_id
44 end 54 end
45 - 55 +
46 def self.find_by_api_key!(key) 56 def self.find_by_api_key!(key)
47 where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) 57 where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key))
48 end 58 end
49 - 59 +
50 def last_deploy_at 60 def last_deploy_at
51 deploys.last && deploys.last.created_at 61 deploys.last && deploys.last.created_at
52 end 62 end
53 - 63 +
  64 +
54 # Legacy apps don't have notify_on_errs and notify_on_deploys params 65 # Legacy apps don't have notify_on_errs and notify_on_deploys params
55 def notify_on_errs 66 def notify_on_errs
56 !(self[:notify_on_errs] == false) 67 !(self[:notify_on_errs] == false)
57 end 68 end
58 alias :notify_on_errs? :notify_on_errs 69 alias :notify_on_errs? :notify_on_errs
59 - 70 +
  71 +
60 def notify_on_deploys 72 def notify_on_deploys
61 !(self[:notify_on_deploys] == false) 73 !(self[:notify_on_deploys] == false)
62 end 74 end
@@ -117,4 +129,3 @@ class App @@ -117,4 +129,3 @@ class App
117 end 129 end
118 130
119 end 131 end
120 -  
app/models/deploy.rb
1 class Deploy 1 class Deploy
2 include Mongoid::Document 2 include Mongoid::Document
3 include Mongoid::Timestamps 3 include Mongoid::Timestamps
4 - 4 +
5 field :username 5 field :username
6 field :repository 6 field :repository
7 field :environment 7 field :environment
8 field :revision 8 field :revision
9 field :message 9 field :message
10 - 10 +
11 index :created_at, Mongo::DESCENDING 11 index :created_at, Mongo::DESCENDING
12 - 12 +
13 embedded_in :app, :inverse_of => :deploys 13 embedded_in :app, :inverse_of => :deploys
14 - 14 +
15 after_create :deliver_notification, :if => :should_notify? 15 after_create :deliver_notification, :if => :should_notify?
16 after_create :resolve_app_errs, :if => :should_resolve_app_errs? 16 after_create :resolve_app_errs, :if => :should_resolve_app_errs?
17 - 17 +
18 validates_presence_of :username, :environment 18 validates_presence_of :username, :environment
19 - 19 +
20 def deliver_notification 20 def deliver_notification
21 Mailer.deploy_notification(self).deliver 21 Mailer.deploy_notification(self).deliver
22 end 22 end
23 - 23 +
24 def resolve_app_errs 24 def resolve_app_errs
25 - app.errs.unresolved.in_env(environment).each {|err| err.resolve!} 25 + app.problems.unresolved.in_env(environment).each {|problem| problem.resolve!}
26 end 26 end
27 - 27 +
28 def short_revision 28 def short_revision
29 revision.to_s[0,7] 29 revision.to_s[0,7]
30 end 30 end
31 - 31 +
32 protected 32 protected
33 - 33 +
34 def should_notify? 34 def should_notify?
35 app.notify_on_deploys? && app.watchers.any? 35 app.notify_on_deploys? && app.watchers.any?
36 end 36 end
37 - 37 +
38 def should_resolve_app_errs? 38 def should_resolve_app_errs?
39 app.resolve_errs_on_deploy? 39 app.resolve_errs_on_deploy?
40 end 40 end
41 - 41 +
42 end 42 end
43 43
app/models/err.rb
  1 +# An Err is a group of notices that can programatically
  2 +# be determined to be equal. (Errbit groups notices into
  3 +# errs by a notice's fingerprint.)
  4 +
1 class Err 5 class Err
2 include Mongoid::Document 6 include Mongoid::Document
3 include Mongoid::Timestamps 7 include Mongoid::Timestamps
4 - 8 +
5 field :klass 9 field :klass
6 field :component 10 field :component
7 field :action 11 field :action
8 field :environment 12 field :environment
9 field :fingerprint 13 field :fingerprint
10 - field :last_notice_at, :type => DateTime  
11 - field :resolved, :type => Boolean, :default => false  
12 - field :issue_link, :type => String  
13 - field :notices_count, :type => Integer, :default => 0  
14 - field :message  
15 -  
16 - index :last_notice_at  
17 - index :app_id  
18 - index :notices  
19 -  
20 - belongs_to :app  
21 - has_many :notices  
22 - has_many :comments, :inverse_of => :err, :dependent => :destroy  
23 -  
24 - validates_presence_of :klass, :environment  
25 -  
26 - scope :resolved, where(:resolved => true)  
27 - scope :unresolved, where(:resolved => false)  
28 - scope :ordered, order_by(:last_notice_at.desc)  
29 - scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))}  
30 -  
31 - def self.in_env(env)  
32 - env.present? ? where(:environment => env) : scoped  
33 - end  
34 -  
35 - def self.for(attrs)  
36 - app = attrs.delete(:app)  
37 - app.errs.where(attrs).first || app.errs.create!(attrs)  
38 - end  
39 -  
40 - def resolve!  
41 - self.update_attributes!(:resolved => true)  
42 - end  
43 -  
44 - def unresolve!  
45 - self.update_attributes!(:resolved => false)  
46 - end  
47 -  
48 - def unresolved?  
49 - !resolved?  
50 - end  
51 -  
52 - def where  
53 - where = component.dup  
54 - where << "##{action}" if action.present?  
55 - where  
56 - end  
57 -  
58 - def message  
59 - super || klass  
60 - end  
61 - 14 +
  15 + belongs_to :problem
  16 + has_many :notices, :inverse_of => :err, :dependent => :destroy
  17 +
  18 + validates_presence_of :klass, :environment, :problem
  19 +
  20 + delegate :app,
  21 + :resolved?,
  22 + :to => :problem
  23 +
  24 +
62 end 25 end
app/models/issue_tracker.rb
@@ -19,8 +19,8 @@ class IssueTracker @@ -19,8 +19,8 @@ class IssueTracker
19 # Subclasses are responsible for overwriting this method. 19 # Subclasses are responsible for overwriting this method.
20 def check_params; true; end 20 def check_params; true; end
21 21
22 - def issue_title err  
23 - "[#{ err.environment }][#{ err.where }] #{err.message.to_s.truncate(100)}" 22 + def issue_title(problem)
  23 + "[#{ problem.environment }][#{ problem.where }] #{problem.message.to_s.truncate(100)}"
24 end 24 end
25 25
26 # Allows us to set the issue tracker class from a single form. 26 # Allows us to set the issue tracker class from a single form.
app/models/issue_trackers/fogbugz_tracker.rb
@@ -22,19 +22,19 @@ class IssueTrackers::FogbugzTracker &lt; IssueTracker @@ -22,19 +22,19 @@ class IssueTrackers::FogbugzTracker &lt; IssueTracker
22 end 22 end
23 end 23 end
24 24
25 - def create_issue(err) 25 + def create_issue(problem)
26 fogbugz = Fogbugz::Interface.new(:email => username, :password => password, :uri => "https://#{account}.fogbugz.com") 26 fogbugz = Fogbugz::Interface.new(:email => username, :password => password, :uri => "https://#{account}.fogbugz.com")
27 fogbugz.authenticate 27 fogbugz.authenticate
28 28
29 issue = {} 29 issue = {}
30 - issue['sTitle'] = issue_title err 30 + issue['sTitle'] = issue_title problem
31 issue['sArea'] = project_id 31 issue['sArea'] = project_id
32 issue['sEvent'] = body_template.result(binding) 32 issue['sEvent'] = body_template.result(binding)
33 issue['sTags'] = ['errbit'].join(',') 33 issue['sTags'] = ['errbit'].join(',')
34 issue['cols'] = ['ixBug'].join(',') 34 issue['cols'] = ['ixBug'].join(',')
35 35
36 fb_resp = fogbugz.command(:new, issue) 36 fb_resp = fogbugz.command(:new, issue)
37 - err.update_attribute :issue_link, "https://#{account}.fogbugz.com/default.asp?#{fb_resp['case']['ixBug']}" 37 + problem.update_attribute :issue_link, "https://#{account}.fogbugz.com/default.asp?#{fb_resp['case']['ixBug']}"
38 end 38 end
39 39
40 def body_template 40 def body_template
app/models/issue_trackers/lighthouse_tracker.rb
@@ -18,20 +18,20 @@ class IssueTrackers::LighthouseTracker &lt; IssueTracker @@ -18,20 +18,20 @@ class IssueTrackers::LighthouseTracker &lt; IssueTracker
18 end 18 end
19 end 19 end
20 20
21 - def create_issue(err) 21 + def create_issue(problem)
22 Lighthouse.account = account 22 Lighthouse.account = account
23 Lighthouse.token = api_token 23 Lighthouse.token = api_token
24 # updating lighthouse account 24 # updating lighthouse account
25 Lighthouse::Ticket.site 25 Lighthouse::Ticket.site
26 26
27 ticket = Lighthouse::Ticket.new(:project_id => project_id) 27 ticket = Lighthouse::Ticket.new(:project_id => project_id)
28 - ticket.title = issue_title err 28 + ticket.title = issue_title problem
29 29
30 ticket.body = body_template.result(binding) 30 ticket.body = body_template.result(binding)
31 31
32 ticket.tags << "errbit" 32 ticket.tags << "errbit"
33 ticket.save! 33 ticket.save!
34 - 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$/, '') 34 + 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$/, '')
35 end 35 end
36 36
37 def body_template 37 def body_template
app/models/issue_trackers/mingle_tracker.rb
@@ -27,21 +27,21 @@ class IssueTrackers::MingleTracker &lt; IssueTracker @@ -27,21 +27,21 @@ class IssueTrackers::MingleTracker &lt; IssueTracker
27 end 27 end
28 end 28 end
29 29
30 - def create_issue(err) 30 + def create_issue(problem)
31 properties = ticket_properties_hash 31 properties = ticket_properties_hash
32 basic_auth = account.gsub(/https?:\/\//, "https://#{username}:#{password}@") 32 basic_auth = account.gsub(/https?:\/\//, "https://#{username}:#{password}@")
33 Mingle.set_site "#{basic_auth}/api/v1/projects/#{project_id}/" 33 Mingle.set_site "#{basic_auth}/api/v1/projects/#{project_id}/"
34 34
35 card = Mingle::Card.new 35 card = Mingle::Card.new
36 card.card_type_name = properties.delete("card_type") 36 card.card_type_name = properties.delete("card_type")
37 - card.name = issue_title(err) 37 + card.name = issue_title(problem)
38 card.description = body_template.result(binding) 38 card.description = body_template.result(binding)
39 properties.each do |property, value| 39 properties.each do |property, value|
40 card.send("cp_#{property}=", value) 40 card.send("cp_#{property}=", value)
41 end 41 end
42 42
43 card.save! 43 card.save!
44 - err.update_attribute :issue_link, URI.parse("#{account}/projects/#{project_id}/cards/#{card.id}").to_s 44 + problem.update_attribute :issue_link, URI.parse("#{account}/projects/#{project_id}/cards/#{card.id}").to_s
45 end 45 end
46 46
47 def body_template 47 def body_template
app/models/issue_trackers/pivotal_labs_tracker.rb
@@ -13,12 +13,12 @@ class IssueTrackers::PivotalLabsTracker &lt; IssueTracker @@ -13,12 +13,12 @@ class IssueTrackers::PivotalLabsTracker &lt; IssueTracker
13 end 13 end
14 end 14 end
15 15
16 - def create_issue(err) 16 + def create_issue(problem)
17 PivotalTracker::Client.token = api_token 17 PivotalTracker::Client.token = api_token
18 PivotalTracker::Client.use_ssl = true 18 PivotalTracker::Client.use_ssl = true
19 project = PivotalTracker::Project.find project_id.to_i 19 project = PivotalTracker::Project.find project_id.to_i
20 - story = project.stories.create :name => issue_title(err), :story_type => 'bug', :description => body_template.result(binding)  
21 - err.update_attribute :issue_link, "https://www.pivotaltracker.com/story/show/#{story.id}" 20 + story = project.stories.create :name => issue_title(problem), :story_type => 'bug', :description => body_template.result(binding)
  21 + problem.update_attribute :issue_link, "https://www.pivotaltracker.com/story/show/#{story.id}"
22 end 22 end
23 23
24 def body_template 24 def body_template
app/models/issue_trackers/redmine_tracker.rb
@@ -17,7 +17,7 @@ class IssueTrackers::RedmineTracker &lt; IssueTracker @@ -17,7 +17,7 @@ class IssueTrackers::RedmineTracker &lt; IssueTracker
17 end 17 end
18 end 18 end
19 19
20 - def create_issue(err) 20 + def create_issue(problem)
21 token = api_token 21 token = api_token
22 acc = account 22 acc = account
23 RedmineClient::Base.configure do 23 RedmineClient::Base.configure do
@@ -25,10 +25,10 @@ class IssueTrackers::RedmineTracker &lt; IssueTracker @@ -25,10 +25,10 @@ class IssueTrackers::RedmineTracker &lt; IssueTracker
25 self.site = acc 25 self.site = acc
26 end 26 end
27 issue = RedmineClient::Issue.new(:project_id => project_id) 27 issue = RedmineClient::Issue.new(:project_id => project_id)
28 - issue.subject = issue_title err 28 + issue.subject = issue_title problem
29 issue.description = body_template.result(binding) 29 issue.description = body_template.result(binding)
30 issue.save! 30 issue.save!
31 - 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}") 31 + 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}")
32 end 32 end
33 33
34 def body_template 34 def body_template
app/models/notice.rb
@@ -4,46 +4,50 @@ require &#39;recurse&#39; @@ -4,46 +4,50 @@ require &#39;recurse&#39;
4 class Notice 4 class Notice
5 include Mongoid::Document 5 include Mongoid::Document
6 include Mongoid::Timestamps 6 include Mongoid::Timestamps
7 - 7 +
8 field :message 8 field :message
9 field :backtrace, :type => Array 9 field :backtrace, :type => Array
10 field :server_environment, :type => Hash 10 field :server_environment, :type => Hash
11 field :request, :type => Hash 11 field :request, :type => Hash
12 field :notifier, :type => Hash 12 field :notifier, :type => Hash
13 - 13 + field :klass
  14 +
14 belongs_to :err 15 belongs_to :err
15 index :err_id 16 index :err_id
16 -  
17 - after_create :cache_last_notice_at 17 + index :created_at
  18 +
18 after_create :deliver_notification, :if => :should_notify? 19 after_create :deliver_notification, :if => :should_notify?
19 - before_create :increase_counter_cache, :cache_message 20 + after_create :increase_counter_cache, :cache_attributes_on_problem
20 before_save :sanitize 21 before_save :sanitize
21 before_destroy :decrease_counter_cache 22 before_destroy :decrease_counter_cache
22 - 23 +
23 validates_presence_of :backtrace, :server_environment, :notifier 24 validates_presence_of :backtrace, :server_environment, :notifier
24 - 25 +
25 scope :ordered, order_by(:created_at.asc) 26 scope :ordered, order_by(:created_at.asc)
26 - index :created_at  
27 - 27 + scope :for_errs, lambda {|errs| where(:err_id.in => errs.all.map(&:id))}
  28 +
  29 + delegate :app, :problem, :to => :err
  30 +
  31 +
  32 +
28 def self.from_xml(hoptoad_xml) 33 def self.from_xml(hoptoad_xml)
29 hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml) 34 hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml)
30 app = App.find_by_api_key!(hoptoad_notice['api-key']) 35 app = App.find_by_api_key!(hoptoad_notice['api-key'])
31 - 36 +
32 hoptoad_notice['request'] ||= {} 37 hoptoad_notice['request'] ||= {}
33 hoptoad_notice['request']['component'] = 'unknown' if hoptoad_notice['request']['component'].blank? 38 hoptoad_notice['request']['component'] = 'unknown' if hoptoad_notice['request']['component'].blank?
34 hoptoad_notice['request']['action'] = nil if hoptoad_notice['request']['action'].blank? 39 hoptoad_notice['request']['action'] = nil if hoptoad_notice['request']['action'].blank?
35 -  
36 - err = Err.for({  
37 - :app => app,  
38 - :klass => hoptoad_notice['error']['class'],  
39 - :component => hoptoad_notice['request']['component'],  
40 - :action => hoptoad_notice['request']['action'],  
41 - :environment => hoptoad_notice['server-environment']['environment-name'],  
42 - :fingerprint => hoptoad_notice['fingerprint'] 40 +
  41 + err = app.find_or_create_err!({
  42 + :klass => hoptoad_notice['error']['class'],
  43 + :component => hoptoad_notice['request']['component'],
  44 + :action => hoptoad_notice['request']['action'],
  45 + :environment => hoptoad_notice['server-environment']['environment-name'],
  46 + :fingerprint => hoptoad_notice['fingerprint']
43 }) 47 })
44 - err.update_attributes(:resolved => false) if err.resolved?  
45 - 48 + err.problem.update_attributes(:resolved => false) if err.problem.resolved?
46 err.notices.create!({ 49 err.notices.create!({
  50 + :klass => hoptoad_notice['error']['class'],
47 :message => hoptoad_notice['error']['message'], 51 :message => hoptoad_notice['error']['message'],
48 :backtrace => [hoptoad_notice['error']['backtrace']['line']].flatten, 52 :backtrace => [hoptoad_notice['error']['backtrace']['line']].flatten,
49 :server_environment => hoptoad_notice['server-environment'], 53 :server_environment => hoptoad_notice['server-environment'],
@@ -51,64 +55,97 @@ class Notice @@ -51,64 +55,97 @@ class Notice
51 :notifier => hoptoad_notice['notifier'] 55 :notifier => hoptoad_notice['notifier']
52 }) 56 })
53 end 57 end
54 - 58 +
  59 +
  60 +
55 def user_agent 61 def user_agent
56 agent_string = env_vars['HTTP_USER_AGENT'] 62 agent_string = env_vars['HTTP_USER_AGENT']
57 agent_string.blank? ? nil : UserAgent.parse(agent_string) 63 agent_string.blank? ? nil : UserAgent.parse(agent_string)
58 end 64 end
59 - 65 +
  66 + def environment_name
  67 + server_environment['server-environment'] || server_environment['environment-name']
  68 + end
  69 +
  70 + def component
  71 + request['component']
  72 + end
  73 +
  74 + def action
  75 + request['action']
  76 + end
  77 +
  78 + def where
  79 + where = component.to_s.dup
  80 + where << "##{action}" if action.present?
  81 + where
  82 + end
  83 +
  84 +
  85 +
60 def self.in_app_backtrace_line? line 86 def self.in_app_backtrace_line? line
61 !!(line['file'] =~ %r{^\[PROJECT_ROOT\]/(?!(vendor))}) 87 !!(line['file'] =~ %r{^\[PROJECT_ROOT\]/(?!(vendor))})
62 end 88 end
63 - 89 +
  90 +
  91 +
64 def request 92 def request
65 read_attribute(:request) || {} 93 read_attribute(:request) || {}
66 end 94 end
67 - 95 +
68 def env_vars 96 def env_vars
69 request['cgi-data'] || {} 97 request['cgi-data'] || {}
70 end 98 end
71 - 99 +
72 def params 100 def params
73 request['params'] || {} 101 request['params'] || {}
74 end 102 end
75 - 103 +
76 def session 104 def session
77 request['session'] || {} 105 request['session'] || {}
78 end 106 end
79 - 107 +
  108 +
  109 +
80 def deliver_notification 110 def deliver_notification
81 Mailer.err_notification(self).deliver 111 Mailer.err_notification(self).deliver
82 end 112 end
83 - 113 +
84 def cache_last_notice_at 114 def cache_last_notice_at
85 err.update_attributes(:last_notice_at => created_at) 115 err.update_attributes(:last_notice_at => created_at)
86 end 116 end
87 - 117 +
88 # Backtrace containing only files from the app itself (ignore gems) 118 # Backtrace containing only files from the app itself (ignore gems)
89 def app_backtrace 119 def app_backtrace
90 backtrace.select { |l| l && l['file'] && l['file'].include?("[PROJECT_ROOT]") } 120 backtrace.select { |l| l && l['file'] && l['file'].include?("[PROJECT_ROOT]") }
91 end 121 end
92 -  
93 - protected  
94 - 122 +
  123 +
  124 +
  125 +protected
  126 +
  127 +
  128 +
95 def should_notify? 129 def should_notify?
96 - 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? 130 + 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?
97 end 131 end
98 -  
99 - 132 +
  133 +
100 def increase_counter_cache 134 def increase_counter_cache
101 - err.inc(:notices_count,1) 135 + problem.inc(:notices_count, 1)
102 end 136 end
103 - 137 +
  138 +
104 def decrease_counter_cache 139 def decrease_counter_cache
105 - err.inc(:notices_count,-1) 140 + problem.inc(:notices_count, -1)
106 end 141 end
107 -  
108 - def cache_message  
109 - err.update_attribute(:message, message) if err.notices_count == 1 142 +
  143 +
  144 + def cache_attributes_on_problem
  145 + problem.cache_notice_attributes(self) if problem.notices_count == 1
110 end 146 end
111 - 147 +
  148 +
112 def sanitize 149 def sanitize
113 [:server_environment, :request, :notifier].each do |h| 150 [:server_environment, :request, :notifier].each do |h|
114 send("#{h}=",sanitize_hash(send(h))) 151 send("#{h}=",sanitize_hash(send(h)))
@@ -116,7 +153,8 @@ class Notice @@ -116,7 +153,8 @@ class Notice
116 # Set unknown backtrace files 153 # Set unknown backtrace files
117 backtrace.each{|line| line['file'] = "[unknown source]" if line['file'].blank? } 154 backtrace.each{|line| line['file'] = "[unknown source]" if line['file'].blank? }
118 end 155 end
119 - 156 +
  157 +
120 def sanitize_hash(h) 158 def sanitize_hash(h)
121 h.recurse do 159 h.recurse do
122 |h| h.inject({}) do |h,(k,v)| 160 |h| h.inject({}) do |h,(k,v)|
@@ -129,5 +167,6 @@ class Notice @@ -129,5 +167,6 @@ class Notice
129 end 167 end
130 end 168 end
131 end 169 end
  170 +
  171 +
132 end 172 end
133 -  
app/models/problem.rb 0 → 100644
@@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
  1 +# An Problem is a group of errs that the user
  2 +# has declared to be equal.
  3 +
  4 +class Problem
  5 + include Mongoid::Document
  6 + include Mongoid::Timestamps
  7 +
  8 + field :last_notice_at, :type => DateTime
  9 + field :resolved, :type => Boolean, :default => false
  10 + field :issue_link, :type => String
  11 +
  12 + # Cached fields
  13 + field :notices_count, :type => Integer, :default => 0
  14 + field :message
  15 + field :environment
  16 + field :klass
  17 + field :where
  18 +
  19 + index :last_notice_at
  20 + index :app_id
  21 +
  22 + belongs_to :app
  23 + has_many :errs, :inverse_of => :problem, :dependent => :destroy
  24 + has_many :comments, :inverse_of => :err, :dependent => :destroy
  25 +
  26 + scope :resolved, where(:resolved => true)
  27 + scope :unresolved, where(:resolved => false)
  28 + scope :ordered, order_by(:last_notice_at.desc)
  29 + scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))}
  30 +
  31 + def self.in_env(env)
  32 + env.present? ? where(:environment => env) : scoped
  33 + end
  34 +
  35 +
  36 +
  37 + def notices
  38 + Notice.for_errs(errs).ordered
  39 + end
  40 +
  41 +
  42 + def resolve!
  43 + self.update_attributes!(:resolved => true)
  44 + end
  45 +
  46 +
  47 + def unresolve!
  48 + self.update_attributes!(:resolved => false)
  49 + end
  50 +
  51 +
  52 + def unresolved?
  53 + !resolved?
  54 + end
  55 +
  56 +
  57 +
  58 + def cache_notice_attributes(notice=nil)
  59 + notice ||= notices.first
  60 + attrs = {:last_notice_at => notices.max(:created_at)}
  61 + attrs.merge!(
  62 + :message => notice.message,
  63 + :environment => notice.environment_name,
  64 + :klass => notice.klass,
  65 + :where => notice.where) if notice
  66 + update_attributes!(attrs)
  67 + end
  68 +
  69 +
  70 +end
0 \ No newline at end of file 71 \ No newline at end of file
app/views/apps/index.html.haml
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 %td.name= link_to app.name, app_path(app) 14 %td.name= link_to app.name, app_path(app)
15 %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' 15 %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'
16 %td.count 16 %td.count
17 - - if app.errs.count > 0 17 + - if app.problems.count > 0
18 - unresolved = @unresolved_counts[app.id] 18 - unresolved = @unresolved_counts[app.id]
19 = link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil) 19 = link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil)
20 - else 20 - else
app/views/apps/show.html.haml
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 = javascript_include_tag 'apps.show' 4 = javascript_include_tag 'apps.show'
5 - content_for :meta do 5 - content_for :meta do
6 %strong Errs Caught: 6 %strong Errs Caught:
7 - = @app.errs.count 7 + = @app.problems.count
8 %strong Deploy Count: 8 %strong Deploy Count:
9 = @app.deploys.count 9 = @app.deploys.count
10 %strong API Key: 10 %strong API Key:
@@ -82,9 +82,9 @@ @@ -82,9 +82,9 @@
82 - else 82 - else
83 %h3 No deploys 83 %h3 No deploys
84 84
85 -- if @app.errs.count > 0 85 +- if @app.problems.any?
86 %h3.clear Errs 86 %h3.clear Errs
87 - = render 'errs/table', :errs => @errs 87 + = render 'errs/table', :errs => @problems
88 - else 88 - else
89 %h3.clear No errs have been caught yet, make sure you setup your app 89 %h3.clear No errs have been caught yet, make sure you setup your app
90 = render 'configuration_instructions', :app => @app 90 = render 'configuration_instructions', :app => @app
app/views/errs/_list.atom.builder
1 -feed.updated(@errs.first.created_at) 1 +feed.updated(@problems.first.created_at)
2 2
3 -for err in @errs  
4 - notice = err.notices.first 3 +for problem in @problems
  4 + notice = problem.notices.first
5 5
6 - feed.entry(err, :url => app_err_url(err.app, err)) do |entry|  
7 - entry.title "[#{ err.where }] #{err.message.to_s.truncate(27)}" 6 + feed.entry(problem, :url => app_err_url(problem.app, problem)) do |entry|
  7 + entry.title "[#{ problem.where }] #{problem.message.to_s.truncate(27)}"
8 entry.author do |author| 8 entry.author do |author|
9 - author.name "#{ err.app.name } [#{ err.environment }]" 9 + author.name "#{ problem.app.name } [#{ problem.environment }]"
10 end 10 end
11 if notice 11 if notice
12 entry.summary(notice_atom_summary(notice), :type => "html") 12 entry.summary(notice_atom_summary(notice), :type => "html")
app/views/errs/_table.html.haml
@@ -10,28 +10,28 @@ @@ -10,28 +10,28 @@
10 %th Count 10 %th Count
11 %th Resolve 11 %th Resolve
12 %tbody 12 %tbody
13 - - errs.each do |err|  
14 - %tr{:class => err.resolved? ? 'resolved' : 'unresolved'} 13 + - errs.each do |problem|
  14 + %tr{:class => problem.resolved? ? 'resolved' : 'unresolved'}
15 %td.select 15 %td.select
16 - = check_box_tag "errs[]", err.id, @selected_errs.member?(err.id.to_s) 16 + = check_box_tag "problems[]", problem.id, @selected_problems.member?(problem.id.to_s)
17 %td.app 17 %td.app
18 - = link_to err.app.name, app_path(err.app) 18 + = link_to problem.app.name, app_path(problem.app)
19 - if current_page?(:controller => 'errs') 19 - if current_page?(:controller => 'errs')
20 - %span.environment= link_to err.environment, errs_path(environment: err.environment) 20 + %span.environment= link_to problem.environment, errs_path(environment: problem.environment)
21 - else 21 - else
22 - %span.environment= link_to err.environment, app_path(err.app, environment: err.environment) 22 + %span.environment= link_to problem.environment, app_path(problem.app, environment: problem.environment)
23 %td.message 23 %td.message
24 - = link_to err.message, app_err_path(err.app, err)  
25 - %em= err.where  
26 - %td.latest #{time_ago_in_words(last_notice_at err)} ago  
27 - %td.deploy= err.app.last_deploy_at ? err.app.last_deploy_at.to_s(:micro) : 'n/a'  
28 - %td.count= link_to err.notices.count, app_err_path(err.app, err)  
29 - %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? 24 + = link_to problem.message, app_err_path(problem.app, problem)
  25 + %em= problem.where
  26 + %td.latest #{time_ago_in_words(last_notice_at problem)} ago
  27 + %td.deploy= problem.app.last_deploy_at ? problem.app.last_deploy_at.to_s(:micro) : 'n/a'
  28 + %td.count= link_to problem.notices.count, app_err_path(problem.app, problem)
  29 + %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?
30 - if errs.none? 30 - if errs.none?
31 %tr 31 %tr
32 %td{:colspan => (@app ? 5 : 6)} 32 %td{:colspan => (@app ? 5 : 6)}
33 %em No errs here 33 %em No errs here
34 - = will_paginate @errs, :previous_label => '&laquo; Previous', :next_label => 'Next &raquo;' 34 + = will_paginate @problems, :previous_label => '&laquo; Previous', :next_label => 'Next &raquo;'
35 .tab-bar 35 .tab-bar
36 %ul 36 %ul
37 %li= submit_tag 'Resolve', :id => 'resolve_errs', :class => 'button', 'data-action' => resolve_several_errs_path 37 %li= submit_tag 'Resolve', :id => 'resolve_errs', :class => 'button', 'data-action' => resolve_several_errs_path
app/views/errs/all.html.haml
1 - content_for :title, 'All Errs' 1 - content_for :title, 'All Errs'
2 - content_for :action_bar do 2 - content_for :action_bar do
3 = link_to 'hide resolved', errs_path, :class => 'button' 3 = link_to 'hide resolved', errs_path, :class => 'button'
4 -= render 'table', :errs => @errs  
5 \ No newline at end of file 4 \ No newline at end of file
  5 += render 'table', :errs => @problems
6 \ No newline at end of file 6 \ No newline at end of file
app/views/errs/index.html.haml
@@ -3,4 +3,4 @@ @@ -3,4 +3,4 @@
3 = auto_discovery_link_tag :atom, errs_url(User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices at #{root_url}" 3 = auto_discovery_link_tag :atom, errs_url(User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices at #{root_url}"
4 - content_for :action_bar do 4 - content_for :action_bar do
5 = link_to 'show resolved', all_errs_path, :class => 'button' 5 = link_to 'show resolved', all_errs_path, :class => 'button'
6 -= render 'table', :errs => @errs  
7 \ No newline at end of file 6 \ No newline at end of file
  7 += render 'table', :errs => @problems
8 \ No newline at end of file 8 \ No newline at end of file
app/views/errs/show.html.haml
1 -- content_for :page_title, @err.message  
2 -- content_for :title, @err.klass 1 +- content_for :page_title, @problem.message
  2 +- content_for :title, @problem.klass
3 - content_for :meta do 3 - content_for :meta do
4 %strong App: 4 %strong App:
5 = link_to @app.name, app_path(@app) 5 = link_to @app.name, app_path(@app)
6 %strong Where: 6 %strong Where:
7 - = @err.where 7 + = @problem.where
8 %br 8 %br
9 %strong Environment: 9 %strong Environment:
10 - = @err.environment 10 + = @problem.environment
11 %strong Last Notice: 11 %strong Last Notice:
12 - = last_notice_at(@err).to_s(:micro) 12 + = last_notice_at(@problem).to_s(:micro)
13 - content_for :action_bar do 13 - content_for :action_bar do
14 - - if @err.app.issue_tracker_configured?  
15 - - if @err.issue_link.blank?  
16 - %span= link_to 'create issue', create_issue_app_err_path(@app, @err), :method => :post, :class => "#{@app.issue_tracker.class::Label}_create create-issue" 14 + - if @problem.app.issue_tracker_configured?
  15 + - if @problem.issue_link.blank?
  16 + %span= link_to 'create issue', create_issue_app_err_path(@app, @problem), :method => :post, :class => "#{@app.issue_tracker.class::Label}_create create-issue"
17 - else 17 - else
18 - %span= link_to 'go to issue', @err.issue_link, :target => "_blank", :class => "#{@app.issue_tracker.class::Label}_goto goto-issue"  
19 - = link_to 'unlink issue', unlink_issue_app_err_path(@app, @err), :method => :delete, :confirm => "Unlink err issues?", :class => "unlink-issue"  
20 - - if @err.unresolved?  
21 - %span= link_to 'resolve', resolve_app_err_path(@app, @err), :method => :put, :confirm => err_confirm, :class => 'resolve' 18 + %span= link_to 'go to issue', @problem.issue_link, :class => "#{@app.issue_tracker.class::Label}_goto goto-issue"
  19 + = link_to 'unlink issue', unlink_issue_app_err_path(@app, @problem), :method => :delete, :confirm => "Unlink err issues?", :class => "unlink-issue"
  20 + - if @problem.unresolved?
  21 + %span= link_to 'resolve', resolve_app_err_path(@app, @problem), :method => :put, :confirm => err_confirm, :class => 'resolve'
22 22
23 -- if !@app.issue_tracker_configured? || @err.comments.any? 23 +- if !@app.issue_tracker_configured? || @problem.comments.any?
24 - content_for :comments do 24 - content_for :comments do
25 %h3 Comments on this Err 25 %h3 Comments on this Err
26 - - @err.comments.each do |comment| 26 + - @problem.comments.each do |comment|
27 .window 27 .window
28 %table.comment 28 %table.comment
29 %tr 29 %tr
30 %th 30 %th
31 - %span= link_to '&#10008;'.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" 31 + %span= link_to '&#10008;'.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"
32 = time_ago_in_words(comment.created_at, true) << " ago by " 32 = time_ago_in_words(comment.created_at, true) << " ago by "
33 = link_to comment.user.email, user_path(comment.user) 33 = link_to comment.user.email, user_path(comment.user)
34 %tr 34 %tr
35 %td= comment.body.gsub("\n", "<br>").html_safe 35 %td= comment.body.gsub("\n", "<br>").html_safe
36 - unless @app.issue_tracker_configured? 36 - unless @app.issue_tracker_configured?
37 - = form_for @comment, :url => create_comment_app_err_path(@app, @err) do |comment_form| 37 + = form_for @comment, :url => create_comment_app_err_path(@app, @problem) do |comment_form|
38 %p Add a comment 38 %p Add a comment
39 = comment_form.text_area :body, :style => "width: 420px; height: 80px;" 39 = comment_form.text_area :body, :style => "width: 420px; height: 80px;"
40 = comment_form.submit "Save Comment" 40 = comment_form.submit "Save Comment"
app/views/issue_trackers/fogbugz_body.txt.erb
1 -"See this exception on Errbit": <%= app_err_url(err.app, err) %>  
2 -<% if notice = err.notices.first %> 1 +"See this exception on Errbit": <%= app_err_url(problem.app, problem) %>
  2 +<% if notice = problem.notices.first %>
3 <%= notice.message %> 3 <%= notice.message %>
4 4
5 Summary 5 Summary
6 - Where 6 - Where
7 - <%= notice.err.where %> 7 + <%= notice.where %>
8 8
9 - Occured 9 - Occured
10 <%= notice.created_at.to_s(:micro) %> 10 <%= notice.created_at.to_s(:micro) %>
11 11
12 - Similar 12 - Similar
13 - <%= (notice.err.notices_count - 1).to_s %> 13 + <%= (notice.problem.notices_count - 1).to_s %>
14 14
15 Params 15 Params
16 <%= pretty_hash(notice.params) %> 16 <%= pretty_hash(notice.params) %>
app/views/issue_trackers/github_issues_body.txt.erb
@@ -7,13 +7,13 @@ @@ -7,13 +7,13 @@
7 [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" 7 [<%= notice.request['url'] %>](<%= notice.request['url'] %>)"
8 <% end %> 8 <% end %>
9 ### Where ### 9 ### Where ###
10 -<%= notice.err.where %> 10 +<%= notice.where %>
11 11
12 ### Occured ### 12 ### Occured ###
13 <%= notice.created_at.to_s(:micro) %> 13 <%= notice.created_at.to_s(:micro) %>
14 14
15 ### Similar ### 15 ### Similar ###
16 -<%= (notice.err.notices_count - 1).to_s %> 16 +<%= (notice.problem.notices_count - 1).to_s %>
17 17
18 ## Params ## 18 ## Params ##
19 ``` 19 ```
app/views/issue_trackers/lighthouseapp_body.txt.erb
1 -[See this exception on Errbit](<%= app_err_url err.app, err %> "See this exception on Errbit")  
2 -<% if notice = err.notices.first %> 1 +[See this exception on Errbit](<%= app_err_url problem.app, problem %> "See this exception on Errbit")
  2 +<% if notice = problem.notices.first %>
3 # <%= notice.message %> # 3 # <%= notice.message %> #
4 ## Summary ## 4 ## Summary ##
5 <% if notice.request['url'].present? %> 5 <% if notice.request['url'].present? %>
@@ -7,13 +7,13 @@ @@ -7,13 +7,13 @@
7 [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" 7 [<%= notice.request['url'] %>](<%= notice.request['url'] %>)"
8 <% end %> 8 <% end %>
9 ### Where ### 9 ### Where ###
10 - <%= notice.err.where %> 10 + <%= notice.where %>
11 11
12 ### Occured ### 12 ### Occured ###
13 <%= notice.created_at.to_s(:micro) %> 13 <%= notice.created_at.to_s(:micro) %>
14 14
15 ### Similar ### 15 ### Similar ###
16 - <%= (notice.err.notices_count - 1).to_s %> 16 + <%= (notice.problem.notices_count - 1).to_s %>
17 17
18 ## Params ## 18 ## Params ##
19 <code><%= pretty_hash(notice.params) %></code> 19 <code><%= pretty_hash(notice.params) %></code>
app/views/issue_trackers/pivotal_body.txt.erb
1 -See this exception on Errbit: <%= app_err_url err.app, err %>  
2 -<% if notice = err.notices.first %> 1 +See this exception on Errbit: <%= app_err_url problem.app, problem %>
  2 +<% if notice = problem.notices.first %>
3 <% if notice.request['url'].present? %>URL: <%= notice.request['url'] %><% end %> 3 <% if notice.request['url'].present? %>URL: <%= notice.request['url'] %><% end %>
4 - Where: <%= notice.err.where %> 4 + Where: <%= notice.where %>
5 Occurred: <%= notice.created_at.to_s :micro %> 5 Occurred: <%= notice.created_at.to_s :micro %>
6 - Similar: <%= (notice.err.notices.count - 1).to_s %> 6 + Similar: <%= (notice.problem.notices.count - 1).to_s %>
7 7
8 Params: 8 Params:
9 <%= pretty_hash notice.params %> 9 <%= pretty_hash notice.params %>
app/views/issue_trackers/textile_body.txt.erb
1 -<% if notice = err.notices.first %> 1 +<% if notice = problem.notices.first %>
2 h1. <%= notice.message %> 2 h1. <%= notice.message %>
3 3
4 -h3. "See this exception on Errbit":<%= app_err_url err.app, err %> 4 +h3. "See this exception on Errbit":<%= app_err_url problem.app, problem %>
5 5
6 h2. Summary 6 h2. Summary
7 <% if notice.request['url'].present? %> 7 <% if notice.request['url'].present? %>
@@ -11,7 +11,7 @@ h3. URL @@ -11,7 +11,7 @@ h3. URL
11 <% end %> 11 <% end %>
12 h3. Where 12 h3. Where
13 13
14 -<%= notice.err.where %> 14 +<%= notice.where %>
15 15
16 h3. Occurred 16 h3. Occurred
17 17
@@ -19,7 +19,7 @@ h3. Occurred @@ -19,7 +19,7 @@ h3. Occurred
19 19
20 h3. Similar 20 h3. Similar
21 21
22 -<%= (notice.err.notices_count - 1).to_s %> 22 +<%= (notice.problem.notices_count - 1).to_s %>
23 23
24 h2. Params 24 h2. Params
25 25
app/views/mailer/err_notification.html.haml
@@ -9,12 +9,12 @@ @@ -9,12 +9,12 @@
9 An err has just occurred in 9 An err has just occurred in
10 = link_to(@app.name, app_url(@app), :class => "bold") << "," 10 = link_to(@app.name, app_url(@app), :class => "bold") << ","
11 on the 11 on the
12 - %span.bold= @notice.err.environment 12 + %span.bold= @notice.environment_name
13 environment. 13 environment.
14 %br 14 %br
15 - This err has occurred #{pluralize @notice.err.notices_count, 'time'}. 15 + This err has occurred #{pluralize @notice.problem.notices_count, 'time'}.
16 %p 16 %p
17 - = link_to("Click here to view the error on Errbit", app_err_url(@app, @notice.err), :class => "bold") << "." 17 + = link_to("Click here to view the error on Errbit", app_err_url(@app, @notice.problem), :class => "bold") << "."
18 %tr 18 %tr
19 %td.section 19 %td.section
20 %table(cellpadding="0" cellspacing="0" border="0" align="left") 20 %table(cellpadding="0" cellspacing="0" border="0" align="left")
@@ -23,10 +23,10 @@ @@ -23,10 +23,10 @@
23 %td.content(valign="top") 23 %td.content(valign="top")
24 %div 24 %div
25 %p.heading ERROR MESSAGE: 25 %p.heading ERROR MESSAGE:
26 - %p= @notice.err.message 26 + %p= @notice.message
27 %p.heading WHERE: 27 %p.heading WHERE:
28 %p.monospace 28 %p.monospace
29 - = @notice.err.where 29 + = @notice.where
30 - if (app_backtrace = @notice.app_backtrace).any? 30 - if (app_backtrace = @notice.app_backtrace).any?
31 - app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| 31 - app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line|
32 %p.backtrace= line 32 %p.backtrace= line
app/views/mailer/err_notification.text.erb
1 -An err has just occurred in <%= @app.name %>, on the <%= @notice.err.environment %> environment. 1 +An err has just occurred in <%= @notice.environment_name %>: <%= raw(@notice.message) %>
2 2
3 -This err has occurred <%= pluralize @notice.err.notices_count, 'time' %>. 3 +This err has occurred <%= pluralize @notice.problem.notices_count, 'time' %>. You should really look into it here:
4 4
5 -You can view it on Errbit here: <%= app_err_url(@app, @notice.err) %> 5 + <%= app_err_url(@app, @notice.problem) %>
6 6
7 7
8 ERROR MESSAGE: 8 ERROR MESSAGE:
9 9
10 -<%= raw(@notice.err.message) %> 10 +<%= raw(@notice.message) %>
11 11
12 12
13 WHERE: 13 WHERE:
14 14
15 -<%= @notice.err.where %> 15 +<%= @notice.where %>
16 16
17 <% @notice.app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| %> 17 <% @notice.app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| %>
18 <%= line %> 18 <%= line %>
app/views/notices/_atom_entry.html.haml
@@ -6,13 +6,13 @@ @@ -6,13 +6,13 @@
6 = link_to(notice.request['url'], notice.request['url']) 6 = link_to(notice.request['url'], notice.request['url'])
7 %p 7 %p
8 %strong Where: 8 %strong Where:
9 - = notice.err.where 9 + = notice.where
10 %p 10 %p
11 %strong Occured: 11 %strong Occured:
12 = notice.created_at.to_s(:micro) 12 = notice.created_at.to_s(:micro)
13 %p 13 %p
14 %strong Similar: 14 %strong Similar:
15 - = notice.err.notices_count - 1 15 + = notice.problem.notices_count - 1
16 16
17 %h3 Params 17 %h3 Params
18 %p= pretty_hash(notice.params) 18 %p= pretty_hash(notice.params)
app/views/notices/_summary.html.haml
@@ -9,13 +9,13 @@ @@ -9,13 +9,13 @@
9 %td.nowrap= link_to notice.request['url'], notice.request['url'] 9 %td.nowrap= link_to notice.request['url'], notice.request['url']
10 %tr 10 %tr
11 %th Where 11 %th Where
12 - %td= notice.err.where 12 + %td= notice.where
13 %tr 13 %tr
14 %th Occurred 14 %th Occurred
15 %td= notice.created_at.to_s(:micro) 15 %td= notice.created_at.to_s(:micro)
16 %tr 16 %tr
17 %th Similar 17 %th Similar
18 - %td= notice.err.notices.count - 1 18 + %td= notice.problem.notices.count - 1
19 %tr 19 %tr
20 %th Browser 20 %th Browser
21 - %td= user_agent_graph(notice.err) 21 + %td= user_agent_graph(notice.problem)
db/migrate/20110422152027_move_notices_to_separate_collection.rb
@@ -17,10 +17,9 @@ class MoveNoticesToSeparateCollection &lt; Mongoid::Migration @@ -17,10 +17,9 @@ class MoveNoticesToSeparateCollection &lt; Mongoid::Migration
17 mongo_db.collection("errs").update({ "_id" => err['_id']}, { "$unset" => { "notices" => 1}}) 17 mongo_db.collection("errs").update({ "_id" => err['_id']}, { "$unset" => { "notices" => 1}})
18 end 18 end
19 Rake::Task["errbit:db:update_notices_count"].invoke 19 Rake::Task["errbit:db:update_notices_count"].invoke
20 - Rake::Task["errbit:db:update_err_message"].invoke 20 + Rake::Task["errbit:db:update_problem_attrs"].invoke
21 end 21 end
22 - 22 +
23 def self.down 23 def self.down
24 end 24 end
25 -  
26 end 25 end
db/migrate/20110905134638_link_errs_to_problems.rb 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +class LinkErrsToProblems < Mongoid::Migration
  2 + def self.up
  3 +
  4 + # Copy err.klass to notice.klass
  5 + Notice.all.each do |notice|
  6 + if notice.err && (klass = notice.err['klass'])
  7 + notice.update_attribute(:klass, klass)
  8 + end
  9 + end
  10 +
  11 + # Create a Problem for each Err
  12 + Err.all.each do |err|
  13 + app_id = err['app_id']
  14 + app = app_id && App.find(app_id)
  15 + if app
  16 + err.problem = app.problems.create
  17 + err.save
  18 + end
  19 + end
  20 +
  21 + Rake::Task["errbit:db:update_notices_count"].invoke
  22 + Rake::Task["errbit:db:update_problem_attrs"].invoke
  23 + end
  24 +
  25 + def self.down
  26 + end
  27 +end
0 \ No newline at end of file 28 \ No newline at end of file
lib/tasks/errbit/database.rake
1 namespace :errbit do 1 namespace :errbit do
2 namespace :db do 2 namespace :db do
3 - desc "Updates Err#notices_count"  
4 - task :update_err_message => :environment do  
5 - puts "Updating err.message"  
6 - Err.all.each do |e|  
7 - e.update_attributes(:message => e.notices.first.message) if e.notices.first  
8 - end 3 +
  4 + desc "Updates cached attributes on Problem"
  5 + task :update_problem_attrs => :environment do
  6 + puts "Updating problems"
  7 + Problem.all.each(&:cache_notice_attributes)
9 end 8 end
10 -  
11 - desc "Updates Err#notices_count" 9 +
  10 + desc "Updates Problem#notices_count"
12 task :update_notices_count => :environment do 11 task :update_notices_count => :environment do
13 - puts "Updating err.notices_count"  
14 - Err.all.each do |e|  
15 - e.update_attributes(:notices_count => e.notices.count) 12 + puts "Updating problem.notices_count"
  13 + Problem.all.each do |p|
  14 + p.update_attributes(:notices_count => p.notices.count)
16 end 15 end
17 end 16 end
18 - 17 +
19 desc "Delete resolved errors from the database. (Useful for limited heroku databases)" 18 desc "Delete resolved errors from the database. (Useful for limited heroku databases)"
20 task :clear_resolved => :environment do 19 task :clear_resolved => :environment do
21 - count = Err.resolved.count  
22 - Err.resolved.each {|err| err.destroy } 20 + count = Problem.resolved.count
  21 + Problem.resolved.each {|problem| problem.destroy }
23 puts "=== Cleared #{count} resolved errors from the database." if count > 0 22 puts "=== Cleared #{count} resolved errors from the database." if count > 0
24 end 23 end
25 end 24 end
26 end 25 end
27 -  
lib/tasks/errbit/demo.rake
@@ -4,7 +4,7 @@ namespace :errbit do @@ -4,7 +4,7 @@ namespace :errbit do
4 require 'factory_girl_rails' 4 require 'factory_girl_rails'
5 Dir.glob(File.join(Rails.root,'spec/factories/*.rb')).each {|f| require f } 5 Dir.glob(File.join(Rails.root,'spec/factories/*.rb')).each {|f| require f }
6 app = Factory(:app, :name => "Demo App #{Time.now.strftime("%N")}") 6 app = Factory(:app, :name => "Demo App #{Time.now.strftime("%N")}")
7 - Factory(:notice, :err => Factory(:err, :app => app)) 7 + Factory(:notice, :err => Factory(:err, :problem => Factory(:problem, :app => app)))
8 puts "=== Created demo app: '#{app.name}', with an example error." 8 puts "=== Created demo app: '#{app.name}', with an example error."
9 end 9 end
10 end 10 end
public/javascripts/application.js
@@ -62,7 +62,7 @@ $(function() { @@ -62,7 +62,7 @@ $(function() {
62 function activateSelectableRows() { 62 function activateSelectableRows() {
63 $('.selectable tr').click(function(event) { 63 $('.selectable tr').click(function(event) {
64 if(!_.include(['A', 'INPUT', 'BUTTON', 'TEXTAREA'], event.target.nodeName)) { 64 if(!_.include(['A', 'INPUT', 'BUTTON', 'TEXTAREA'], event.target.nodeName)) {
65 - var checkbox = $(this).find('input[name="errs[]"]'); 65 + var checkbox = $(this).find('input[name="problems[]"]');
66 checkbox.attr('checked', !checkbox.is(':checked')); 66 checkbox.attr('checked', !checkbox.is(':checked'));
67 } 67 }
68 }) 68 })
spec/controllers/apps_controller_spec.rb
@@ -2,10 +2,11 @@ require &#39;spec_helper&#39; @@ -2,10 +2,11 @@ require &#39;spec_helper&#39;
2 2
3 describe AppsController do 3 describe AppsController do
4 render_views 4 render_views
5 - 5 +
6 it_requires_authentication 6 it_requires_authentication
7 it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete} 7 it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete}
8 - 8 +
  9 +
9 describe "GET /apps" do 10 describe "GET /apps" do
10 context 'when logged in as an admin' do 11 context 'when logged in as an admin' do
11 it 'finds all apps' do 12 it 'finds all apps' do
@@ -16,7 +17,7 @@ describe AppsController do @@ -16,7 +17,7 @@ describe AppsController do
16 assigns(:apps).should == apps 17 assigns(:apps).should == apps
17 end 18 end
18 end 19 end
19 - 20 +
20 context 'when logged in as a regular user' do 21 context 'when logged in as a regular user' do
21 it 'finds apps the user is watching' do 22 it 'finds apps the user is watching' do
22 sign_in(user = Factory(:user)) 23 sign_in(user = Factory(:user))
@@ -31,116 +32,116 @@ describe AppsController do @@ -31,116 +32,116 @@ describe AppsController do
31 end 32 end
32 end 33 end
33 end 34 end
34 - 35 +
35 describe "GET /apps/:id" do 36 describe "GET /apps/:id" do
36 context 'logged in as an admin' do 37 context 'logged in as an admin' do
37 before(:each) do 38 before(:each) do
38 @user = Factory(:admin) 39 @user = Factory(:admin)
39 sign_in @user 40 sign_in @user
40 @app = Factory(:app) 41 @app = Factory(:app)
41 - @err = Factory :err, :app => @app  
42 - @notice = Factory :notice, :err => @err 42 + @problem = Factory(:notice, :err => Factory(:err, :problem => Factory(:problem, :app => @app))).problem
43 end 43 end
44 - 44 +
45 it 'finds the app' do 45 it 'finds the app' do
46 get :show, :id => @app.id 46 get :show, :id => @app.id
47 assigns(:app).should == @app 47 assigns(:app).should == @app
48 end 48 end
49 - 49 +
50 it "should not raise errors for app with err without notices" do 50 it "should not raise errors for app with err without notices" do
51 - Factory :err, :app => @app 51 + Factory(:err, :problem => Factory(:problem, :app => @app))
52 lambda { get :show, :id => @app.id }.should_not raise_error 52 lambda { get :show, :id => @app.id }.should_not raise_error
53 end 53 end
54 - 54 +
55 it "should list atom feed successfully" do 55 it "should list atom feed successfully" do
56 get :show, :id => @app.id, :format => "atom" 56 get :show, :id => @app.id, :format => "atom"
57 response.should be_success 57 response.should be_success
58 - response.body.should match(@err.message) 58 + response.body.should match(@problem.message)
59 end 59 end
60 - 60 +
61 context "pagination" do 61 context "pagination" do
62 before(:each) do 62 before(:each) do
63 - 35.times { Factory :err, :app => @app } 63 + 35.times { Factory(:err, :problem => Factory(:problem, :app => @app)) }
64 end 64 end
65 - 65 +
66 it "should have default per_page value for user" do 66 it "should have default per_page value for user" do
67 get :show, :id => @app.id 67 get :show, :id => @app.id
68 - assigns(:errs).size.should == User::PER_PAGE 68 + assigns(:problems).size.should == User::PER_PAGE
69 end 69 end
70 - 70 +
71 it "should be able to override default per_page value" do 71 it "should be able to override default per_page value" do
72 @user.update_attribute :per_page, 10 72 @user.update_attribute :per_page, 10
73 get :show, :id => @app.id 73 get :show, :id => @app.id
74 - assigns(:errs).size.should == 10 74 + assigns(:problems).size.should == 10
75 end 75 end
76 end 76 end
77 - 77 +
78 context 'with resolved errors' do 78 context 'with resolved errors' do
79 before(:each) do 79 before(:each) do
80 - resolved_err = Factory.create(:err, :app => @app, :resolved => true)  
81 - Factory.create(:notice, :err => resolved_err) 80 + resolved_problem = Factory(:problem, :app => @app)
  81 + Factory(:notice, :err => Factory(:err, :problem => resolved_problem))
  82 + resolved_problem.resolve!
82 end 83 end
83 - 84 +
84 context 'and no params' do 85 context 'and no params' do
85 - it 'shows only unresolved errs' do 86 + it 'shows only unresolved problems' do
86 get :show, :id => @app.id 87 get :show, :id => @app.id
87 - assigns(:errs).size.should == 1 88 + assigns(:problems).size.should == 1
88 end 89 end
89 end 90 end
90 -  
91 - context 'and all_errs=true params' do 91 +
  92 + context 'and all_problems=true params' do
92 it 'shows all errors' do 93 it 'shows all errors' do
93 get :show, :id => @app.id, :all_errs => true 94 get :show, :id => @app.id, :all_errs => true
94 - assigns(:errs).size.should == 2 95 + assigns(:problems).size.should == 2
95 end 96 end
96 end 97 end
97 end 98 end
98 - 99 +
99 context 'with environment filters' do 100 context 'with environment filters' do
100 before(:each) do 101 before(:each) do
101 environments = ['production', 'test', 'development', 'staging'] 102 environments = ['production', 'test', 'development', 'staging']
102 20.times do |i| 103 20.times do |i|
103 - Factory.create(:err, :app => @app, :environment => environments[i % environments.length]) 104 + Factory.create(:problem, :app => @app, :environment => environments[i % environments.length])
104 end 105 end
105 end 106 end
106 - 107 +
107 context 'no params' do 108 context 'no params' do
108 it 'shows errs for all environments' do 109 it 'shows errs for all environments' do
109 get :show, :id => @app.id 110 get :show, :id => @app.id
110 - assigns(:errs).size.should == 21 111 + assigns(:problems).size.should == 21
111 end 112 end
112 end 113 end
113 - 114 +
114 context 'environment production' do 115 context 'environment production' do
115 it 'shows errs for just production' do 116 it 'shows errs for just production' do
116 - get :show, :id => @app.id, :environment => :production  
117 - assigns(:errs).size.should == 6 117 + get :show, :id => @app.id, :environment => 'production'
  118 + assigns(:problems).size.should == 6
118 end 119 end
119 end 120 end
120 - 121 +
121 context 'environment staging' do 122 context 'environment staging' do
122 it 'shows errs for just staging' do 123 it 'shows errs for just staging' do
123 - get :show, :id => @app.id, :environment => :staging  
124 - assigns(:errs).size.should == 5 124 + get :show, :id => @app.id, :environment => 'staging'
  125 + assigns(:problems).size.should == 5
125 end 126 end
126 end 127 end
127 - 128 +
128 context 'environment development' do 129 context 'environment development' do
129 it 'shows errs for just development' do 130 it 'shows errs for just development' do
130 - get :show, :id => @app.id, :environment => :development  
131 - assigns(:errs).size.should == 5 131 + get :show, :id => @app.id, :environment => 'development'
  132 + assigns(:problems).size.should == 5
132 end 133 end
133 end 134 end
134 - 135 +
135 context 'environment test' do 136 context 'environment test' do
136 it 'shows errs for just test' do 137 it 'shows errs for just test' do
137 - get :show, :id => @app.id, :environment => :test  
138 - assigns(:errs).size.should == 5 138 + get :show, :id => @app.id, :environment => 'test'
  139 + assigns(:problems).size.should == 5
139 end 140 end
140 end 141 end
141 end 142 end
142 end 143 end
143 - 144 +
144 context 'logged in as a user' do 145 context 'logged in as a user' do
145 it 'finds the app if the user is watching it' do 146 it 'finds the app if the user is watching it' do
146 user = Factory(:user) 147 user = Factory(:user)
@@ -150,7 +151,7 @@ describe AppsController do @@ -150,7 +151,7 @@ describe AppsController do
150 get :show, :id => app.id 151 get :show, :id => app.id
151 assigns(:app).should == app 152 assigns(:app).should == app
152 end 153 end
153 - 154 +
154 it 'does not find the app if the user is not watching it' do 155 it 'does not find the app if the user is not watching it' do
155 sign_in Factory(:user) 156 sign_in Factory(:user)
156 app = Factory(:app) 157 app = Factory(:app)
@@ -160,12 +161,12 @@ describe AppsController do @@ -160,12 +161,12 @@ describe AppsController do
160 end 161 end
161 end 162 end
162 end 163 end
163 - 164 +
164 context 'logged in as an admin' do 165 context 'logged in as an admin' do
165 before do 166 before do
166 sign_in Factory(:admin) 167 sign_in Factory(:admin)
167 end 168 end
168 - 169 +
169 describe "GET /apps/new" do 170 describe "GET /apps/new" do
170 it 'instantiates a new app with a prebuilt watcher' do 171 it 'instantiates a new app with a prebuilt watcher' do
171 get :new 172 get :new
@@ -184,7 +185,7 @@ describe AppsController do @@ -184,7 +185,7 @@ describe AppsController do
184 assigns(:app).github_url.should == "github.com/test/example" 185 assigns(:app).github_url.should == "github.com/test/example"
185 end 186 end
186 end 187 end
187 - 188 +
188 describe "GET /apps/:id/edit" do 189 describe "GET /apps/:id/edit" do
189 it 'finds the correct app' do 190 it 'finds the correct app' do
190 app = Factory(:app) 191 app = Factory(:app)
@@ -192,47 +193,47 @@ describe AppsController do @@ -192,47 +193,47 @@ describe AppsController do
192 assigns(:app).should == app 193 assigns(:app).should == app
193 end 194 end
194 end 195 end
195 - 196 +
196 describe "POST /apps" do 197 describe "POST /apps" do
197 before do 198 before do
198 @app = Factory(:app) 199 @app = Factory(:app)
199 App.stub(:new).and_return(@app) 200 App.stub(:new).and_return(@app)
200 end 201 end
201 - 202 +
202 context "when the create is successful" do 203 context "when the create is successful" do
203 before do 204 before do
204 @app.should_receive(:save).and_return(true) 205 @app.should_receive(:save).and_return(true)
205 end 206 end
206 - 207 +
207 it "should redirect to the app page" do 208 it "should redirect to the app page" do
208 post :create, :app => {} 209 post :create, :app => {}
209 response.should redirect_to(app_path(@app)) 210 response.should redirect_to(app_path(@app))
210 end 211 end
211 - 212 +
212 it "should display a message" do 213 it "should display a message" do
213 post :create, :app => {} 214 post :create, :app => {}
214 request.flash[:success].should match(/success/) 215 request.flash[:success].should match(/success/)
215 end 216 end
216 end 217 end
217 end 218 end
218 - 219 +
219 describe "PUT /apps/:id" do 220 describe "PUT /apps/:id" do
220 before do 221 before do
221 @app = Factory(:app) 222 @app = Factory(:app)
222 end 223 end
223 - 224 +
224 context "when the update is successful" do 225 context "when the update is successful" do
225 it "should redirect to the app page" do 226 it "should redirect to the app page" do
226 put :update, :id => @app.id, :app => {} 227 put :update, :id => @app.id, :app => {}
227 response.should redirect_to(app_path(@app)) 228 response.should redirect_to(app_path(@app))
228 end 229 end
229 - 230 +
230 it "should display a message" do 231 it "should display a message" do
231 put :update, :id => @app.id, :app => {} 232 put :update, :id => @app.id, :app => {}
232 request.flash[:success].should match(/success/) 233 request.flash[:success].should match(/success/)
233 end 234 end
234 end 235 end
235 - 236 +
236 context "changing name" do 237 context "changing name" do
237 it "should redirect to app page" do 238 it "should redirect to app page" do
238 id = @app.id 239 id = @app.id
@@ -240,14 +241,14 @@ describe AppsController do @@ -240,14 +241,14 @@ describe AppsController do
240 response.should redirect_to(app_path(id)) 241 response.should redirect_to(app_path(id))
241 end 242 end
242 end 243 end
243 - 244 +
244 context "when the update is unsuccessful" do 245 context "when the update is unsuccessful" do
245 it "should render the edit page" do 246 it "should render the edit page" do
246 put :update, :id => @app.id, :app => { :name => '' } 247 put :update, :id => @app.id, :app => { :name => '' }
247 response.should render_template(:edit) 248 response.should render_template(:edit)
248 end 249 end
249 end 250 end
250 - 251 +
251 context "changing email_at_notices" do 252 context "changing email_at_notices" do
252 it "should parse legal csv values" do 253 it "should parse legal csv values" do
253 put :update, :id => @app.id, :app => { :email_at_notices => '1, 4, 7,8, 10' } 254 put :update, :id => @app.id, :app => { :email_at_notices => '1, 4, 7,8, 10' }
@@ -261,14 +262,14 @@ describe AppsController do @@ -261,14 +262,14 @@ describe AppsController do
261 @app.reload 262 @app.reload
262 @app.email_at_notices.should == Errbit::Config.email_at_notices 263 @app.email_at_notices.should == Errbit::Config.email_at_notices
263 end 264 end
264 - 265 +
265 it "should display a message" do 266 it "should display a message" do
266 put :update, :id => @app.id, :app => { :email_at_notices => 'qwertyuiop' } 267 put :update, :id => @app.id, :app => { :email_at_notices => 'qwertyuiop' }
267 request.flash[:error].should match(/Couldn't parse/) 268 request.flash[:error].should match(/Couldn't parse/)
268 end 269 end
269 end 270 end
270 end 271 end
271 - 272 +
272 context "setting up issue tracker", :cur => true do 273 context "setting up issue tracker", :cur => true do
273 context "unknown tracker type" do 274 context "unknown tracker type" do
274 before(:each) do 275 before(:each) do
@@ -277,12 +278,12 @@ describe AppsController do @@ -277,12 +278,12 @@ describe AppsController do
277 } } 278 } }
278 @app.reload 279 @app.reload
279 end 280 end
280 - 281 +
281 it "should not create issue tracker" do 282 it "should not create issue tracker" do
282 @app.issue_tracker_configured?.should == false 283 @app.issue_tracker_configured?.should == false
283 end 284 end
284 end 285 end
285 - 286 +
286 IssueTracker.subclasses.each do |tracker_klass| 287 IssueTracker.subclasses.each do |tracker_klass|
287 context tracker_klass do 288 context tracker_klass do
288 it "should save tracker params" do 289 it "should save tracker params" do
@@ -290,7 +291,7 @@ describe AppsController do @@ -290,7 +291,7 @@ describe AppsController do
290 params['ticket_properties'] = "card_type = defect" if tracker_klass == MingleTracker 291 params['ticket_properties'] = "card_type = defect" if tracker_klass == MingleTracker
291 params['type'] = tracker_klass.to_s 292 params['type'] = tracker_klass.to_s
292 put :update, :id => @app.id, :app => {:issue_tracker_attributes => params} 293 put :update, :id => @app.id, :app => {:issue_tracker_attributes => params}
293 - 294 +
294 @app.reload 295 @app.reload
295 tracker = @app.issue_tracker 296 tracker = @app.issue_tracker
296 tracker.should be_a(tracker_klass) 297 tracker.should be_a(tracker_klass)
@@ -301,13 +302,13 @@ describe AppsController do @@ -301,13 +302,13 @@ describe AppsController do
301 end 302 end
302 end 303 end
303 end 304 end
304 - 305 +
305 it "should show validation notice when sufficient params are not present" do 306 it "should show validation notice when sufficient params are not present" do
306 # Leave out one required param 307 # Leave out one required param
307 params = tracker_klass::Fields[1..-1].inject({}){|hash,f| hash[f[0]] = "test_value"; hash } 308 params = tracker_klass::Fields[1..-1].inject({}){|hash,f| hash[f[0]] = "test_value"; hash }
308 params['type'] = tracker_klass.to_s 309 params['type'] = tracker_klass.to_s
309 put :update, :id => @app.id, :app => {:issue_tracker_attributes => params} 310 put :update, :id => @app.id, :app => {:issue_tracker_attributes => params}
310 - 311 +
311 @app.reload 312 @app.reload
312 @app.issue_tracker_configured?.should == false 313 @app.issue_tracker_configured?.should == false
313 response.body.should match(/You must specify your/) 314 response.body.should match(/You must specify your/)
@@ -316,34 +317,34 @@ describe AppsController do @@ -316,34 +317,34 @@ describe AppsController do
316 end 317 end
317 end 318 end
318 end 319 end
319 - 320 +
320 describe "DELETE /apps/:id" do 321 describe "DELETE /apps/:id" do
321 before do 322 before do
322 @app = Factory(:app) 323 @app = Factory(:app)
323 App.stub(:find).with(@app.id).and_return(@app) 324 App.stub(:find).with(@app.id).and_return(@app)
324 end 325 end
325 - 326 +
326 it "should find the app" do 327 it "should find the app" do
327 delete :destroy, :id => @app.id 328 delete :destroy, :id => @app.id
328 assigns(:app).should == @app 329 assigns(:app).should == @app
329 end 330 end
330 - 331 +
331 it "should destroy the app" do 332 it "should destroy the app" do
332 @app.should_receive(:destroy) 333 @app.should_receive(:destroy)
333 delete :destroy, :id => @app.id 334 delete :destroy, :id => @app.id
334 end 335 end
335 - 336 +
336 it "should display a message" do 337 it "should display a message" do
337 delete :destroy, :id => @app.id 338 delete :destroy, :id => @app.id
338 request.flash[:success].should match(/success/) 339 request.flash[:success].should match(/success/)
339 end 340 end
340 - 341 +
341 it "should redirect to the apps page" do 342 it "should redirect to the apps page" do
342 delete :destroy, :id => @app.id 343 delete :destroy, :id => @app.id
343 response.should redirect_to(apps_path) 344 response.should redirect_to(apps_path)
344 end 345 end
345 end 346 end
346 end 347 end
347 - 348 +
  349 +
348 end 350 end
349 -  
spec/controllers/errs_controller_spec.rb
@@ -6,443 +6,443 @@ describe ErrsController do @@ -6,443 +6,443 @@ describe ErrsController do
6 :index => :get, :all => :get, :show => :get, :resolve => :put 6 :index => :get, :all => :get, :show => :get, :resolve => :put
7 }, 7 },
8 :params => {:app_id => 'dummyid', :id => 'dummyid'} 8 :params => {:app_id => 'dummyid', :id => 'dummyid'}
9 - 9 +
10 let(:app) { Factory(:app) } 10 let(:app) { Factory(:app) }
11 - let(:err) { Factory(:err, :app => app) }  
12 - 11 + let(:err) { Factory(:err, :problem => Factory(:problem, :app => app, :environment => "production")) }
  12 +
  13 +
13 describe "GET /errs" do 14 describe "GET /errs" do
14 render_views 15 render_views
15 context 'when logged in as an admin' do 16 context 'when logged in as an admin' do
16 before(:each) do 17 before(:each) do
17 @user = Factory(:admin) 18 @user = Factory(:admin)
18 sign_in @user 19 sign_in @user
19 - @notice = Factory :notice  
20 - @err = @notice.err 20 + @problem = Factory(:notice, :err => Factory(:err, :problem => Factory(:problem, :app => app, :environment => "production"))).problem
21 end 21 end
22 - 22 +
23 it "should successfully list errs" do 23 it "should successfully list errs" do
24 get :index 24 get :index
25 response.should be_success 25 response.should be_success
26 - response.body.should match(@err.message) 26 + response.body.should match(@problem.message)
27 end 27 end
28 - 28 +
29 it "should list atom feed successfully" do 29 it "should list atom feed successfully" do
30 get :index, :format => "atom" 30 get :index, :format => "atom"
31 response.should be_success 31 response.should be_success
32 - response.body.should match(@err.message) 32 + response.body.should match(@problem.message)
33 end 33 end
34 - 34 +
35 context "pagination" do 35 context "pagination" do
36 before(:each) do 36 before(:each) do
37 35.times { Factory :err } 37 35.times { Factory :err }
38 end 38 end
39 - 39 +
40 it "should have default per_page value for user" do 40 it "should have default per_page value for user" do
41 get :index 41 get :index
42 - assigns(:errs).size.should == User::PER_PAGE 42 + assigns(:problems).size.should == User::PER_PAGE
43 end 43 end
44 - 44 +
45 it "should be able to override default per_page value" do 45 it "should be able to override default per_page value" do
46 @user.update_attribute :per_page, 10 46 @user.update_attribute :per_page, 10
47 get :index 47 get :index
48 - assigns(:errs).size.should == 10 48 + assigns(:problems).size.should == 10
49 end 49 end
50 end 50 end
51 - 51 +
52 context 'with environment filters' do 52 context 'with environment filters' do
53 before(:each) do 53 before(:each) do
54 environments = ['production', 'test', 'development', 'staging'] 54 environments = ['production', 'test', 'development', 'staging']
55 20.times do |i| 55 20.times do |i|
56 - Factory.create(:err, :environment => environments[i % environments.length]) 56 + Factory(:problem, :environment => environments[i % environments.length])
57 end 57 end
58 end 58 end
59 - 59 +
60 context 'no params' do 60 context 'no params' do
61 it 'shows errs for all environments' do 61 it 'shows errs for all environments' do
62 get :index 62 get :index
63 - assigns(:errs).size.should == 21 63 + assigns(:problems).size.should == 21
64 end 64 end
65 end 65 end
66 - 66 +
67 context 'environment production' do 67 context 'environment production' do
68 it 'shows errs for just production' do 68 it 'shows errs for just production' do
69 - get :index, :environment => :production  
70 - assigns(:errs).size.should == 6 69 + get :index, :environment => 'production'
  70 + assigns(:problems).size.should == 6
71 end 71 end
72 end 72 end
73 - 73 +
74 context 'environment staging' do 74 context 'environment staging' do
75 it 'shows errs for just staging' do 75 it 'shows errs for just staging' do
76 - get :index, :environment => :staging  
77 - assigns(:errs).size.should == 5 76 + get :index, :environment => 'staging'
  77 + assigns(:problems).size.should == 5
78 end 78 end
79 end 79 end
80 - 80 +
81 context 'environment development' do 81 context 'environment development' do
82 it 'shows errs for just development' do 82 it 'shows errs for just development' do
83 - get :index, :environment => :development  
84 - assigns(:errs).size.should == 5 83 + get :index, :environment => 'development'
  84 + assigns(:problems).size.should == 5
85 end 85 end
86 end 86 end
87 - 87 +
88 context 'environment test' do 88 context 'environment test' do
89 it 'shows errs for just test' do 89 it 'shows errs for just test' do
90 - get :index, :environment => :test  
91 - assigns(:errs).size.should == 5 90 + get :index, :environment => 'test'
  91 + assigns(:problems).size.should == 5
92 end 92 end
93 end 93 end
94 end 94 end
95 end 95 end
96 - 96 +
97 context 'when logged in as a user' do 97 context 'when logged in as a user' do
98 it 'gets a paginated list of unresolved errs for the users apps' do 98 it 'gets a paginated list of unresolved errs for the users apps' do
99 sign_in(user = Factory(:user)) 99 sign_in(user = Factory(:user))
100 unwatched_err = Factory(:err) 100 unwatched_err = Factory(:err)
101 - watched_unresolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => false)  
102 - watched_resolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => true) 101 + watched_unresolved_err = Factory(:err, :problem => Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => false))
  102 + watched_resolved_err = Factory(:err, :problem => Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => true))
103 get :index 103 get :index
104 - assigns(:errs).should include(watched_unresolved_err)  
105 - assigns(:errs).should_not include(unwatched_err, watched_resolved_err) 104 + assigns(:problems).should include(watched_unresolved_err.problem)
  105 + assigns(:problems).should_not include(unwatched_err.problem, watched_resolved_err.problem)
106 end 106 end
107 end 107 end
108 end 108 end
109 - 109 +
110 describe "GET /errs/all" do 110 describe "GET /errs/all" do
111 context 'when logged in as an admin' do 111 context 'when logged in as an admin' do
112 it "gets a paginated list of all errs" do 112 it "gets a paginated list of all errs" do
113 sign_in Factory(:admin) 113 sign_in Factory(:admin)
114 errs = WillPaginate::Collection.new(1,30) 114 errs = WillPaginate::Collection.new(1,30)
115 - 3.times { errs << Factory(:err) }  
116 - 3.times { errs << Factory(:err, :resolved => true)}  
117 - Err.should_receive(:ordered).and_return( 115 + 3.times { errs << Factory(:err).problem }
  116 + 3.times { errs << Factory(:err, :problem => Factory(:problem, :resolved => true)).problem }
  117 + Problem.should_receive(:ordered).and_return(
118 mock('proxy', :paginate => errs) 118 mock('proxy', :paginate => errs)
119 ) 119 )
120 get :all 120 get :all
121 - assigns(:errs).should == errs 121 + assigns(:problems).should == errs
122 end 122 end
123 end 123 end
124 - 124 +
125 context 'when logged in as a user' do 125 context 'when logged in as a user' do
126 it 'gets a paginated list of all errs for the users apps' do 126 it 'gets a paginated list of all errs for the users apps' do
127 sign_in(user = Factory(:user)) 127 sign_in(user = Factory(:user))
128 - unwatched_err = Factory(:err)  
129 - watched_unresolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => false)  
130 - watched_resolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => true) 128 + unwatched_err = Factory(:problem)
  129 + watched_unresolved_err = Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => false)
  130 + watched_resolved_err = Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => true)
131 get :all 131 get :all
132 - assigns(:errs).should include(watched_resolved_err, watched_unresolved_err)  
133 - assigns(:errs).should_not include(unwatched_err) 132 + assigns(:problems).should include(watched_resolved_err, watched_unresolved_err)
  133 + assigns(:problems).should_not include(unwatched_err)
134 end 134 end
135 end 135 end
136 end 136 end
137 - 137 +
138 describe "GET /apps/:app_id/errs/:id" do 138 describe "GET /apps/:app_id/errs/:id" do
139 render_views 139 render_views
140 - 140 +
141 before do 141 before do
142 3.times { Factory(:notice, :err => err)} 142 3.times { Factory(:notice, :err => err)}
143 end 143 end
144 - 144 +
145 context 'when logged in as an admin' do 145 context 'when logged in as an admin' do
146 before do 146 before do
147 sign_in Factory(:admin) 147 sign_in Factory(:admin)
148 end 148 end
149 - 149 +
150 it "finds the app" do 150 it "finds the app" do
151 - get :show, :app_id => app.id, :id => err.id 151 + get :show, :app_id => app.id, :id => err.problem.id
152 assigns(:app).should == app 152 assigns(:app).should == app
153 end 153 end
154 - 154 +
155 it "finds the err" do 155 it "finds the err" do
156 - get :show, :app_id => app.id, :id => err.id  
157 - assigns(:err).should == err 156 + get :show, :app_id => app.id, :id => err.problem.id
  157 + assigns(:problem).should == err.problem
158 end 158 end
159 - 159 +
160 it "successfully render page" do 160 it "successfully render page" do
161 - get :show, :app_id => app.id, :id => err.id 161 + get :show, :app_id => app.id, :id => err.problem.id
162 response.should be_success 162 response.should be_success
163 end 163 end
164 - 164 +
165 context "create issue button" do 165 context "create issue button" do
166 let(:button_matcher) { match(/create issue/) } 166 let(:button_matcher) { match(/create issue/) }
167 - 167 +
168 it "should not exist for err's app without issue tracker" do 168 it "should not exist for err's app without issue tracker" do
169 err = Factory :err 169 err = Factory :err
170 - get :show, :app_id => err.app.id, :id => err.id  
171 - 170 + get :show, :app_id => err.app.id, :id => err.problem.id
  171 +
172 response.body.should_not button_matcher 172 response.body.should_not button_matcher
173 end 173 end
174 - 174 +
175 it "should exist for err's app with issue tracker" do 175 it "should exist for err's app with issue tracker" do
176 tracker = Factory(:lighthouse_tracker) 176 tracker = Factory(:lighthouse_tracker)
177 - err = Factory(:err, :app => tracker.app)  
178 - get :show, :app_id => err.app.id, :id => err.id  
179 - 177 + err = Factory(:err, :problem => Factory(:problem, :app => tracker.app))
  178 + get :show, :app_id => err.app.id, :id => err.problem.id
  179 +
180 response.body.should button_matcher 180 response.body.should button_matcher
181 end 181 end
182 - 182 +
183 it "should not exist for err with issue_link" do 183 it "should not exist for err with issue_link" do
184 tracker = Factory(:lighthouse_tracker) 184 tracker = Factory(:lighthouse_tracker)
185 - err = Factory(:err, :app => tracker.app, :issue_link => "http://some.host")  
186 - get :show, :app_id => err.app.id, :id => err.id  
187 - 185 + err = Factory(:err, :problem => Factory(:problem, :app => tracker.app, :issue_link => "http://some.host"))
  186 + get :show, :app_id => err.app.id, :id => err.problem.id
  187 +
188 response.body.should_not button_matcher 188 response.body.should_not button_matcher
189 end 189 end
190 end 190 end
191 end 191 end
192 - 192 +
193 context 'when logged in as a user' do 193 context 'when logged in as a user' do
194 before do 194 before do
195 sign_in(@user = Factory(:user)) 195 sign_in(@user = Factory(:user))
196 @unwatched_err = Factory(:err) 196 @unwatched_err = Factory(:err)
197 @watched_app = Factory(:app) 197 @watched_app = Factory(:app)
198 @watcher = Factory(:user_watcher, :user => @user, :app => @watched_app) 198 @watcher = Factory(:user_watcher, :user => @user, :app => @watched_app)
199 - @watched_err = Factory(:err, :app => @watched_app) 199 + @watched_err = Factory(:err, :problem => Factory(:problem, :app => @watched_app))
200 end 200 end
201 - 201 +
202 it 'finds the err if the user is watching the app' do 202 it 'finds the err if the user is watching the app' do
203 - get :show, :app_id => @watched_app.to_param, :id => @watched_err.id  
204 - assigns(:err).should == @watched_err 203 + get :show, :app_id => @watched_app.to_param, :id => @watched_err.problem.id
  204 + assigns(:problem).should == @watched_err.problem
205 end 205 end
206 - 206 +
207 it 'raises a DocumentNotFound error if the user is not watching the app' do 207 it 'raises a DocumentNotFound error if the user is not watching the app' do
208 lambda { 208 lambda {
209 - get :show, :app_id => @unwatched_err.app_id, :id => @unwatched_err.id 209 + get :show, :app_id => @unwatched_err.problem.app_id, :id => @unwatched_err.problem.id
210 }.should raise_error(Mongoid::Errors::DocumentNotFound) 210 }.should raise_error(Mongoid::Errors::DocumentNotFound)
211 end 211 end
212 end 212 end
213 end 213 end
214 - 214 +
215 describe "PUT /apps/:app_id/errs/:id/resolve" do 215 describe "PUT /apps/:app_id/errs/:id/resolve" do
216 before do 216 before do
217 sign_in Factory(:admin) 217 sign_in Factory(:admin)
218 -  
219 - @err = Factory(:err)  
220 - App.stub(:find).with(@err.app.id).and_return(@err.app)  
221 - @err.app.errs.stub(:find).and_return(@err)  
222 - @err.stub(:resolve!) 218 +
  219 + @problem = Factory(:err)
  220 + App.stub(:find).with(@problem.app.id).and_return(@problem.app)
  221 + @problem.app.problems.stub(:find).and_return(@problem.problem)
  222 + @problem.problem.stub(:resolve!)
223 end 223 end
224 - 224 +
225 it 'finds the app and the err' do 225 it 'finds the app and the err' do
226 - App.should_receive(:find).with(@err.app.id).and_return(@err.app)  
227 - @err.app.errs.should_receive(:find).and_return(@err)  
228 - put :resolve, :app_id => @err.app.id, :id => @err.id  
229 - assigns(:app).should == @err.app  
230 - assigns(:err).should == @err 226 + App.should_receive(:find).with(@problem.app.id).and_return(@problem.app)
  227 + @problem.app.problems.should_receive(:find).and_return(@problem.problem)
  228 + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
  229 + assigns(:app).should == @problem.app
  230 + assigns(:problem).should == @problem.problem
231 end 231 end
232 - 232 +
233 it "should resolve the issue" do 233 it "should resolve the issue" do
234 - @err.should_receive(:resolve!).and_return(true)  
235 - put :resolve, :app_id => @err.app.id, :id => @err.id 234 + @problem.problem.should_receive(:resolve!).and_return(true)
  235 + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
236 end 236 end
237 - 237 +
238 it "should display a message" do 238 it "should display a message" do
239 - put :resolve, :app_id => @err.app.id, :id => @err.id 239 + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
240 request.flash[:success].should match(/Great news/) 240 request.flash[:success].should match(/Great news/)
241 end 241 end
242 - 242 +
243 it "should redirect to the app page" do 243 it "should redirect to the app page" do
244 - put :resolve, :app_id => @err.app.id, :id => @err.id  
245 - response.should redirect_to(app_path(@err.app)) 244 + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
  245 + response.should redirect_to(app_path(@problem.app))
246 end 246 end
247 - 247 +
248 it "should redirect back to errs page" do 248 it "should redirect back to errs page" do
249 request.env["Referer"] = errs_path 249 request.env["Referer"] = errs_path
250 - put :resolve, :app_id => @err.app.id, :id => @err.id 250 + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id
251 response.should redirect_to(errs_path) 251 response.should redirect_to(errs_path)
252 end 252 end
253 end 253 end
254 - 254 +
255 describe "POST /apps/:app_id/errs/:id/create_issue" do 255 describe "POST /apps/:app_id/errs/:id/create_issue" do
256 render_views 256 render_views
257 - 257 +
258 before(:each) do 258 before(:each) do
259 sign_in Factory(:admin) 259 sign_in Factory(:admin)
260 end 260 end
261 - 261 +
262 context "successful issue creation" do 262 context "successful issue creation" do
263 context "lighthouseapp tracker" do 263 context "lighthouseapp tracker" do
264 let(:notice) { Factory :notice } 264 let(:notice) { Factory :notice }
265 - let(:tracker) { Factory :lighthouse_tracker, :app => notice.err.app }  
266 - let(:err) { notice.err }  
267 - 265 + let(:tracker) { Factory :lighthouse_tracker, :app => notice.app }
  266 + let(:problem) { notice.problem }
  267 +
268 before(:each) do 268 before(:each) do
269 number = 5 269 number = 5
270 @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml" 270 @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"
271 body = "<ticket><number type=\"integer\">#{number}</number></ticket>" 271 body = "<ticket><number type=\"integer\">#{number}</number></ticket>"
272 stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml"). 272 stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").
273 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) 273 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
274 -  
275 - post :create_issue, :app_id => err.app.id, :id => err.id  
276 - err.reload 274 +
  275 + post :create_issue, :app_id => problem.app.id, :id => problem.id
  276 + problem.reload
277 end 277 end
278 -  
279 - it "should redirect to err page" do  
280 - response.should redirect_to( app_err_path(err.app, err) ) 278 +
  279 + it "should redirect to problem page" do
  280 + response.should redirect_to( app_err_path(problem.app, problem) )
281 end 281 end
282 end 282 end
283 end 283 end
284 - 284 +
285 context "absent issue tracker" do 285 context "absent issue tracker" do
286 - let(:err) { Factory :err }  
287 - 286 + let(:problem) { Factory :problem }
  287 +
288 before(:each) do 288 before(:each) do
289 - post :create_issue, :app_id => err.app.id, :id => err.id 289 + post :create_issue, :app_id => problem.app.id, :id => problem.id
290 end 290 end
291 -  
292 - it "should redirect to err page" do  
293 - response.should redirect_to( app_err_path(err.app, err) ) 291 +
  292 + it "should redirect to problem page" do
  293 + response.should redirect_to( app_err_path(problem.app, problem) )
294 end 294 end
295 - 295 +
296 it "should set flash error message telling issue tracker of the app doesn't exist" do 296 it "should set flash error message telling issue tracker of the app doesn't exist" do
297 flash[:error].should == "This app has no issue tracker setup." 297 flash[:error].should == "This app has no issue tracker setup."
298 end 298 end
299 end 299 end
300 - 300 +
301 context "error during request to a tracker" do 301 context "error during request to a tracker" do
302 context "lighthouseapp tracker" do 302 context "lighthouseapp tracker" do
303 let(:tracker) { Factory :lighthouse_tracker } 303 let(:tracker) { Factory :lighthouse_tracker }
304 - let(:err) { Factory :err, :app => tracker.app }  
305 - 304 + let(:err) { Factory(:err, :problem => Factory(:problem, :app => tracker.app)) }
  305 +
306 before(:each) do 306 before(:each) do
307 stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 500) 307 stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 500)
308 -  
309 - post :create_issue, :app_id => err.app.id, :id => err.id 308 +
  309 + post :create_issue, :app_id => err.app.id, :id => err.problem.id
310 end 310 end
311 - 311 +
312 it "should redirect to err page" do 312 it "should redirect to err page" do
313 - response.should redirect_to( app_err_path(err.app, err) ) 313 + response.should redirect_to( app_err_path(err.app, err.problem) )
314 end 314 end
315 - 315 +
316 it "should notify of connection error" do 316 it "should notify of connection error" do
317 flash[:error].should == "There was an error during issue creation. Check your tracker settings or try again later." 317 flash[:error].should == "There was an error during issue creation. Check your tracker settings or try again later."
318 end 318 end
319 end 319 end
320 end 320 end
321 end 321 end
322 - 322 +
323 describe "DELETE /apps/:app_id/errs/:id/unlink_issue" do 323 describe "DELETE /apps/:app_id/errs/:id/unlink_issue" do
324 before(:each) do 324 before(:each) do
325 sign_in Factory(:admin) 325 sign_in Factory(:admin)
326 end 326 end
327 - 327 +
328 context "err with issue" do 328 context "err with issue" do
329 - let(:err) { Factory :err, :issue_link => "http://some.host" }  
330 - 329 + let(:err) { Factory(:err, :problem => Factory(:problem, :issue_link => "http://some.host")) }
  330 +
331 before(:each) do 331 before(:each) do
332 - delete :unlink_issue, :app_id => err.app.id, :id => err.id  
333 - err.reload 332 + delete :unlink_issue, :app_id => err.app.id, :id => err.problem.id
  333 + err.problem.reload
334 end 334 end
335 - 335 +
336 it "should redirect to err page" do 336 it "should redirect to err page" do
337 - response.should redirect_to( app_err_path(err.app, err) ) 337 + response.should redirect_to( app_err_path(err.app, err.problem) )
338 end 338 end
339 - 339 +
340 it "should clear issue link" do 340 it "should clear issue link" do
341 - err.issue_link.should be_nil 341 + err.problem.issue_link.should be_nil
342 end 342 end
343 end 343 end
344 - 344 +
345 context "err without issue" do 345 context "err without issue" do
346 let(:err) { Factory :err } 346 let(:err) { Factory :err }
347 - 347 +
348 before(:each) do 348 before(:each) do
349 - delete :unlink_issue, :app_id => err.app.id, :id => err.id  
350 - err.reload 349 + delete :unlink_issue, :app_id => err.app.id, :id => err.problem.id
  350 + err.problem.reload
351 end 351 end
352 - 352 +
353 it "should redirect to err page" do 353 it "should redirect to err page" do
354 - response.should redirect_to( app_err_path(err.app, err) ) 354 + response.should redirect_to( app_err_path(err.app, err.problem) )
355 end 355 end
356 end 356 end
357 end 357 end
358 -  
359 - 358 +
  359 +
360 describe "POST /apps/:app_id/errs/:id/create_comment" do 360 describe "POST /apps/:app_id/errs/:id/create_comment" do
361 render_views 361 render_views
362 - 362 +
363 before(:each) do 363 before(:each) do
364 sign_in Factory(:admin) 364 sign_in Factory(:admin)
365 end 365 end
366 - 366 +
367 context "successful comment creation" do 367 context "successful comment creation" do
368 - let(:err) { Factory(:err) } 368 + let(:problem) { Factory(:problem) }
369 let(:user) { Factory(:user) } 369 let(:user) { Factory(:user) }
370 - 370 +
371 before(:each) do 371 before(:each) do
372 - post :create_comment, :app_id => err.app.id, :id => err.id, 372 + post :create_comment, :app_id => problem.app.id, :id => problem.id,
373 :comment => { :body => "One test comment", :user_id => user.id } 373 :comment => { :body => "One test comment", :user_id => user.id }
374 - err.reload 374 + problem.reload
375 end 375 end
376 - 376 +
377 it "should create the comment" do 377 it "should create the comment" do
378 - err.comments.size.should == 1 378 + problem.comments.size.should == 1
379 end 379 end
380 -  
381 - it "should redirect to err page" do  
382 - response.should redirect_to( app_err_path(err.app, err) ) 380 +
  381 + it "should redirect to problem page" do
  382 + response.should redirect_to( app_err_path(problem.app, problem) )
383 end 383 end
384 end 384 end
385 end 385 end
386 - 386 +
387 describe "DELETE /apps/:app_id/errs/:id/destroy_comment" do 387 describe "DELETE /apps/:app_id/errs/:id/destroy_comment" do
388 render_views 388 render_views
389 - 389 +
390 before(:each) do 390 before(:each) do
391 sign_in Factory(:admin) 391 sign_in Factory(:admin)
392 end 392 end
393 - 393 +
394 context "successful comment deletion" do 394 context "successful comment deletion" do
395 - let(:err) { Factory :err_with_comments }  
396 - let(:comment) { err.comments.first }  
397 - 395 + let(:problem) { Factory(:problem_with_comments) }
  396 + let(:comment) { problem.comments.first }
  397 +
398 before(:each) do 398 before(:each) do
399 - delete :destroy_comment, :app_id => err.app.id, :id => err.id, :comment_id => comment.id  
400 - err.reload 399 + delete :destroy_comment, :app_id => problem.app.id, :id => problem.id, :comment_id => comment.id
  400 + problem.reload
401 end 401 end
402 - 402 +
403 it "should delete the comment" do 403 it "should delete the comment" do
404 - err.comments.detect{|c| c.id.to_s == comment.id }.should == nil 404 + problem.comments.detect{|c| c.id.to_s == comment.id }.should == nil
405 end 405 end
406 -  
407 - it "should redirect to err page" do  
408 - response.should redirect_to( app_err_path(err.app, err) ) 406 +
  407 + it "should redirect to problem page" do
  408 + response.should redirect_to( app_err_path(problem.app, problem) )
409 end 409 end
410 end 410 end
411 end 411 end
412 - 412 +
413 describe "Bulk Actions" do 413 describe "Bulk Actions" do
414 before(:each) do 414 before(:each) do
415 sign_in Factory(:admin) 415 sign_in Factory(:admin)
416 - @err1 = Factory(:err, :resolved => true)  
417 - @err2 = Factory(:err, :resolved => false) 416 + @problem1 = Factory(:problem, :resolved => true)
  417 + @problem2 = Factory(:problem, :resolved => false)
418 end 418 end
419 419
420 - it "should apply to multiple errs" do  
421 - post :resolve_several, :errs => [@err1.id.to_s, @err2.id.to_s]  
422 - assigns(:selected_errs).should == [@err1, @err2] 420 + it "should apply to multiple problems" do
  421 + post :resolve_several, :problems => [@problem1.id.to_s, @problem2.id.to_s]
  422 + assigns(:selected_problems).should == [@problem1, @problem2]
423 end 423 end
424 424
425 context "POST /errs/resolve_several" do 425 context "POST /errs/resolve_several" do
426 it "should resolve the issue" do 426 it "should resolve the issue" do
427 - post :resolve_several, :errs => [@err2.id.to_s]  
428 - @err2.reload.resolved?.should == true 427 + post :resolve_several, :problems => [@problem2.id.to_s]
  428 + @problem2.reload.resolved?.should == true
429 end 429 end
430 end 430 end
431 431
432 context "POST /errs/unresolve_several" do 432 context "POST /errs/unresolve_several" do
433 it "should unresolve the issue" do 433 it "should unresolve the issue" do
434 - post :unresolve_several, :errs => [@err1.id.to_s]  
435 - @err1.reload.resolved?.should == false 434 + post :unresolve_several, :problems => [@problem1.id.to_s]
  435 + @problem1.reload.resolved?.should == false
436 end 436 end
437 end 437 end
438 438
439 context "POST /errs/destroy_several" do 439 context "POST /errs/destroy_several" do
440 it "should delete the errs" do 440 it "should delete the errs" do
441 lambda { 441 lambda {
442 - post :destroy_several, :errs => [@err1.id.to_s]  
443 - }.should change(Err, :count).by(-1) 442 + post :destroy_several, :problems => [@problem1.id.to_s]
  443 + }.should change(Problem, :count).by(-1)
444 end 444 end
445 end 445 end
446 end 446 end
  447 +
447 end 448 end
448 -  
spec/controllers/notices_controller_spec.rb
@@ -29,9 +29,9 @@ describe NoticesController do @@ -29,9 +29,9 @@ describe NoticesController do
29 post :create 29 post :create
30 email = ActionMailer::Base.deliveries.last 30 email = ActionMailer::Base.deliveries.last
31 email.to.should include(@app.watchers.first.email) 31 email.to.should include(@app.watchers.first.email)
32 - email.subject.should include(@notice.err.message) 32 + email.subject.should include(@notice.message)
33 email.subject.should include("[#{@app.name}]") 33 email.subject.should include("[#{@app.name}]")
34 - email.subject.should include("[#{@notice.err.environment}]") 34 + email.subject.should include("[#{@notice.environment_name}]")
35 end 35 end
36 end 36 end
37 37
spec/factories/app_factories.rb
@@ -20,7 +20,7 @@ Factory.define(:user_watcher, :parent =&gt; :watcher) do |w| @@ -20,7 +20,7 @@ Factory.define(:user_watcher, :parent =&gt; :watcher) do |w|
20 end 20 end
21 21
22 Factory.define(:deploy) do |d| 22 Factory.define(:deploy) do |d|
23 - d.app {|p| p.association :app} 23 + d.app {|p| p.association :app}
24 d.username 'clyde.frog' 24 d.username 'clyde.frog'
25 d.repository 'git@github.com/errbit/errbit.git' 25 d.repository 'git@github.com/errbit/errbit.git'
26 d.environment 'production' 26 d.environment 'production'
spec/factories/err_factories.rb
  1 +Factory.define :problem do |p|
  2 + p.app {|a| a.association :app}
  3 + p.comments []
  4 +end
  5 +
  6 +Factory.define(:problem_with_comments, :parent => :problem) do |ec|
  7 + ec.comments { (1..3).map { Factory(:comment) } }
  8 +end
  9 +
  10 +
  11 +
1 Factory.define :err do |e| 12 Factory.define :err do |e|
2 - e.app {|p| p.association :app } 13 + e.problem {|p| p.association :problem}
3 e.klass 'FooError' 14 e.klass 'FooError'
4 e.component 'foo' 15 e.component 'foo'
5 e.action 'bar' 16 e.action 'bar'
6 e.environment 'production' 17 e.environment 'production'
7 - e.comments []  
8 end 18 end
9 19
10 -Factory.define(:err_with_comments, :parent => :err) do |ec|  
11 - ec.comments { (1..3).map{Factory(:comment)} }  
12 -end 20 +
13 21
14 Factory.define :notice do |n| 22 Factory.define :notice do |n|
15 n.err {|e| e.association :err} 23 n.err {|e| e.association :err}
16 n.message 'FooError: Too Much Bar' 24 n.message 'FooError: Too Much Bar'
17 n.backtrace { random_backtrace } 25 n.backtrace { random_backtrace }
18 - n.server_environment 'server-environment' => 'production' 26 + n.server_environment 'environment-name' => 'production'
19 n.notifier 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' 27 n.notifier 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com'
20 end 28 end
21 29
@@ -28,4 +36,3 @@ def random_backtrace @@ -28,4 +36,3 @@ def random_backtrace
28 }} 36 }}
29 backtrace 37 backtrace
30 end 38 end
31 -  
spec/models/app_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe App do 3 describe App do
4 - 4 +
  5 +
5 context 'validations' do 6 context 'validations' do
6 it 'requires a name' do 7 it 'requires a name' do
7 app = Factory.build(:app, :name => nil) 8 app = Factory.build(:app, :name => nil)
8 app.should_not be_valid 9 app.should_not be_valid
9 app.errors[:name].should include("can't be blank") 10 app.errors[:name].should include("can't be blank")
10 end 11 end
11 - 12 +
12 it 'requires unique names' do 13 it 'requires unique names' do
13 Factory(:app, :name => 'Errbit') 14 Factory(:app, :name => 'Errbit')
14 app = Factory.build(:app, :name => 'Errbit') 15 app = Factory.build(:app, :name => 'Errbit')
15 app.should_not be_valid 16 app.should_not be_valid
16 app.errors[:name].should include('is already taken') 17 app.errors[:name].should include('is already taken')
17 end 18 end
18 - 19 +
19 it 'requires unique api_keys' do 20 it 'requires unique api_keys' do
20 Factory(:app, :api_key => 'APIKEY') 21 Factory(:app, :api_key => 'APIKEY')
21 app = Factory.build(:app, :api_key => 'APIKEY') 22 app = Factory.build(:app, :api_key => 'APIKEY')
@@ -23,7 +24,8 @@ describe App do @@ -23,7 +24,8 @@ describe App do
23 app.errors[:api_key].should include('is already taken') 24 app.errors[:api_key].should include('is already taken')
24 end 25 end
25 end 26 end
26 - 27 +
  28 +
27 context 'being created' do 29 context 'being created' do
28 it 'generates a new api-key' do 30 it 'generates a new api-key' do
29 app = Factory.build(:app) 31 app = Factory.build(:app)
@@ -31,62 +33,62 @@ describe App do @@ -31,62 +33,62 @@ describe App do
31 app.save 33 app.save
32 app.api_key.should_not be_nil 34 app.api_key.should_not be_nil
33 end 35 end
34 - 36 +
35 it 'generates a correct api-key' do 37 it 'generates a correct api-key' do
36 app = Factory(:app) 38 app = Factory(:app)
37 app.api_key.should match(/^[a-f0-9]{32}$/) 39 app.api_key.should match(/^[a-f0-9]{32}$/)
38 end 40 end
39 - 41 +
40 it 'is fine with blank github urls' do 42 it 'is fine with blank github urls' do
41 app = Factory.build(:app, :github_url => "") 43 app = Factory.build(:app, :github_url => "")
42 app.save 44 app.save
43 app.github_url.should == "" 45 app.github_url.should == ""
44 end 46 end
45 - 47 +
46 it 'does not touch https github urls' do 48 it 'does not touch https github urls' do
47 app = Factory.build(:app, :github_url => "https://github.com/errbit/errbit") 49 app = Factory.build(:app, :github_url => "https://github.com/errbit/errbit")
48 app.save 50 app.save
49 app.github_url.should == "https://github.com/errbit/errbit" 51 app.github_url.should == "https://github.com/errbit/errbit"
50 end 52 end
51 - 53 +
52 it 'normalizes http github urls' do 54 it 'normalizes http github urls' do
53 app = Factory.build(:app, :github_url => "http://github.com/errbit/errbit") 55 app = Factory.build(:app, :github_url => "http://github.com/errbit/errbit")
54 app.save 56 app.save
55 app.github_url.should == "https://github.com/errbit/errbit" 57 app.github_url.should == "https://github.com/errbit/errbit"
56 end 58 end
57 - 59 +
58 it 'normalizes public git repo as a github url' do 60 it 'normalizes public git repo as a github url' do
59 app = Factory.build(:app, :github_url => "https://github.com/errbit/errbit.git") 61 app = Factory.build(:app, :github_url => "https://github.com/errbit/errbit.git")
60 app.save 62 app.save
61 app.github_url.should == "https://github.com/errbit/errbit" 63 app.github_url.should == "https://github.com/errbit/errbit"
62 end 64 end
63 - 65 +
64 it 'normalizes private git repo as a github url' do 66 it 'normalizes private git repo as a github url' do
65 app = Factory.build(:app, :github_url => "git@github.com:errbit/errbit.git") 67 app = Factory.build(:app, :github_url => "git@github.com:errbit/errbit.git")
66 app.save 68 app.save
67 app.github_url.should == "https://github.com/errbit/errbit" 69 app.github_url.should == "https://github.com/errbit/errbit"
68 end 70 end
69 end 71 end
70 - 72 +
71 context '#github_url_to_file' do 73 context '#github_url_to_file' do
72 it 'resolves to full path to file' do 74 it 'resolves to full path to file' do
73 app = Factory(:app, :github_url => "https://github.com/errbit/errbit") 75 app = Factory(:app, :github_url => "https://github.com/errbit/errbit")
74 app.github_url_to_file('/path/to/file').should == "https://github.com/errbit/errbit/blob/master/path/to/file" 76 app.github_url_to_file('/path/to/file').should == "https://github.com/errbit/errbit/blob/master/path/to/file"
75 end 77 end
76 end 78 end
77 - 79 +
78 context '#github_url?' do 80 context '#github_url?' do
79 it 'is true when there is a github_url' do 81 it 'is true when there is a github_url' do
80 app = Factory(:app, :github_url => "https://github.com/errbit/errbit") 82 app = Factory(:app, :github_url => "https://github.com/errbit/errbit")
81 app.github_url?.should be_true 83 app.github_url?.should be_true
82 end 84 end
83 - 85 +
84 it 'is false when no github_url' do 86 it 'is false when no github_url' do
85 app = Factory(:app) 87 app = Factory(:app)
86 app.github_url?.should be_false 88 app.github_url?.should be_false
87 end 89 end
88 end 90 end
89 - 91 +
90 context "notification recipients" do 92 context "notification recipients" do
91 it "should send notices to either all users, or the configured watchers" do 93 it "should send notices to either all users, or the configured watchers" do
92 @app = Factory(:app) 94 @app = Factory(:app)
@@ -98,7 +100,8 @@ describe App do @@ -98,7 +100,8 @@ describe App do
98 @app.notification_recipients.size.should == 5 100 @app.notification_recipients.size.should == 5
99 end 101 end
100 end 102 end
101 - 103 +
  104 +
102 context "copying attributes from existing app" do 105 context "copying attributes from existing app" do
103 it "should only copy the necessary fields" do 106 it "should only copy the necessary fields" do
104 @app, @copy_app = Factory(:app, :name => "app", :github_url => "url"), 107 @app, @copy_app = Factory(:app, :name => "app", :github_url => "url"),
@@ -110,5 +113,36 @@ describe App do @@ -110,5 +113,36 @@ describe App do
110 @app.watchers.first.email.should == "copywatcher@example.com" 113 @app.watchers.first.email.should == "copywatcher@example.com"
111 end 114 end
112 end 115 end
  116 +
  117 +
  118 + context '#find_or_create_err!' do
  119 + before do
  120 + @app = Factory(:app)
  121 + @conditions = {
  122 + :klass => 'Whoops',
  123 + :component => 'Foo',
  124 + :action => 'bar',
  125 + :environment => 'production'
  126 + }
  127 + end
  128 +
  129 + it 'returns the correct err if one already exists' do
  130 + existing = Factory(:err, @conditions.merge(:problem => Factory(:problem, :app => @app)))
  131 + Err.where(@conditions).first.should == existing
  132 + @app.find_or_create_err!(@conditions).should == existing
  133 + end
  134 +
  135 + it 'assigns the returned err to the given app' do
  136 + @app.find_or_create_err!(@conditions).app.should == @app
  137 + end
  138 +
  139 + it 'creates a new problem if a matching one does not already exist' do
  140 + Err.where(@conditions).first.should be_nil
  141 + lambda {
  142 + @app.find_or_create_err!(@conditions)
  143 + }.should change(Problem,:count).by(1)
  144 + end
  145 + end
  146 +
  147 +
113 end 148 end
114 -  
spec/models/deploy_spec.rb
@@ -26,20 +26,20 @@ describe Deploy do @@ -26,20 +26,20 @@ describe Deploy do
26 context 'when the app has resolve_errs_on_deploy set to false' do 26 context 'when the app has resolve_errs_on_deploy set to false' do
27 it 'should not resolve the apps errs' do 27 it 'should not resolve the apps errs' do
28 app = Factory(:app, :resolve_errs_on_deploy => false) 28 app = Factory(:app, :resolve_errs_on_deploy => false)
29 - @errs = 3.times.inject([]) {|errs,_| errs << Factory(:err, :resolved => false, :app => app)} 29 + @problems = 3.times.map{Factory(:err, :problem => Factory(:problem, :resolved => false, :app => app))}
30 Factory(:deploy, :app => app) 30 Factory(:deploy, :app => app)
31 - app.reload.errs.none?{|err| err.resolved?}.should == true 31 + app.reload.problems.none?{|problem| problem.resolved?}.should == true
32 end 32 end
33 end 33 end
34 34
35 context 'when the app has resolve_errs_on_deploy set to true' do 35 context 'when the app has resolve_errs_on_deploy set to true' do
36 it 'should resolve the apps errs that were in the same environment' do 36 it 'should resolve the apps errs that were in the same environment' do
37 app = Factory(:app, :resolve_errs_on_deploy => true) 37 app = Factory(:app, :resolve_errs_on_deploy => true)
38 - @prod_errs = 3.times.inject([]) {|errs,_| errs << Factory(:err, :resolved => false, :app => app, :environment => 'production')}  
39 - @staging_errs = 3.times.inject([]) {|errs,_| errs << Factory(:err, :resolved => false, :app => app, :environment => 'staging')} 38 + @prod_errs = 3.times.map{Factory(:problem, :resolved => false, :app => app, :environment => 'production')}
  39 + @staging_errs = 3.times.map{Factory(:problem, :resolved => false, :app => app, :environment => 'staging')}
40 Factory(:deploy, :app => app, :environment => 'production') 40 Factory(:deploy, :app => app, :environment => 'production')
41 - @prod_errs.all?{|err| err.reload.resolved?}.should == true  
42 - @staging_errs.all?{|err| err.reload.resolved?}.should == false 41 + @prod_errs.all?{|problem| problem.reload.resolved?}.should == true
  42 + @staging_errs.all?{|problem| problem.reload.resolved?}.should == false
43 end 43 end
44 end 44 end
45 45
spec/models/err_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe Err do 3 describe Err do
4 - 4 +
5 context 'validations' do 5 context 'validations' do
6 it 'requires a klass' do 6 it 'requires a klass' do
7 err = Factory.build(:err, :klass => nil) 7 err = Factory.build(:err, :klass => nil)
8 err.should_not be_valid 8 err.should_not be_valid
9 err.errors[:klass].should include("can't be blank") 9 err.errors[:klass].should include("can't be blank")
10 end 10 end
11 - 11 +
12 it 'requires an environment' do 12 it 'requires an environment' do
13 err = Factory.build(:err, :environment => nil) 13 err = Factory.build(:err, :environment => nil)
14 err.should_not be_valid 14 err.should_not be_valid
15 err.errors[:environment].should include("can't be blank") 15 err.errors[:environment].should include("can't be blank")
16 end 16 end
17 end 17 end
18 -  
19 - context '#for' do  
20 - before do  
21 - @app = Factory(:app)  
22 - @conditions = {  
23 - :app => @app,  
24 - :klass => 'Whoops',  
25 - :component => 'Foo',  
26 - :action => 'bar',  
27 - :environment => 'production'  
28 - }  
29 - end  
30 -  
31 - it 'returns the correct err if one already exists' do  
32 - existing = Err.create(@conditions)  
33 - Err.for(@conditions).should == existing  
34 - end  
35 -  
36 - it 'assigns the returned err to the given app' do  
37 - Err.for(@conditions).app.should == @app  
38 - end  
39 -  
40 - it 'creates a new err if a matching one does not already exist' do  
41 - Err.where(@conditions.except(:app)).exists?.should == false  
42 - lambda {  
43 - Err.for(@conditions)  
44 - }.should change(Err,:count).by(1)  
45 - end  
46 - end  
47 -  
48 - context '#last_notice_at' do  
49 - it "returns the created_at timestamp of the latest notice" do  
50 - err = Factory(:err)  
51 - err.last_notice_at.should be_nil  
52 -  
53 - notice1 = Factory(:notice, :err => err)  
54 - err.last_notice_at.should == notice1.created_at  
55 -  
56 - notice2 = Factory(:notice, :err => err)  
57 - err.last_notice_at.should == notice2.created_at  
58 - end  
59 - end  
60 -  
61 - context '#message' do  
62 - it "returns klass by default" do  
63 - err = Factory(:err)  
64 - err.message.should == err.klass  
65 - end  
66 -  
67 - it 'returns the message from the first notice' do  
68 - err = Factory(:err)  
69 - notice1 = Factory(:notice, :err => err, :message => 'ERR 1')  
70 - notice2 = Factory(:notice, :err => err, :message => 'ERR 2')  
71 - err.message.should == notice1.message  
72 - end  
73 -  
74 - it "adding a notice caches its message" do  
75 - err = Factory(:err)  
76 - lambda {  
77 - notice1 = Factory(:notice, :err => err, :message => 'ERR 1')}.should change(err, :message).from(err.klass).to('ERR 1')  
78 - end  
79 - end  
80 -  
81 - context "#resolved?" do  
82 - it "should start out as unresolved" do  
83 - err = Err.new  
84 - err.should_not be_resolved  
85 - err.should be_unresolved  
86 - end  
87 -  
88 - it "should be able to be resolved" do  
89 - err = Factory(:err)  
90 - err.should_not be_resolved  
91 - err.resolve!  
92 - err.reload.should be_resolved  
93 - end  
94 - end  
95 -  
96 - context "resolve!" do  
97 - it "marks the err as resolved" do  
98 - err = Factory(:err)  
99 - err.should_not be_resolved  
100 - err.resolve!  
101 - err.should be_resolved  
102 - end  
103 -  
104 - it "should throw an err if it's not successful" do  
105 - err = Factory(:err)  
106 - err.should_not be_resolved  
107 - err.klass = nil  
108 - err.should_not be_valid  
109 - lambda {  
110 - err.resolve!  
111 - }.should raise_error(Mongoid::Errors::Validations)  
112 - end  
113 - end  
114 -  
115 - context "Scopes" do  
116 - context "resolved" do  
117 - it 'only finds resolved Errs' do  
118 - resolved = Factory(:err, :resolved => true)  
119 - unresolved = Factory(:err, :resolved => false)  
120 - Err.resolved.all.should include(resolved)  
121 - Err.resolved.all.should_not include(unresolved)  
122 - end  
123 - end  
124 -  
125 - context "unresolved" do  
126 - it 'only finds unresolved Errs' do  
127 - resolved = Factory(:err, :resolved => true)  
128 - unresolved = Factory(:err, :resolved => false)  
129 - Err.unresolved.all.should_not include(resolved)  
130 - Err.unresolved.all.should include(unresolved)  
131 - end  
132 - end  
133 - end  
134 -  
135 - context 'being created' do  
136 - context 'when the app has err notifications set to false' do  
137 - it 'should not send an email notification' do  
138 - app = Factory(:app_with_watcher, :notify_on_errs => false)  
139 - Mailer.should_not_receive(:err_notification)  
140 - Factory(:err, :app => app)  
141 - end  
142 - end  
143 - end  
144 -  
145 - context "notice counter cache" do  
146 -  
147 - before do  
148 - @app = Factory(:app)  
149 - @err = Factory(:err, :app => @app)  
150 - end  
151 -  
152 - it "#notices_count returns 0 by default" do  
153 - @err.notices_count.should == 0  
154 - end  
155 -  
156 - it "adding a notice increases #notices_count by 1" do  
157 - lambda {  
158 - notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')}.should change(@err, :notices_count).from(0).to(1)  
159 - end  
160 -  
161 - it "removing a notice decreases #notices_count by 1" do  
162 - notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')  
163 - lambda {  
164 - @err.notices.first.destroy  
165 - @err.reload  
166 - }.should change(@err, :notices_count).from(1).to(0)  
167 - end  
168 - end  
169 -  
170 - 18 +
171 end 19 end
172 -  
spec/models/issue_trackers/fogbugz_tracker_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe FogbugzTracker do 3 describe FogbugzTracker do
4 - it "should create an issue on Fogbugz with err params, and set issue link for err" do 4 + it "should create an issue on Fogbugz with problem params, and set issue link for problem" do
5 notice = Factory :notice 5 notice = Factory :notice
6 - tracker = Factory :fogbugz_tracker, :app => notice.err.app  
7 - err = notice.err  
8 - 6 + tracker = Factory :fogbugz_tracker, :app => notice.app
  7 + problem = notice.problem
  8 +
9 number = 123 9 number = 123
10 @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}" 10 @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}"
11 response = "<response><token>12345</token><case><ixBug>123</ixBug></case></response>" 11 response = "<response><token>12345</token><case><ixBug>123</ixBug></case></response>"
@@ -13,11 +13,10 @@ describe FogbugzTracker do @@ -13,11 +13,10 @@ describe FogbugzTracker do
13 http_mock.should_receive(:new).and_return(http_mock) 13 http_mock.should_receive(:new).and_return(http_mock)
14 http_mock.should_receive(:request).twice.and_return(response) 14 http_mock.should_receive(:request).twice.and_return(response)
15 Fogbugz.adapter[:http] = http_mock 15 Fogbugz.adapter[:http] = http_mock
16 -  
17 - err.app.issue_tracker.create_issue(err)  
18 - err.reload  
19 -  
20 - err.issue_link.should == @issue_link 16 +
  17 + problem.app.issue_tracker.create_issue(problem)
  18 + problem.reload
  19 +
  20 + problem.issue_link.should == @issue_link
21 end 21 end
22 end 22 end
23 -  
spec/models/issue_trackers/github_issues_tracker_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe GithubIssuesTracker do 3 describe GithubIssuesTracker do
4 - it "should create an issue on Github Issues with err params, and set issue link for err" do 4 + it "should create an issue on Github Issues with problem params, and set issue link for problem" do
5 notice = Factory :notice 5 notice = Factory :notice
6 - tracker = Factory :github_issues_tracker, :app => notice.err.app  
7 - err = notice.err  
8 - 6 + tracker = Factory :github_issues_tracker, :app => notice.app
  7 + problem = notice.problem
  8 +
9 number = 5 9 number = 5
10 @issue_link = "https://github.com/#{tracker.project_id}/issues/#{number}" 10 @issue_link = "https://github.com/#{tracker.project_id}/issues/#{number}"
11 body = <<EOF 11 body = <<EOF
@@ -26,16 +26,15 @@ describe GithubIssuesTracker do @@ -26,16 +26,15 @@ describe GithubIssuesTracker do
26 EOF 26 EOF
27 stub_request(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}"). 27 stub_request(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}").
28 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) 28 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
29 -  
30 - err.app.issue_tracker.create_issue(err)  
31 - err.reload  
32 - 29 +
  30 + problem.app.issue_tracker.create_issue(problem)
  31 + problem.reload
  32 +
33 requested = have_requested(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}") 33 requested = have_requested(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}")
34 WebMock.should requested.with(:headers => {'Content-Type' => 'application/x-www-form-urlencoded'}) 34 WebMock.should requested.with(:headers => {'Content-Type' => 'application/x-www-form-urlencoded'})
35 WebMock.should requested.with(:body => /title=%5Bproduction%5D%5Bfoo%23bar%5D%20FooError%3A%20Too%20Much%20Bar/) 35 WebMock.should requested.with(:body => /title=%5Bproduction%5D%5Bfoo%23bar%5D%20FooError%3A%20Too%20Much%20Bar/)
36 WebMock.should requested.with(:body => /See%20this%20exception%20on%20Errbit/) 36 WebMock.should requested.with(:body => /See%20this%20exception%20on%20Errbit/)
37 -  
38 - err.issue_link.should == @issue_link 37 +
  38 + problem.issue_link.should == @issue_link
39 end 39 end
40 end 40 end
41 -  
spec/models/issue_trackers/lighthouse_tracker_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe LighthouseTracker do 3 describe LighthouseTracker do
4 - it "should create an issue on Lighthouse with err params, and set issue link for err" do 4 + it "should create an issue on Lighthouse with problem params, and set issue link for problem" do
5 notice = Factory :notice 5 notice = Factory :notice
6 - tracker = Factory :lighthouse_tracker, :app => notice.err.app  
7 - err = notice.err  
8 - 6 + tracker = Factory :lighthouse_tracker, :app => notice.app
  7 + problem = notice.problem
  8 +
9 number = 5 9 number = 5
10 @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml" 10 @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"
11 body = "<ticket><number type=\"integer\">#{number}</number></ticket>" 11 body = "<ticket><number type=\"integer\">#{number}</number></ticket>"
12 stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml"). 12 stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").
13 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) 13 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
14 -  
15 - err.app.issue_tracker.create_issue(err)  
16 - err.reload  
17 - 14 +
  15 + problem.app.issue_tracker.create_issue(problem)
  16 + problem.reload
  17 +
18 requested = have_requested(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml") 18 requested = have_requested(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml")
19 WebMock.should requested.with(:headers => {'X-Lighthousetoken' => tracker.api_token}) 19 WebMock.should requested.with(:headers => {'X-Lighthousetoken' => tracker.api_token})
20 WebMock.should requested.with(:body => /<tag>errbit<\/tag>/) 20 WebMock.should requested.with(:body => /<tag>errbit<\/tag>/)
21 - WebMock.should requested.with(:body => /<title>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/title>/) 21 + WebMock.should requested.with(:body => /<title>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/title>/)
22 WebMock.should requested.with(:body => /<body>.+<\/body>/m) 22 WebMock.should requested.with(:body => /<body>.+<\/body>/m)
23 -  
24 - err.issue_link.should == @issue_link.sub(/\.xml$/, '') 23 +
  24 + problem.issue_link.should == @issue_link.sub(/\.xml$/, '')
25 end 25 end
26 end 26 end
27 -  
spec/models/issue_trackers/mingle_tracker_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe MingleTracker do 3 describe MingleTracker do
4 - it "should create an issue on Mingle with err params, and set issue link for err" do 4 + it "should create an issue on Mingle with problem params, and set issue link for problem" do
5 notice = Factory :notice 5 notice = Factory :notice
6 - tracker = Factory :mingle_tracker, :app => notice.err.app  
7 - err = notice.err  
8 - 6 + tracker = Factory :mingle_tracker, :app => notice.app
  7 + problem = notice.problem
  8 +
9 number = 5 9 number = 5
10 @issue_link = "#{tracker.account}/projects/#{tracker.project_id}/cards/#{number}.xml" 10 @issue_link = "#{tracker.account}/projects/#{tracker.project_id}/cards/#{number}.xml"
11 @basic_auth = tracker.account.gsub("://", "://#{tracker.username}:#{tracker.password}@") 11 @basic_auth = tracker.account.gsub("://", "://#{tracker.username}:#{tracker.password}@")
12 body = "<card><id type=\"integer\">#{number}</id></card>" 12 body = "<card><id type=\"integer\">#{number}</id></card>"
13 stub_request(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml"). 13 stub_request(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml").
14 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) 14 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
15 -  
16 - err.app.issue_tracker.create_issue(err)  
17 - err.reload  
18 - 15 +
  16 + problem.app.issue_tracker.create_issue(problem)
  17 + problem.reload
  18 +
19 requested = have_requested(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml") 19 requested = have_requested(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml")
20 WebMock.should requested.with(:headers => {'Content-Type' => 'application/xml'}) 20 WebMock.should requested.with(:headers => {'Content-Type' => 'application/xml'})
21 WebMock.should requested.with(:body => /FooError: Too Much Bar/) 21 WebMock.should requested.with(:body => /FooError: Too Much Bar/)
22 WebMock.should requested.with(:body => /See this exception on Errbit/) 22 WebMock.should requested.with(:body => /See this exception on Errbit/)
23 WebMock.should requested.with(:body => /<card-type-name>Defect<\/card-type-name>/) 23 WebMock.should requested.with(:body => /<card-type-name>Defect<\/card-type-name>/)
24 -  
25 - err.issue_link.should == @issue_link.sub(/\.xml$/, '') 24 +
  25 + problem.issue_link.should == @issue_link.sub(/\.xml$/, '')
26 end 26 end
27 end 27 end
28 -  
spec/models/issue_trackers/pivotal_labs_tracker_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe PivotalLabsTracker do 3 describe PivotalLabsTracker do
4 - it "should create an issue on Pivotal Tracker with err params, and set issue link for err" do 4 + it "should create an issue on Pivotal Tracker with problem params, and set issue link for problem" do
5 notice = Factory :notice 5 notice = Factory :notice
6 - tracker = Factory :pivotal_labs_tracker, :app => notice.err.app, :project_id => 10  
7 - err = notice.err  
8 - 6 + tracker = Factory :pivotal_labs_tracker, :app => notice.app, :project_id => 10
  7 + problem = notice.problem
  8 +
9 story_id = 5 9 story_id = 5
10 @issue_link = "https://www.pivotaltracker.com/story/show/#{story_id}" 10 @issue_link = "https://www.pivotaltracker.com/story/show/#{story_id}"
11 project_body = "<project><id>#{tracker.project_id}</id><name>TestProject</name></project>" 11 project_body = "<project><id>#{tracker.project_id}</id><name>TestProject</name></project>"
@@ -14,17 +14,16 @@ describe PivotalLabsTracker do @@ -14,17 +14,16 @@ describe PivotalLabsTracker do
14 story_body = "<story><name>Test Story</name><id>#{story_id}</id></story>" 14 story_body = "<story><name>Test Story</name><id>#{story_id}</id></story>"
15 stub_request(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories"). 15 stub_request(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories").
16 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => story_body ) 16 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => story_body )
17 -  
18 - err.app.issue_tracker.create_issue(err)  
19 - err.reload  
20 - 17 +
  18 + problem.app.issue_tracker.create_issue(problem)
  19 + problem.reload
  20 +
21 requested = have_requested(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories") 21 requested = have_requested(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories")
22 WebMock.should requested.with(:headers => {'X-Trackertoken' => tracker.api_token}) 22 WebMock.should requested.with(:headers => {'X-Trackertoken' => tracker.api_token})
23 WebMock.should requested.with(:body => /See this exception on Errbit/) 23 WebMock.should requested.with(:body => /See this exception on Errbit/)
24 - WebMock.should requested.with(:body => /<name>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/name>/) 24 + WebMock.should requested.with(:body => /<name>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/name>/)
25 WebMock.should requested.with(:body => /<description>.+<\/description>/m) 25 WebMock.should requested.with(:body => /<description>.+<\/description>/m)
26 -  
27 - err.issue_link.should == @issue_link 26 +
  27 + problem.issue_link.should == @issue_link
28 end 28 end
29 end 29 end
30 -  
spec/models/issue_trackers/redmine_tracker_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe RedmineTracker do 3 describe RedmineTracker do
4 - it "should create an issue on Redmine with err params, and set issue link for err" do 4 + it "should create an issue on Redmine with problem params, and set issue link for problem" do
5 notice = Factory :notice 5 notice = Factory :notice
6 - tracker = Factory :redmine_tracker, :app => notice.err.app, :project_id => 10  
7 - err = notice.err 6 + tracker = Factory :redmine_tracker, :app => notice.app, :project_id => 10
  7 + problem = notice.problem
8 number = 5 8 number = 5
9 @issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}" 9 @issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}"
10 body = "<issue><subject>my subject</subject><id>#{number}</id></issue>" 10 body = "<issue><subject>my subject</subject><id>#{number}</id></issue>"
11 stub_request(:post, "#{tracker.account}/issues.xml"). 11 stub_request(:post, "#{tracker.account}/issues.xml").
12 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) 12 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
13 -  
14 - err.app.issue_tracker.create_issue(err)  
15 - err.reload  
16 - 13 +
  14 + problem.app.issue_tracker.create_issue(problem)
  15 + problem.reload
  16 +
17 requested = have_requested(:post, "#{tracker.account}/issues.xml") 17 requested = have_requested(:post, "#{tracker.account}/issues.xml")
18 WebMock.should requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token}) 18 WebMock.should requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token})
19 WebMock.should requested.with(:body => /<project-id>#{tracker.project_id}<\/project-id>/) 19 WebMock.should requested.with(:body => /<project-id>#{tracker.project_id}<\/project-id>/)
20 - WebMock.should requested.with(:body => /<subject>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/subject>/) 20 + WebMock.should requested.with(:body => /<subject>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/subject>/)
21 WebMock.should requested.with(:body => /<description>.+<\/description>/m) 21 WebMock.should requested.with(:body => /<description>.+<\/description>/m)
22 -  
23 - err.issue_link.should == @issue_link.sub(/\.xml/, '') 22 +
  23 + problem.issue_link.should == @issue_link.sub(/\.xml/, '')
24 end 24 end
25 end 25 end
26 -  
spec/models/notice_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe Notice do 3 describe Notice do
4 - 4 +
  5 +
5 context 'validations' do 6 context 'validations' do
6 it 'requires a backtrace' do 7 it 'requires a backtrace' do
7 notice = Factory.build(:notice, :backtrace => nil) 8 notice = Factory.build(:notice, :backtrace => nil)
8 notice.should_not be_valid 9 notice.should_not be_valid
9 notice.errors[:backtrace].should include("can't be blank") 10 notice.errors[:backtrace].should include("can't be blank")
10 end 11 end
11 - 12 +
12 it 'requires the server_environment' do 13 it 'requires the server_environment' do
13 notice = Factory.build(:notice, :server_environment => nil) 14 notice = Factory.build(:notice, :server_environment => nil)
14 notice.should_not be_valid 15 notice.should_not be_valid
15 notice.errors[:server_environment].should include("can't be blank") 16 notice.errors[:server_environment].should include("can't be blank")
16 end 17 end
17 - 18 +
18 it 'requires the notifier' do 19 it 'requires the notifier' do
19 notice = Factory.build(:notice, :notifier => nil) 20 notice = Factory.build(:notice, :notifier => nil)
20 notice.should_not be_valid 21 notice.should_not be_valid
21 notice.errors[:notifier].should include("can't be blank") 22 notice.errors[:notifier].should include("can't be blank")
22 end 23 end
23 end 24 end
24 - 25 +
  26 +
25 context '.in_app_backtrace_line?' do 27 context '.in_app_backtrace_line?' do
26 let(:backtrace) do [{ 28 let(:backtrace) do [{
27 'number' => rand(999), 29 'number' => rand(999),
@@ -50,22 +52,23 @@ describe Notice do @@ -50,22 +52,23 @@ describe Notice do
50 Notice.in_app_backtrace_line?(backtrace[2]).should == true 52 Notice.in_app_backtrace_line?(backtrace[2]).should == true
51 end 53 end
52 end 54 end
53 - 55 +
  56 +
54 context '#from_xml' do 57 context '#from_xml' do
55 before do 58 before do
56 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read 59 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
57 @app = Factory(:app, :api_key => 'APIKEY') 60 @app = Factory(:app, :api_key => 'APIKEY')
58 Digest::MD5.stub(:hexdigest).and_return('fingerprintdigest') 61 Digest::MD5.stub(:hexdigest).and_return('fingerprintdigest')
59 end 62 end
60 - 63 +
61 it 'finds the correct app' do 64 it 'finds the correct app' do
62 @notice = Notice.from_xml(@xml) 65 @notice = Notice.from_xml(@xml)
63 @notice.err.app.should == @app 66 @notice.err.app.should == @app
64 end 67 end
65 - 68 +
66 it 'finds the correct err for the notice' do 69 it 'finds the correct err for the notice' do
67 - Err.should_receive(:for).with({  
68 - :app => @app, 70 + App.should_receive(:find_by_api_key!).and_return(@app)
  71 + @app.should_receive(:find_or_create_err!).with({
69 :klass => 'HoptoadTestingException', 72 :klass => 'HoptoadTestingException',
70 :component => 'application', 73 :component => 'application',
71 :action => 'verify', 74 :action => 'verify',
@@ -75,70 +78,72 @@ describe Notice do @@ -75,70 +78,72 @@ describe Notice do
75 err.notices.stub(:create!) 78 err.notices.stub(:create!)
76 @notice = Notice.from_xml(@xml) 79 @notice = Notice.from_xml(@xml)
77 end 80 end
78 -  
79 - it 'marks the err as unresolve if it was previously resolved' do  
80 - Err.should_receive(:for).with({  
81 - :app => @app, 81 +
  82 +
  83 + it 'marks the err as unresolved if it was previously resolved' do
  84 + App.should_receive(:find_by_api_key!).and_return(@app)
  85 + @app.should_receive(:find_or_create_err!).with({
82 :klass => 'HoptoadTestingException', 86 :klass => 'HoptoadTestingException',
83 :component => 'application', 87 :component => 'application',
84 :action => 'verify', 88 :action => 'verify',
85 :environment => 'development', 89 :environment => 'development',
86 :fingerprint => 'fingerprintdigest' 90 :fingerprint => 'fingerprintdigest'
87 - }).and_return(err = Factory(:err, :resolved => true)) 91 + }).and_return(err = Factory(:err, :problem => Factory(:problem, :resolved => true)))
88 err.should be_resolved 92 err.should be_resolved
89 @notice = Notice.from_xml(@xml) 93 @notice = Notice.from_xml(@xml)
90 @notice.err.should == err 94 @notice.err.should == err
91 @notice.err.should_not be_resolved 95 @notice.err.should_not be_resolved
92 end 96 end
93 - 97 +
94 it 'should create a new notice' do 98 it 'should create a new notice' do
95 @notice = Notice.from_xml(@xml) 99 @notice = Notice.from_xml(@xml)
96 @notice.should be_persisted 100 @notice.should be_persisted
97 end 101 end
98 - 102 +
99 it 'assigns an err to the notice' do 103 it 'assigns an err to the notice' do
100 @notice = Notice.from_xml(@xml) 104 @notice = Notice.from_xml(@xml)
101 @notice.err.should be_a(Err) 105 @notice.err.should be_a(Err)
102 end 106 end
103 - 107 +
104 it 'captures the err message' do 108 it 'captures the err message' do
105 @notice = Notice.from_xml(@xml) 109 @notice = Notice.from_xml(@xml)
106 @notice.message.should == 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.' 110 @notice.message.should == 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.'
107 end 111 end
108 - 112 +
109 it 'captures the backtrace' do 113 it 'captures the backtrace' do
110 @notice = Notice.from_xml(@xml) 114 @notice = Notice.from_xml(@xml)
111 @notice.backtrace.size.should == 73 115 @notice.backtrace.size.should == 73
112 @notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake' 116 @notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake'
113 end 117 end
114 - 118 +
115 it 'captures the server_environment' do 119 it 'captures the server_environment' do
116 @notice = Notice.from_xml(@xml) 120 @notice = Notice.from_xml(@xml)
117 @notice.server_environment['environment-name'].should == 'development' 121 @notice.server_environment['environment-name'].should == 'development'
118 end 122 end
119 - 123 +
120 it 'captures the request' do 124 it 'captures the request' do
121 @notice = Notice.from_xml(@xml) 125 @notice = Notice.from_xml(@xml)
122 @notice.request['url'].should == 'http://example.org/verify' 126 @notice.request['url'].should == 'http://example.org/verify'
123 @notice.request['params']['controller'].should == 'application' 127 @notice.request['params']['controller'].should == 'application'
124 end 128 end
125 - 129 +
126 it 'captures the notifier' do 130 it 'captures the notifier' do
127 @notice = Notice.from_xml(@xml) 131 @notice = Notice.from_xml(@xml)
128 @notice.notifier['name'].should == 'Hoptoad Notifier' 132 @notice.notifier['name'].should == 'Hoptoad Notifier'
129 end 133 end
130 -  
131 - it "should handle params without 'request' section" do 134 +
  135 + it "should handle params withour 'request' section" do
132 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read 136 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read
133 lambda { Notice.from_xml(@xml) }.should_not raise_error 137 lambda { Notice.from_xml(@xml) }.should_not raise_error
134 end 138 end
135 - 139 +
136 it "should raise ApiVersionError" do 140 it "should raise ApiVersionError" do
137 @xml = Rails.root.join('spec', 'fixtures', 'hoptoad_test_notice_with_wrong_version.xml').read 141 @xml = Rails.root.join('spec', 'fixtures', 'hoptoad_test_notice_with_wrong_version.xml').read
138 expect { Notice.from_xml(@xml) }.to raise_error(Hoptoad::V2::ApiVersionError) 142 expect { Notice.from_xml(@xml) }.to raise_error(Hoptoad::V2::ApiVersionError)
139 end 143 end
140 end 144 end
141 - 145 +
  146 +
142 describe "key sanitization" do 147 describe "key sanitization" do
143 before do 148 before do
144 @hash = { "some.key" => { "$nested.key" => {"$Path" => "/", "some$key" => "key"}}} 149 @hash = { "some.key" => { "$nested.key" => {"$Path" => "/", "some$key" => "key"}}}
@@ -152,42 +157,44 @@ describe Notice do @@ -152,42 +157,44 @@ describe Notice do
152 end 157 end
153 end 158 end
154 end 159 end
155 - 160 +
  161 +
156 describe "user agent" do 162 describe "user agent" do
157 it "should be parsed and human-readable" do 163 it "should be parsed and human-readable" do
158 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'}}) 164 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'}})
159 notice.user_agent.browser.should == 'Chrome' 165 notice.user_agent.browser.should == 'Chrome'
160 notice.user_agent.version.to_s.should =~ /^10\.0/ 166 notice.user_agent.version.to_s.should =~ /^10\.0/
161 end 167 end
162 - 168 +
163 it "should be nil if HTTP_USER_AGENT is blank" do 169 it "should be nil if HTTP_USER_AGENT is blank" do
164 notice = Factory.build(:notice) 170 notice = Factory.build(:notice)
165 notice.user_agent.should == nil 171 notice.user_agent.should == nil
166 end 172 end
167 end 173 end
168 - 174 +
  175 +
169 describe "email notifications (configured individually for each app)" do 176 describe "email notifications (configured individually for each app)" do
170 custom_thresholds = [2, 4, 8, 16, 32, 64] 177 custom_thresholds = [2, 4, 8, 16, 32, 64]
171 - 178 +
172 before do 179 before do
173 Errbit::Config.per_app_email_at_notices = true 180 Errbit::Config.per_app_email_at_notices = true
174 @app = Factory(:app_with_watcher, :email_at_notices => custom_thresholds) 181 @app = Factory(:app_with_watcher, :email_at_notices => custom_thresholds)
175 - @err = Factory(:err, :app => @app) 182 + @problem = Factory(:err, :problem => @app.problems.create!)
176 end 183 end
177 - 184 +
178 after do 185 after do
179 Errbit::Config.per_app_email_at_notices = false 186 Errbit::Config.per_app_email_at_notices = false
180 end 187 end
181 - 188 +
182 custom_thresholds.each do |threshold| 189 custom_thresholds.each do |threshold|
183 it "sends an email notification after #{threshold} notice(s)" do 190 it "sends an email notification after #{threshold} notice(s)" do
184 - @err.notices.stub(:count).and_return(threshold) 191 + @problem.notices.stub(:count).and_return(threshold)
185 Mailer.should_receive(:err_notification). 192 Mailer.should_receive(:err_notification).
186 and_return(mock('email', :deliver => true)) 193 and_return(mock('email', :deliver => true))
187 - Factory(:notice, :err => @err) 194 + Factory(:notice, :err => @problem)
188 end 195 end
189 end 196 end
190 end 197 end
191 - 198 +
  199 +
192 end 200 end
193 -  
spec/models/problem_spec.rb 0 → 100644
@@ -0,0 +1,130 @@ @@ -0,0 +1,130 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Problem do
  4 +
  5 +
  6 + context '#last_notice_at' do
  7 + it "returns the created_at timestamp of the latest notice" do
  8 + err = Factory(:err)
  9 + problem = err.problem
  10 + problem.should_not be_nil
  11 +
  12 + problem.last_notice_at.should be_nil
  13 +
  14 + notice1 = Factory(:notice, :err => err)
  15 + problem.last_notice_at.should == notice1.created_at
  16 +
  17 + notice2 = Factory(:notice, :err => err)
  18 + problem.last_notice_at.should == notice2.created_at
  19 + end
  20 + end
  21 +
  22 +
  23 + context '#message' do
  24 + it "adding a notice caches its message" do
  25 + err = Factory(:err)
  26 + problem = err.problem
  27 + lambda {
  28 + Factory(:notice, :err => err, :message => 'ERR 1')
  29 + }.should change(problem, :message).from(nil).to('ERR 1')
  30 + end
  31 + end
  32 +
  33 +
  34 + context 'being created' do
  35 + context 'when the app has err notifications set to false' do
  36 + it 'should not send an email notification' do
  37 + app = Factory(:app_with_watcher, :notify_on_errs => false)
  38 + Mailer.should_not_receive(:err_notification)
  39 + Factory(:problem, :app => app)
  40 + end
  41 + end
  42 + end
  43 +
  44 +
  45 + context "#resolved?" do
  46 + it "should start out as unresolved" do
  47 + problem = Problem.new
  48 + problem.should_not be_resolved
  49 + problem.should be_unresolved
  50 + end
  51 +
  52 + it "should be able to be resolved" do
  53 + problem = Factory(:problem)
  54 + problem.should_not be_resolved
  55 + problem.resolve!
  56 + problem.reload.should be_resolved
  57 + end
  58 + end
  59 +
  60 +
  61 + context "resolve!" do
  62 + it "marks the problem as resolved" do
  63 + problem = Factory(:problem)
  64 + problem.should_not be_resolved
  65 + problem.resolve!
  66 + problem.should be_resolved
  67 + end
  68 +
  69 + it "should throw an err if it's not successful" do
  70 + problem = Factory(:problem)
  71 + problem.should_not be_resolved
  72 + problem.stub!(:valid?).and_return(false)
  73 + problem.should_not be_valid
  74 + lambda {
  75 + problem.resolve!
  76 + }.should raise_error(Mongoid::Errors::Validations)
  77 + end
  78 + end
  79 +
  80 +
  81 + context "Scopes" do
  82 + context "resolved" do
  83 + it 'only finds resolved Problems' do
  84 + resolved = Factory(:problem, :resolved => true)
  85 + unresolved = Factory(:problem, :resolved => false)
  86 + Problem.resolved.all.should include(resolved)
  87 + Problem.resolved.all.should_not include(unresolved)
  88 + end
  89 + end
  90 +
  91 + context "unresolved" do
  92 + it 'only finds unresolved Problems' do
  93 + resolved = Factory(:problem, :resolved => true)
  94 + unresolved = Factory(:problem, :resolved => false)
  95 + Problem.unresolved.all.should_not include(resolved)
  96 + Problem.unresolved.all.should include(unresolved)
  97 + end
  98 + end
  99 + end
  100 +
  101 +
  102 + context "notice counter cache" do
  103 +
  104 + before do
  105 + @app = Factory(:app)
  106 + @problem = Factory(:problem, :app => @app)
  107 + @err = Factory(:err, :problem => @problem)
  108 + end
  109 +
  110 + it "#notices_count returns 0 by default" do
  111 + @problem.notices_count.should == 0
  112 + end
  113 +
  114 + it "adding a notice increases #notices_count by 1" do
  115 + lambda {
  116 + Factory(:notice, :err => @err, :message => 'ERR 1')
  117 + }.should change(@problem, :notices_count).from(0).to(1)
  118 + end
  119 +
  120 + it "removing a notice decreases #notices_count by 1" do
  121 + notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')
  122 + lambda {
  123 + @err.notices.first.destroy
  124 + @problem.reload
  125 + }.should change(@problem, :notices_count).from(1).to(0)
  126 + end
  127 + end
  128 +
  129 +
  130 +end
spec/views/errs/show.html.haml_spec.rb
@@ -3,23 +3,24 @@ require &#39;spec_helper&#39; @@ -3,23 +3,24 @@ require &#39;spec_helper&#39;
3 describe "errs/show.html.haml" do 3 describe "errs/show.html.haml" do
4 before do 4 before do
5 err = Factory(:err) 5 err = Factory(:err)
  6 + problem = err.problem
6 comment = Factory(:comment) 7 comment = Factory(:comment)
7 - assign :err, err 8 + assign :problem, problem
8 assign :comment, comment 9 assign :comment, comment
9 - assign :app, err.app  
10 - assign :notices, err.notices.ordered.paginate(:page => 1, :per_page => 1) 10 + assign :app, problem.app
  11 + assign :notices, err.notices.paginate(:page => 1, :per_page => 1)
11 assign :notice, err.notices.first 12 assign :notice, err.notices.first
12 end 13 end
13 - 14 +
14 describe "content_for :action_bar" do 15 describe "content_for :action_bar" do
15 - 16 +
16 it "should confirm the 'resolve' link by default" do 17 it "should confirm the 'resolve' link by default" do
17 render 18 render
18 action_bar = String.new(view.instance_variable_get(:@_content_for)[:action_bar]) 19 action_bar = String.new(view.instance_variable_get(:@_content_for)[:action_bar])
19 resolve_link = action_bar.match(/(<a href.*?(class="resolve").*?>)/)[0] 20 resolve_link = action_bar.match(/(<a href.*?(class="resolve").*?>)/)[0]
20 resolve_link.should =~ /data-confirm="Seriously\?"/ 21 resolve_link.should =~ /data-confirm="Seriously\?"/
21 end 22 end
22 - 23 +
23 it "should confirm the 'resolve' link if configuration is unset" do 24 it "should confirm the 'resolve' link if configuration is unset" do
24 Errbit::Config.stub(:confirm_resolve_err).and_return(nil) 25 Errbit::Config.stub(:confirm_resolve_err).and_return(nil)
25 render 26 render
@@ -27,7 +28,7 @@ describe &quot;errs/show.html.haml&quot; do @@ -27,7 +28,7 @@ describe &quot;errs/show.html.haml&quot; do
27 resolve_link = action_bar.match(/(<a href.*?(class="resolve").*?>)/)[0] 28 resolve_link = action_bar.match(/(<a href.*?(class="resolve").*?>)/)[0]
28 resolve_link.should =~ /data-confirm="Seriously\?"/ 29 resolve_link.should =~ /data-confirm="Seriously\?"/
29 end 30 end
30 - 31 +
31 it "should not confirm the 'resolve' link if configured not to" do 32 it "should not confirm the 'resolve' link if configured not to" do
32 Errbit::Config.stub(:confirm_resolve_err).and_return(false) 33 Errbit::Config.stub(:confirm_resolve_err).and_return(false)
33 render 34 render
@@ -35,35 +36,37 @@ describe &quot;errs/show.html.haml&quot; do @@ -35,35 +36,37 @@ describe &quot;errs/show.html.haml&quot; do
35 resolve_link = action_bar.match(/(<a href.*?(class="resolve").*?>)/)[0] 36 resolve_link = action_bar.match(/(<a href.*?(class="resolve").*?>)/)[0]
36 resolve_link.should_not =~ /data-confirm=/ 37 resolve_link.should_not =~ /data-confirm=/
37 end 38 end
38 - 39 +
39 end 40 end
40 - 41 +
41 describe "content_for :comments" do 42 describe "content_for :comments" do
42 it 'should display comments and new comment form when no issue tracker' do 43 it 'should display comments and new comment form when no issue tracker' do
43 - err = Factory(:err_with_comments)  
44 - assign :err, err  
45 - assign :app, err.app 44 + problem = Factory(:problem_with_comments)
  45 + assign :problem, problem
  46 + assign :app, problem.app
46 render 47 render
47 comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments]) 48 comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments])
48 comments_section.should =~ /Test comment/ 49 comments_section.should =~ /Test comment/
49 comments_section.should =~ /Add a comment/ 50 comments_section.should =~ /Add a comment/
50 end 51 end
51 - 52 +
52 context "with issue tracker" do 53 context "with issue tracker" do
53 - def with_issue_tracker(err)  
54 - err.app.issue_tracker = PivotalLabsTracker.new :api_token => "token token token", :project_id => "1234"  
55 - assign :err, err  
56 - assign :app, err.app 54 + def with_issue_tracker(problem)
  55 + problem.app.issue_tracker = PivotalLabsTracker.new :api_token => "token token token", :project_id => "1234"
  56 + assign :problem, problem
  57 + assign :app, problem.app
57 end 58 end
58 - 59 +
59 it 'should not display the comments section' do 60 it 'should not display the comments section' do
60 - with_issue_tracker(Factory(:err)) 61 + problem = Factory(:problem)
  62 + with_issue_tracker(problem)
61 render 63 render
62 view.instance_variable_get(:@_content_for)[:comments].should be_blank 64 view.instance_variable_get(:@_content_for)[:comments].should be_blank
63 end 65 end
64 - 66 +
65 it 'should display existing comments' do 67 it 'should display existing comments' do
66 - with_issue_tracker(Factory(:err_with_comments)) 68 + problem = Factory(:problem_with_comments)
  69 + with_issue_tracker(problem)
67 render 70 render
68 comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments]) 71 comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments])
69 comments_section.should =~ /Test comment/ 72 comments_section.should =~ /Test comment/
@@ -72,4 +75,3 @@ describe &quot;errs/show.html.haml&quot; do @@ -72,4 +75,3 @@ describe &quot;errs/show.html.haml&quot; do
72 end 75 end
73 end 76 end
74 end 77 end
75 -