Commit e549876493e453971c28cd048c333f0e56f59cb9

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

add the ability to sort the errs table by its

headers; 

- cache sortable attributes on Problem
- index sortable attributes for performance
app/controllers/apps_controller.rb
... ... @@ -8,10 +8,14 @@ class AppsController < InheritedResources::Base
8 8 respond_to do |format|
9 9 format.html do
10 10 @all_errs = !!params[:all_errs]
  11 + @sort = params[:sort]
  12 + @order = params[:order]
  13 + @sort = "app" unless %w{message last_notice_at last_deploy_at count}.member?(@sort)
  14 + @order = "asc" unless (@order == "desc")
11 15  
12 16 @problems = resource.problems
13 17 @problems = @problems.unresolved unless @all_errs
14   - @problems = @problems.in_env(params[:environment]).ordered.paginate(:page => params[:page], :per_page => current_user.per_page)
  18 + @problems = @problems.in_env(params[:environment]).ordered_by(@sort, @order).paginate(:page => params[:page], :per_page => current_user.per_page)
15 19  
16 20 @selected_problems = params[:problems] || []
17 21 @deploys = @app.deploys.order_by(:created_at.desc).limit(5)
... ...
app/controllers/errs_controller.rb
... ... @@ -9,7 +9,13 @@ class ErrsController < ApplicationController
9 9  
10 10 def index
11 11 app_scope = current_user.admin? ? App.all : current_user.apps
12   - @problems = Problem.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered
  12 +
  13 + @sort = params[:sort]
  14 + @order = params[:order]
  15 + @sort = "app" unless %w{message last_notice_at last_deploy_at count}.member?(@sort)
  16 + @order = "asc" unless (@order == "desc")
  17 +
  18 + @problems = Problem.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered_by(@sort, @order)
13 19 @selected_problems = params[:problems] || []
14 20 respond_to do |format|
15 21 format.html do
... ...
app/helpers/sort_helper.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +# encoding: utf-8
  2 +module SortHelper
  3 +
  4 + def link_for_sort(name, field=nil)
  5 + field ||= name.underscore
  6 + current = (@sort == field)
  7 + order = (current && (@order == "asc")) ? "desc" : "asc"
  8 + url = request.path + "?sort=#{field}&order=#{order}"
  9 + options = {}
  10 + options.merge(:class => "current") if current
  11 + link_to(name, url, options)
  12 + end
  13 +
  14 +end
... ...
app/models/app.rb
... ... @@ -28,6 +28,7 @@ class App
28 28  
29 29 before_validation :generate_api_key, :on => :create
30 30 before_save :normalize_github_url
  31 + after_update :store_cached_attributes_on_problems
31 32  
32 33 validates_presence_of :name, :api_key
33 34 validates_uniqueness_of :name, :allow_blank => true
... ... @@ -148,7 +149,11 @@ class App
148 149 end
149 150  
150 151 protected
151   -
  152 +
  153 + def store_cached_attributes_on_problems
  154 + problems.each(&:cache_app_attributes)
  155 + end
  156 +
152 157 def generate_api_key
153 158 self.api_key ||= ActiveSupport::SecureRandom.hex
154 159 end
... ...
app/models/deploy.rb
... ... @@ -14,6 +14,7 @@ class Deploy
14 14  
15 15 after_create :deliver_notification, :if => :should_notify?
16 16 after_create :resolve_app_errs, :if => :should_resolve_app_errs?
  17 + after_create :store_cached_attributes_on_problems
17 18  
18 19 validates_presence_of :username, :environment
19 20  
... ... @@ -39,5 +40,8 @@ class Deploy
39 40 app.resolve_errs_on_deploy?
40 41 end
41 42  
  43 + def store_cached_attributes_on_problems
  44 + Problem.where(:app_id => app.id).each(&:cache_app_attributes)
  45 + end
  46 +
42 47 end
43   -
... ...
app/models/problem.rb
... ... @@ -7,18 +7,24 @@ class Problem
7 7 include Mongoid::Timestamps
8 8  
9 9 field :last_notice_at, :type => DateTime
  10 + field :last_deploy_at, :type => Time
10 11 field :resolved, :type => Boolean, :default => false
11 12 field :issue_link, :type => String
12 13  
13 14 # Cached fields
  15 + field :app_name, :type => String
14 16 field :notices_count, :type => Integer, :default => 0
15 17 field :message
16 18 field :environment
17 19 field :klass
18 20 field :where
19 21  
20   - index :last_notice_at
21 22 index :app_id
  23 + index :app_name
  24 + index :message
  25 + index :last_notice_at
  26 + index :last_deploy_at
  27 + index :notices_count
