lib/rack/insight/panel.rb in rack-insight-0.5.13 vs lib/rack/insight/panel.rb in rack-insight-0.5.14
- old
+ new
@@ -18,11 +18,15 @@
class << self
include Rack::Insight::Logging
- attr_accessor :template_root
+ attr_accessor :template_root, :is_probed, :has_table, :is_magic
+ @is_probed = false # Once a panel is probed this should be set to true
+ @has_table = true # default to true. Panels without tables override with self.has_table = false
+ @is_magic = false # check this to wrap any functionality targeted at magic panels.
+
def file_index
return @file_index ||= Hash.new do |h,k|
h[k] = []
end
end
@@ -31,14 +35,21 @@
return @panel_exclusion ||= []
end
def from_file(rel_path)
old_rel, Thread::current['rack-panel_file'] = Thread::current['rack-panel_file'], rel_path
- Rack::Insight::Config.config[:panel_load_paths].each do |load_path|
+ load_paths_to_check = Rack::Insight::Config.config[:panel_load_paths].length
+ Rack::Insight::Config.config[:panel_load_paths].each_with_index do |load_path, index|
begin
require File::join(load_path, rel_path)
+ break # once found
rescue LoadError => e
+ # TODO: If probes are defined for this panel, instantiate a magic panel
+ # if self.has_custom_probes?
+ if (index + 1) == load_paths_to_check # You have failed me for the last time!
+ warn "Rack::Insight #{e.class}: Unable to find panel specified as '#{rel_path}'. Looked in the following :panel_load_paths: #{Rack::Insight::Config.config[:panel_load_paths].inspect}."
+ end
end
end
return (file_index[rel_path] - panel_exclusion)
ensure
Thread::current['rack-panel_file'] = old_rel
@@ -89,10 +100,52 @@
#XXX use mappings
@app = Rack::Cascade.new([panel_app, app])
else
@app = app
end
+
+ # User has explicitly declared what classes/methods to probe:
+ # Rack::Insight::Config.configure do |config|
+ # config[:panel_configs][:log] = {:probes => {'Logger' => [:instance, :add] } }
+ # # OR EQUIVALENTLY
+ # config[:panel_configs][:log] = {:probes => ['Logger', :instance, :add] }
+ # end
+ panel_name = self.underscored_name.to_sym
+ if self.has_custom_probes?(panel_name)
+ custom_probes = Rack::Insight::Config.config[:panel_configs][panel_name][:probes]
+ if custom_probes.kind_of?(Hash)
+ probe(self) do
+ custom_probes.each do |klass, method_probes|
+ probe_type = method_probes.shift
+ instrument klass do
+ self.send("#{probe_type}_probe", *method_probes)
+ end
+ end
+ end
+ elsif custom_probes.kind_of?(Array) && custom_probes.length >=3
+ probe(self) do
+ custom_probes.each do |probe|
+ klass = probe.shift
+ probe_type = probe.shift
+ instrument klass do
+ self.send("#{probe_type}_probe", *probe)
+ end
+ end
+ end
+ else
+ raise "Expected Rack::Insight::Config.config[:panel_configs][#{self.as_sym.inspect}][:probes] to be a kind of Hash or an Array with length >= 3, but is a #{Rack::Insight::Config.config[:panel_configs][self.as_sym][:probes].class}"
+ end
+ end
+
+ # Setup a table for the panel unless
+ # 1. self.has_table = false has been set for the Panel class
+ # 2. class instance variable @has_table has been set to false
+ # 3. table_setup has already been called by the sub class' initializer
+ if !has_table?
+ table_setup(self.name)
+ end
+ #puts "Initalization Complete for #{panel_name}\n1. #{self.is_magic?} && 2. #{self.has_table?} && 3. #{self.is_probed?} 4. #{self.has_custom_probes?}"
end
def call(env)
@env = env
logger.debug{ "Before call: #{self.name}" } if verbose(:debug)
@@ -111,32 +164,148 @@
def self.panel_mappings
{}
end
+ def has_table?
+ !!self.class.has_table
+ end
+
+ def is_magic?
+ !!self.class.is_magic
+ end
+
def has_content?
true
end
+ def is_probed?
+ !!self.class.is_probed
+ end
+
+ def has_custom_probes?(panel_name = self.underscored_name.to_sym)
+ #puts "Rack::Insight::Config.config[:panel_configs][#{panel_name.inspect}]: #{Rack::Insight::Config.config[:panel_configs][panel_name].inspect}"
+ Rack::Insight::Config.config[:panel_configs][panel_name].respond_to?(:[]) &&
+ !Rack::Insight::Config.config[:panel_configs][panel_name][:probes].nil?
+ end
+
+ # The name informs the table name, and the panel_configs hash among other things.
+ # Override in subclass panels if you want a custom name
def name
- "Unnamed panel: #{self.class.name}" #for shame
+ self.underscored_name
end
+ # Mostly stolen from Rails' ActiveSupport' underscore method:
+ # See activesupport/lib/active_support/inflector/methods.rb, line 77
+ # HTTPClientPanel => http_client
+ # LogPanel => log
+ # ActiveRecordPanel => active_record
+ def underscored_name(word = self.class.to_s)
+ @underscored_name ||= begin
+ words = word.dup.split('::')
+ word = words.last
+ if word == 'Panel'
+ word = words[-2] # Panel class is Panel... and this won't do.
+ end
+ #word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
+ word.gsub!(/Panel$/,'')
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
+ word.tr!("-", "_")
+ word.downcase!
+ word
+ end
+ end
+
+ def camelized_name(str = self.underscored_name)
+ str.split('_').map {|w| w.capitalize}.join
+ end
+
def heading_for_request(number)
- heading rescue "xxx" #XXX: no panel should need this
+ if !self.has_table?
+ heading
+ else
+ num = count(number)
+ if num == 0
+ heading
+ else
+ "#{self.camelized_name} (#{num})"
+ end
+ end
+ rescue 'XXX' # no panel should need this
end
def content_for_request(number)
- content rescue "" #XXX: no panel should need this
+ logger.info("Rack::Insight is using default content_for_request for #{self.class}") if verbose(:med)
+ if !self.has_table?
+ logger.info("#{self.class} is being used without a table") if verbose(:med)
+ content
+ elsif self.is_probed?
+ html = "<h3>#{self.camelized_name}</h3>"
+ invocations = retrieve(number)
+ if invocations.length > 0 && invocations.first.is_a?(Rack::Insight::Panel::DefaultInvocation)
+ html += '<table><thead><tr>'
+ logger.info("Rack::Insight is using magic content for #{self.class}, which is probed") if verbose(:med)
+ members = invocations.first.members # is a struct, so we have members, not keys
+ members.each do |member|
+ html += '<td>' << member.to_s << '</td>'
+ end
+ html += '</tr></thead><tbody>'
+ invocations.each do |invocation|
+ html += '<tr>'
+ members.each do |member|
+ html += '<td>' << invocation.send(member).inspect << '</td>'
+ end
+ html += '</tr>'
+ end
+ html += '</tbody></table>'
+ else
+ html += '<p>No Data Available</p>'
+ end
+ html
+ else
+ content
+ end
+ rescue 'XXX' #XXX: no panel should need this
end
+ def heading
+ self.camelized_name
+ end
+
+ def content
+ logger.info("Rack::Insight is using default content for #{self.class}") if verbose(:med)
+ html = "<h3>#{self.camelized_name}</h3>"
+ html += '<p>Add a content method to your panel</p>'
+ html
+ end
+
+ # Override in subclasses.
+ # This is to make magic classes work.
+ def after_detect(method_call, timing, args, result)
+ #puts "Default After Detect for #{self.underscored_name}: 1. #{self.is_magic?} && 2. #{self.has_table?} && 3. #{self.is_probed?}"
+ if self.is_magic? && self.has_table? && self.is_probed?
+ store(@env, DefaultInvocation.new(method_call.to_s, timing.duration, args, result))
+ end
+ end
+
def before(env)
end
def after(env, status, headers, body)
end
def render(template)
+ end
+
+ # For Magic Panels (not fully implemented)
+ class DefaultInvocation < Struct.new :method, :time, :args, :result
+ def initialize(*args)
+ @time = human_time(args[1])
+ end
+ def human_time(t)
+ "%.2fms" % (t * 1_000)
+ end
end
end
end