| Added :end_date option. - reportable - Fork of reportable required by WarVox, f… | |
| Log | |
| Files | |
| Refs | |
| README | |
| --- | |
| commit 5dd3e6e1a3c3851b6fe2b7c0d20abc65425ed268 | |
| parent 52fc6a795cb9b10fce03b8283a6d461389871e02 | |
| Author: Myron Marston <[email protected]> | |
| Date: Fri, 3 Apr 2009 23:55:03 +0800 | |
| Added :end_date option. | |
| Signed-off-by: Marco Otte-Witte <[email protected]> | |
| Diffstat: | |
| M lib/kvlr/reports_as_sparkline/repo… | 27 +++++++++++++++++++-------- | |
| M lib/kvlr/reports_as_sparkline/repo… | 46 ++++++++++++++++++++-------… | |
| M lib/kvlr/reports_as_sparkline/repo… | 8 ++++---- | |
| M spec/classes/report_cache_spec.rb | 32 +++++++++++++++++++++++++++--… | |
| M spec/classes/report_spec.rb | 93 +++++++++++++++++++++++------… | |
| M spec/classes/reporting_period_spec… | 6 ++++++ | |
| 6 files changed, 157 insertions(+), 55 deletions(-) | |
| --- | |
| diff --git a/lib/kvlr/reports_as_sparkline/report.rb b/lib/kvlr/reports_as_spar… | |
| @@ -20,6 +20,7 @@ module Kvlr #:nodoc: | |
| # * <tt>:limit</tt> - The number of periods to get (see :grouping) | |
| # * <tt>:conditions</tt> - Conditions like in ActiveRecord::Base#find; o… | |
| # * <tt>:live_data</tt> - Specified whether data for the current reporti… | |
| + # * <tt>:end_date</tt> - When specified, the report will be for the peri… | |
| def initialize(klass, name, options = {}) | |
| ensure_valid_options(options) | |
| @klass = klass | |
| @@ -31,7 +32,8 @@ module Kvlr #:nodoc: | |
| :limit => options[:limit] || 100, | |
| :conditions => options[:conditions] || [], | |
| :grouping => Grouping.new(options[:grouping] || :day), | |
| - :live_data => options[:live_data] || false | |
| + :live_data => options[:live_data] || false, | |
| + :end_date => options[:end_date] | |
| } | |
| @options.merge!(options) | |
| @options.freeze | |
| @@ -44,20 +46,22 @@ module Kvlr #:nodoc: | |
| # * <tt>:conditions</tt> - Conditions like in ActiveRecord::Base#find; o… | |
| # * <tt>:grouping</tt> - The period records are grouped on (:hour, :day,… | |
| # * <tt>:live_data</tt> - Specified whether data for the current reporti… | |
| + # * <tt>:end_date</tt> - When specified, the report will be for the peri… | |
| def run(options = {}) | |
| + options = options.dup | |
| ensure_valid_options(options, :run) | |
| custom_conditions = options.key?(:conditions) | |
| options.reverse_merge!(@options) | |
| options[:grouping] = Grouping.new(options[:grouping]) unless options[:… | |
| - ReportCache.process(self, options, !custom_conditions) do |begin_at| | |
| - read_data(begin_at, options) | |
| + ReportCache.process(self, options, !custom_conditions) do |begin_at, e… | |
| + read_data(begin_at, end_at, options) | |
| end | |
| end | |
| private | |
| - def read_data(begin_at, options) | |
| - conditions = setup_conditions(begin_at, options[:conditions]) | |
| + def read_data(begin_at, end_at, options) | |
| + conditions = setup_conditions(begin_at, end_at, options[:conditions]) | |
| @klass.send(@aggregation, | |
| @value_column, | |
| :conditions => conditions, | |
| @@ -66,7 +70,7 @@ module Kvlr #:nodoc: | |
| ) | |
| end | |
| - def setup_conditions(begin_at, custom_conditions = []) | |
| + def setup_conditions(begin_at, end_at, custom_conditions = []) | |
| conditions = [''] | |
| if custom_conditions.is_a?(Hash) | |
| conditions = [custom_conditions.map do |k, v| | |
| @@ -83,19 +87,26 @@ module Kvlr #:nodoc: | |
| end | |
| conditions[0] += "#{(conditions[0].blank? ? '' : ' AND ') + @date_co… | |
| conditions << begin_at | |
| + | |
| + if end_at | |
| + conditions[0].sub!(/>= \?\z/, 'BETWEEN ? AND ?') | |
| + conditions << end_at | |
| + end | |
| + | |
| + conditions | |
| end | |
| def ensure_valid_options(options, context = :initialize) | |
| case context | |
| when :initialize | |
| options.each_key do |k| | |
| - raise ArgumentError.new("Invalid option #{k}") unless [:limit,… | |
| + raise ArgumentError.new("Invalid option #{k}") unless [:limit,… | |
| end | |
| raise ArgumentError.new("Invalid aggregation #{options[:aggregat… | |
| raise ArgumentError.new('The name of the column holding the valu… | |
| when :run | |
| options.each_key do |k| | |
| - raise ArgumentError.new("Invalid option #{k}") unless [:limit,… | |
| + raise ArgumentError.new("Invalid option #{k}") unless [:limit,… | |
| end | |
| end | |
| raise ArgumentError.new("Invalid grouping #{options[:grouping]}") if… | |
| diff --git a/lib/kvlr/reports_as_sparkline/report_cache.rb b/lib/kvlr/reports_a… | |
| @@ -8,16 +8,23 @@ module Kvlr #:nodoc: | |
| raise ArgumentError.new('A block must be given') unless block_given? | |
| self.transaction do | |
| cached_data = [] | |
| - first_reporting_period = ReportingPeriod.first(options[:grouping], o… | |
| + first_reporting_period = ReportingPeriod.first(options[:grouping], o… | |
| + last_reporting_period = options[:end_date] ? ReportingPeriod.new(opt… | |
| + | |
| if cache | |
| - cached_data = find_cached_data(report, options, first_reporting_pe… | |
| + cached_data = find_cached_data(report, options, first_reporting_pe… | |
| + first_cached_reporting_period = cached_data.empty? ? nil : Reporti… | |
| last_cached_reporting_period = cached_data.empty? ? nil : Reportin… | |
| end | |
| + | |
| + # Get any missing data that comes after our cached data... | |
| new_data = if !options[:live_data] && last_cached_reporting_period =… | |
| [] | |
| else | |
| - yield((last_cached_reporting_period.next rescue first_reporting_pe… | |
| + end_date = options[:live_data] ? nil : last_reporting_period && la… | |
| + yield((last_cached_reporting_period.next rescue first_reporting_pe… | |
| end | |
| + | |
| prepare_result(new_data, cached_data, report, options, cache) | |
| end | |
| end | |
| @@ -27,16 +34,16 @@ module Kvlr #:nodoc: | |
| def self.prepare_result(new_data, cached_data, report, options, cache … | |
| new_data = new_data.map { |data| [ReportingPeriod.from_db_string(opt… | |
| result = cached_data.map { |cached| [cached.reporting_period, cached… | |
| - current_reporting_period = ReportingPeriod.new(options[:grouping]) | |
| - reporting_period = cached_data.empty? ? ReportingPeriod.first(option… | |
| - while reporting_period < current_reporting_period | |
| + last_reporting_period = ReportingPeriod.new(options[:grouping], opti… | |
| + reporting_period = cached_data.empty? ? ReportingPeriod.first(option… | |
| + while reporting_period < last_reporting_period | |
| cached = build_cached_data(report, options[:grouping], reporting_p… | |
| cached.save! if cache | |
| result << [reporting_period.date_time, cached.value] | |
| reporting_period = reporting_period.next | |
| end | |
| if options[:live_data] | |
| - result << [current_reporting_period.date_time, find_value(new_data… | |
| + result << [last_reporting_period.date_time, find_value(new_data, l… | |
| end | |
| result | |
| end | |
| @@ -57,17 +64,24 @@ module Kvlr #:nodoc: | |
| ) | |
| end | |
| - def self.find_cached_data(report, options, first_reporting_period) | |
| + def self.find_cached_data(report, options, first_reporting_period, las… | |
| + conditions = [ | |
| + 'model_name = ? AND report_name = ? AND grouping = ? AND aggregati… | |
| + report.klass.to_s, | |
| + report.name.to_s, | |
| + options[:grouping].identifier.to_s, | |
| + report.aggregation.to_s, | |
| + first_reporting_period.date_time | |
| + ] | |
| + | |
| + if last_reporting_period | |
| + conditions.first.sub!(/>= \?\z/, 'BETWEEN ? AND ?') | |
| + conditions << last_reporting_period.date_time | |
| + end | |
| + | |
| self.find( | |
| :all, | |
| - :conditions => [ | |
| - 'model_name = ? AND report_name = ? AND grouping = ? AND aggrega… | |
| - report.klass.to_s, | |
| - report.name.to_s, | |
| - options[:grouping].identifier.to_s, | |
| - report.aggregation.to_s, | |
| - first_reporting_period.date_time | |
| - ], | |
| + :conditions => conditions, | |
| :limit => options[:limit], | |
| :order => 'reporting_period ASC' | |
| ) | |
| diff --git a/lib/kvlr/reports_as_sparkline/reporting_period.rb b/lib/kvlr/repor… | |
| @@ -10,9 +10,9 @@ module Kvlr #:nodoc: | |
| # ==== Parameters | |
| # * <tt>grouping</tt> - The Kvlr::ReportsAsSparkline::Grouping of the re… | |
| # * <tt>date_time</tt> - The DateTime that reporting period is created f… | |
| - def initialize(grouping, date_time = DateTime.now) | |
| + def initialize(grouping, date_time = nil) | |
| @grouping = grouping | |
| - @date_time = parse_date_time(date_time) | |
| + @date_time = parse_date_time(date_time || DateTime.now) | |
| end | |
| # Returns the first reporting period for a grouping and a limit; e.g. th… | |
| @@ -20,8 +20,8 @@ module Kvlr #:nodoc: | |
| # ==== Parameters | |
| # * <tt>grouping</tt> - The Kvlr::ReportsAsSparkline::Grouping of the re… | |
| # * <tt>limit</tt> - The number of reporting periods until the first one | |
| - def self.first(grouping, limit) | |
| - self.new(grouping, DateTime.now).offset(-limit) | |
| + def self.first(grouping, limit, end_date = nil) | |
| + self.new(grouping, end_date).offset(-limit) | |
| end | |
| def self.from_db_string(grouping, db_string) #:nodoc: | |
| diff --git a/spec/classes/report_cache_spec.rb b/spec/classes/report_cache_spec… | |
| @@ -46,8 +46,9 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| cached.stub!(:reporting_period).and_return(reporting_period.date_time) | |
| Kvlr::ReportsAsSparkline::ReportCache.stub!(:find).and_return([cached]) | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, @options) do |b… | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, @options) do |b… | |
| begin_at.should == reporting_period.next.date_time | |
| + end_at.should == nil | |
| [] | |
| end | |
| end | |
| @@ -80,8 +81,9 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| cached.stub!(:reporting_period).and_return(reporting_period.date_time) | |
| Kvlr::ReportsAsSparkline::ReportCache.stub!(:find).and_return([cached]) | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, @report.options… | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, @report.options… | |
| begin_at.should == reporting_period.next.date_time | |
| + end_at.should == nil | |
| [] | |
| end | |
| end | |
| @@ -106,6 +108,26 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| Kvlr::ReportsAsSparkline::ReportCache.process(@report, @report.options) … | |
| end | |
| + it 'should utilize the end_date in the conditions' do | |
| + end_date = Time.now | |
| + Kvlr::ReportsAsSparkline::ReportCache.should_receive(:find).once.with( | |
| + :all, | |
| + :conditions => [ | |
| + 'model_name = ? AND report_name = ? AND grouping = ? AND aggregation… | |
| + @report.klass.to_s, | |
| + @report.name.to_s, | |
| + @report.options[:grouping].identifier.to_s, | |
| + @report.aggregation.to_s, | |
| + Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.options[:gro… | |
| + Kvlr::ReportsAsSparkline::ReportingPeriod.new(@report.options[:group… | |
| + ], | |
| + :limit => 10, | |
| + :order => 'reporting_period ASC' | |
| + ) | |
| + | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, @report.options.m… | |
| + end | |
| + | |
| it "should read existing data from the cache for the correct grouping if o… | |
| grouping = Kvlr::ReportsAsSparkline::Grouping.new(:month) | |
| Kvlr::ReportsAsSparkline::ReportCache.should_receive(:find).once.with( | |
| @@ -135,8 +157,9 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| end | |
| it 'should yield the first reporting period if the cache is empty' do | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, @report.options) … | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, @report.options) … | |
| begin_at.should == Kvlr::ReportsAsSparkline::ReportingPeriod.first(@re… | |
| + end_at.should == nil | |
| [] | |
| end | |
| end | |
| @@ -150,8 +173,9 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| end | |
| it 'should yield the first reporting period' do | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, @report.options… | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, @report.options… | |
| begin_at.should == Kvlr::ReportsAsSparkline::ReportingPeriod.first(@… | |
| + end_at.should == nil | |
| [] | |
| end | |
| end | |
| diff --git a/spec/classes/report_spec.rb b/spec/classes/report_spec.rb | |
| @@ -3,6 +3,8 @@ require File.join(File.dirname(__FILE__), '..', 'spec_helper') | |
| describe Kvlr::ReportsAsSparkline::Report do | |
| before do | |
| + @now = DateTime.now | |
| + DateTime.stub!(:now).and_return(@now) | |
| @report = Kvlr::ReportsAsSparkline::Report.new(User, :registrations) | |
| end | |
| @@ -19,7 +21,7 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| it 'should process the data with the report cache' do | |
| Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with( | |
| @report, | |
| - { :limit => 100, :grouping => @report.options[:grouping], :conditions … | |
| + { :limit => 100, :grouping => @report.options[:grouping], :conditions … | |
| true | |
| ) | |
| @@ -29,7 +31,7 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| it 'should process the data with the report cache and specify cache = fals… | |
| Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with( | |
| @report, | |
| - { :limit => 100, :grouping => @report.options[:grouping], :conditions … | |
| + { :limit => 100, :grouping => @report.options[:grouping], :conditions … | |
| false | |
| ) | |
| @@ -47,7 +49,7 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| Kvlr::ReportsAsSparkline::Grouping.should_receive(:new).once.with(:month… | |
| Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with( | |
| @report, | |
| - { :limit => 100, :grouping => grouping, :conditions => [], :live_data … | |
| + { :limit => 100, :grouping => grouping, :conditions => [], :live_data … | |
| true | |
| ) | |
| @@ -66,6 +68,44 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| @report.run.length.should == 11 | |
| end | |
| + describe "a month report with a limit of 2" do | |
| + before(:all) do | |
| + User.create!(:login => 'test 1', :created_at => Time.now, :p… | |
| + User.create!(:login => 'test 2', :created_at => Time.now - 1.month, :p… | |
| + User.create!(:login => 'test 3', :created_at => Time.now - 3.month, :p… | |
| + User.create!(:login => 'test 4', :created_at => Time.now - 3.month, :p… | |
| + | |
| + @report2 = Kvlr::ReportsAsSparkline::Report.new(User, :registrations, | |
| + :grouping => :month, | |
| + :limit => 2 | |
| + ) | |
| + | |
| + @one_month_ago = Date.new(DateTime.now.year, DateTime.now.month, 1)… | |
| + @two_months_ago = Date.new(DateTime.now.year, DateTime.now.month, 1)… | |
| + @three_months_ago = Date.new(DateTime.now.year, DateTime.now.month, 1)… | |
| + end | |
| + | |
| + it "should return data for the last two months when there is no end date… | |
| + @report2.run.should == [[@two_months_ago, 0.0], [@one_month_ago, 1.0]] | |
| + end | |
| + | |
| + it "should return data for two months prior to the end date" do | |
| + @report2.run(:end_date => 1.month.ago).should == [[@three_months_ago, … | |
| + end | |
| + | |
| + it "should return data for the two months prior to the end date, and als… | |
| + @report2.run(:end_date => 1.month.ago, :live_data => true).should == [… | |
| + end | |
| + | |
| + it "should return data for the two months prior to the end date, and als… | |
| + # run it once to get from the database... | |
| + @report2.run(:end_date => 1.month.ago, :live_data => true) | |
| + | |
| + # and then again from the cache... | |
| + @report2.run(:end_date => 1.month.ago, :live_data => true).should == [… | |
| + end | |
| + end | |
| + | |
| for grouping in [:hour, :day, :week, :month] do | |
| describe "for grouping #{grouping.to_s}" do | |
| @@ -316,13 +356,13 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| @report = Kvlr::ReportsAsSparkline::Report.new(User, :registrations, :ag… | |
| User.should_receive(:count).once.and_return([]) | |
| - @report.send(:read_data, Time.now, { :grouping => @report.options[:group… | |
| + @report.send(:read_data, Time.now, 5.days.from_now, { :grouping => @repo… | |
| end | |
| it 'should setup the conditions' do | |
| @report.should_receive(:setup_conditions).once.and_return([]) | |
| - @report.send(:read_data, Time.now, { :grouping => @report.options[:group… | |
| + @report.send(:read_data, Time.now, 5.days.from_now, { :grouping => @repo… | |
| end | |
| end | |
| @@ -331,53 +371,60 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| before do | |
| @begin_at = Time.now | |
| + @end_at = 5.days.from_now | |
| + end | |
| + | |
| + it 'should return conditions for date_column BETWEEN begin_at and end_at o… | |
| + @report.send(:setup_conditions, @begin_at, @end_at).should == ['created_… | |
| end | |
| - it 'should return conditions for date_column >= begin_at only when no cust… | |
| - @report.send(:setup_conditions, @begin_at).should == ['created_at >= ?',… | |
| + it 'should return conditions for date_column >= begin_at when no custom co… | |
| + @report.send(:setup_conditions, @begin_at, nil).should == ['created_at >… | |
| end | |
| - it 'should return conditions for date_column >= begin_at only when an empt… | |
| - @report.send(:setup_conditions, @begin_at, {}).should == ['created_at >=… | |
| + it 'should return conditions for date_column BETWEEN begin_at and end_date… | |
| + @report.send(:setup_conditions, @begin_at, @end_at, {}).should == ['crea… | |
| end | |
| - it 'should return conditions for date_column >= begin_at only when an empt… | |
| - @report.send(:setup_conditions, @begin_at, []).should == ['created_at >=… | |
| + it 'should return conditions for date_column BETWEEN begin_at and end_date… | |
| + @report.send(:setup_conditions, @begin_at, @end_at, []).should == ['crea… | |
| end | |
| it 'should correctly include custom conditions if they are specified as a … | |
| custom_conditions = { :first_name => 'first name', :last_name => 'last n… | |
| - conditions = @report.send(:setup_conditions, @begin_at, custom_condition… | |
| + conditions = @report.send(:setup_conditions, @begin_at, @end_at, custom_… | |
| #cannot check for equality of complete conditions array since hashes are… | |
| conditions[0].should include('first_name = ?') | |
| conditions[0].should include('last_name = ?') | |
| - conditions[0].should include('created_at >= ?') | |
| + conditions[0].should include('created_at BETWEEN ? AND ?') | |
| conditions.should include('first name') | |
| conditions.should include('last name') | |
| conditions.should include(@begin_at) | |
| + conditions.should include(@end_at) | |
| end | |
| it 'should correctly translate { :column => nil }' do | |
| - @report.send(:setup_conditions, @begin_at, { :column => nil }).should ==… | |
| + @report.send(:setup_conditions, @begin_at, @end_at, { :column => nil }).… | |
| end | |
| it 'should correctly translate { :column => [1, 2] }' do | |
| - @report.send(:setup_conditions, @begin_at, { :column => [1, 2] }).should… | |
| + @report.send(:setup_conditions, @begin_at, @end_at, { :column => [1, 2] … | |
| end | |
| it 'should correctly translate { :column => (1..3) }' do | |
| - @report.send(:setup_conditions, @begin_at, { :column => (1..3) }).should… | |
| + @report.send(:setup_conditions, @begin_at, @end_at, { :column => (1..3) … | |
| end | |
| it 'should correctly include custom conditions if they are specified as an… | |
| custom_conditions = ['first_name = ? AND last_name = ?', 'first name', '… | |
| - @report.send(:setup_conditions, @begin_at, custom_conditions).should == [ | |
| - 'first_name = ? AND last_name = ? AND created_at >= ?', | |
| + @report.send(:setup_conditions, @begin_at, @end_at, custom_conditions).s… | |
| + 'first_name = ? AND last_name = ? AND created_at BETWEEN ? AND ?', | |
| 'first name', | |
| 'last name', | |
| - @begin_at | |
| + @begin_at, | |
| + @end_at | |
| ] | |
| end | |
| @@ -415,11 +462,11 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| }) | |
| }.should_not raise_error(ArgumentError) | |
| end | |
| - | |
| + | |
| it 'should raise an error if an unsupported option is specified' do | |
| lambda { @report.send(:ensure_valid_options, { :invalid => :option }) … | |
| end | |
| - | |
| + | |
| it 'should raise an error if an invalid aggregation is specified' do | |
| lambda { @report.send(:ensure_valid_options, { :aggregation => :invali… | |
| end | |
| @@ -436,13 +483,13 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| lambda { @report.send(:ensure_valid_options, { :limit => 100, :conditi… | |
| }.should_not raise_error(ArgumentError) | |
| end | |
| - | |
| + | |
| it 'should raise an error if an unsupported option is specified' do | |
| lambda { @report.send(:ensure_valid_options, { :aggregation => :sum },… | |
| end | |
| end | |
| - | |
| + | |
| end | |
| end | |
| diff --git a/spec/classes/reporting_period_spec.rb b/spec/classes/reporting_per… | |
| @@ -232,6 +232,12 @@ describe Kvlr::ReportsAsSparkline::ReportingPeriod do | |
| reporting_period.date_time.should == DateTime.new(2008, 12, 8) #the mond… | |
| end | |
| + it 'should return a reporting period that is offset from the end date by t… | |
| + date = DateTime.new(2009, 3, 31) | |
| + reporting_period = Kvlr::ReportsAsSparkline::ReportingPeriod.first(Kvlr:… | |
| + reporting_period.date_time.should == DateTime.new(2009, 3, 28) | |
| + end | |
| + | |
| end | |
| end |