22 28  
23 29 belongs_to :app
24 30 has_many :errs, :inverse_of => :problem, :dependent => :destroy
... ... @@ -29,6 +35,10 @@ class Problem
29 35 scope :ordered, order_by(:last_notice_at.desc)
30 36 scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))}
31 37  
  38 + before_create :cache_app_attributes
  39 +
  40 +
  41 +
32 42 def self.in_env(env)
33 43 env.present? ? where(:environment => env) : scoped
34 44 end
... ... @@ -67,6 +77,18 @@ class Problem
67 77 merged_problem
68 78 end
69 79  
  80 + def self.ordered_by(sort, order)
  81 + case sort
  82 + when "app"; order_by(["app_name", order])
  83 + when "message"; order_by(["message", order])
  84 + when "last_notice_at"; order_by(["last_notice_at", order])
  85 + when "last_deploy_at"; order_by(["last_deploy_at", order])
  86 + when "count"; order_by(["notices_count", order])
  87 + else raise("\"#{sort}\" is not a recognized sort")
  88 + end
  89 + end
  90 +
  91 +
70 92 def merged?
71 93 errs.length > 1
72 94 end
... ... @@ -85,9 +107,18 @@ class Problem
85 107  
86 108 def reset_cached_attributes
87 109 update_attribute(:notices_count, notices.count)
  110 + cache_app_attributes
88 111 cache_notice_attributes
89 112 end
90 113  
  114 + def cache_app_attributes
  115 + if app
  116 + self.app_name = app.name
  117 + self.last_deploy_at = app.last_deploy_at
  118 + self.save if persisted?
  119 + end
  120 + end
  121 +
91 122 def cache_notice_attributes(notice=nil)
92 123 notice ||= notices.first
93 124 attrs = {:last_notice_at => notices.max(:created_at)}
... ...
app/views/errs/_table.html.haml
... ... @@ -3,11 +3,11 @@
3 3 %thead
4 4 %tr
5 5 %th
6   - %th App
7   - %th What & Where
8   - %th Latest
9   - %th Deploy
10   - %th Count
  6 + %th= link_for_sort "App"
  7 + %th= link_for_sort "What & Where".html_safe, "message"
  8 + %th= link_for_sort "Latest", "last_notice_at"
  9 + %th= link_for_sort "Deploy", "last_deploy_at"
  10 + %th= link_for_sort "Count"
11 11 %th Resolve
12 12 %tbody
13 13 - errs.each do |problem|
... ... @@ -24,7 +24,7 @@
24 24 = link_to problem.message, app_err_path(problem.app, problem)
25 25 %em= problem.where
26 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'
  27 + %td.deploy= problem.last_deploy_at ? problem.last_deploy_at.to_s(:micro) : 'n/a'
28 28 %td.count= link_to problem.notices.count, app_err_path(problem.app, problem)
29 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 30 - if errs.none?
... ...
spec/models/problem_spec.rb
... ... @@ -131,7 +131,6 @@ describe Problem do
131 131  
132 132  
133 133 context "notice counter cache" do
134   -
135 134 before do
136 135 @app = Factory(:app)
137 136 @problem = Factory(:problem, :app => @app)
... ... @@ -158,4 +157,47 @@ describe Problem do
158 157 end
159 158  
160 159  
  160 + context "#app_name" do
  161 + before do
  162 + @app = Factory(:app)
  163 + end
  164 +
  165 + it "is set when a problem is created" do
  166 + problem = Factory(:problem, :app => @app)
  167 + assert_equal @app.name, problem.app_name
  168 + end
  169 +
  170 + it "is updated when an app is updated" do
  171 + problem = Factory(:problem, :app => @app)
  172 + lambda {
  173 + @app.update_attributes!(:name => "Bar App")
  174 + problem.reload
  175 + }.should change(problem, :app_name).to("Bar App")
  176 + end
  177 + end
  178 +
  179 +
  180 + context "#last_deploy_at" do
  181 + before do
  182 + @app = Factory(:app)
  183 + @last_deploy = 10.days.ago.localtime.round(0)
  184 + deploy = Factory(:deploy, :app => @app, :created_at => @last_deploy)
  185 + end
  186 +
  187 + it "is set when a problem is created" do
  188 + problem = Factory(:problem, :app => @app)
  189 + assert_equal @last_deploy, problem.last_deploy_at
  190 + end
  191 +
  192 + it "is updated when a deploy is created" do
  193 + problem = Factory(:problem, :app => @app)
  194 + next_deploy = 5.minutes.ago.localtime.round(0)
  195 + lambda {
  196 + @deploy = Factory(:deploy, :app => @app, :created_at => next_deploy)
  197 + problem.reload
  198 + }.should change(problem, :last_deploy_at).from(@last_deploy).to(next_deploy)
  199 + end
  200 + end
  201 +
  202 +
161 203 end
... ...