Skip to content

Commit

Permalink
Added country grouping for 'report' command.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Vollmer committed Sep 15, 2009
1 parent b145e03 commit e4de4b3
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 28 deletions.
20 changes: 15 additions & 5 deletions lib/appstore/commands/report.rb
Expand Up @@ -12,6 +12,7 @@ def initialize(c)
c.opt('t', 'to', :desc => 'The ending date, inclusive') do |t|
Date.parse(t)
end
c.flag('g', 'group', :desc => 'Group results by country code')
end

def execute!(opts, args=[], out=$stdout)
Expand All @@ -22,11 +23,20 @@ def execute!(opts, args=[], out=$stdout)
:from => opts.from,
:country => opts.country
}
store.counts(params).each do |x|
out.puts [x.report_date,
x.country,
x.install_count,
x.update_count].join("\t")

if opts.group?
store.country_counts(params).each do |x|
out.puts [x.country,
x.install_count,
x.update_count].join("\t")
end
else
store.counts(params).each do |x|
out.puts [x.report_date,
x.country,
x.install_count,
x.update_count].join("\t")
end
end
out.flush
end
Expand Down
44 changes: 44 additions & 0 deletions lib/appstore/store.rb
Expand Up @@ -60,6 +60,7 @@ def counts(opts={ })

sql << " WHERE " unless clauses.empty?
sql << clauses.join(" AND ") unless params.empty?
sql << " ORDER BY report_date DESC"

@db.execute(sql, *params).map do |row|
OpenStruct.new({
Expand All @@ -70,5 +71,48 @@ def counts(opts={ })
})
end
end

# Get summed counts by country, optionally constrained by dates
# and/or country codes. Available options are:
# <tt>:from</tt>:: The from date, defaults to the beginning
# <tt>:to</tt>:: The end date, defaults to now
# <tt>:country</tt>:: The country code, defaults to <tt>nil</tt>
# which means no country code restriction
def country_counts(opts={ })
unless (leftovers = opts.keys - VALID_COUNT_OPTIONS).empty?
raise "Invalid keys: #{leftovers.join(', ')}"
end

params = []
clauses = []
sql = "SELECT country, SUM(install_count), SUM(update_count) FROM reports"

if opts[:from]
clauses << "report_date >= ?"
params << opts[:from]
end

if opts[:to]
clauses << "report_date <= ?"
params << opts[:to]
end

if opts[:country]
clauses << "country = ?"
params << opts[:country]
end

sql << " WHERE " unless clauses.empty?
sql << clauses.join(" AND ") unless params.empty?
sql << " GROUP BY country, ORDER BY country"

@db.execute(sql, *params).map do |row|
OpenStruct.new({
:country => row[0],
:install_count => row[1].to_i,
:update_count => row[2].to_i
})
end
end
end
end
44 changes: 33 additions & 11 deletions spec/commands/report_spec.rb
Expand Up @@ -13,38 +13,60 @@
and_return(@store)
@io = StringIO.new
@data = [
mock(:report_date => Date.parse('2009/09/09'), :country => 'USD',
mock(:report_date => Date.parse('2009/09/09'), :country => 'US',
:install_count => 1, :update_count => 2),
mock(:report_date => Date.parse('2009/09/09'), :country => 'GBP',
mock(:report_date => Date.parse('2009/09/09'), :country => 'GB',
:install_count => 3, :update_count => 4)
]
end

it 'should requests counts with no options with no qualifiers' do
it 'should request counts with no options with no qualifiers' do
@store.should_receive(:counts).and_return(@data)
clip = stub(:db => '/tmp/store.db', :null_object => true)
clip = stub(:db => '/tmp/store.db', :group? => false, :null_object => true)
@cmd.execute!(clip, [], @io)
@io.string.should == "2009-09-09\tUSD\t1\t2\n" +
"2009-09-09\tGBP\t3\t4\n"
@io.string.should == "2009-09-09\tUS\t1\t2\n" +
"2009-09-09\tGB\t3\t4\n"
end

it 'should output data with other options' do
@store.should_receive(:counts).
with(:to => Date.parse('2009/09/09'),
:from => Date.parse('2009/09/01'),
:country => 'USD').
:country => 'US').
and_return(@data)

clip = stub(:db => '/tmp/store.db',
:group? => false,
:to => Date.parse('2009/09/09'),
:from => Date.parse('2009/09/01'),
:country => 'USD')
:country => 'US')

@cmd.execute!(clip, [], @io)
@io.string.should == "2009-09-09\tUSD\t1\t2\n" +
"2009-09-09\tGBP\t3\t4\n"
@io.string.should == "2009-09-09\tUS\t1\t2\n" +
"2009-09-09\tGB\t3\t4\n"
end
end

describe 'with :group option specified' do
before(:each) do
@store = mock(AppStore::Store)
AppStore::Store.should_receive(:new).
with('/tmp/store.db').
and_return(@store)

@io = StringIO.new
@data = [
mock(:country => 'US', :install_count => 1, :update_count => 2),
mock(:country => 'GB', :install_count => 3, :update_count => 4)
]
end

it 'should request grouped country data' do
@store.should_receive(:country_counts).and_return(@data)
clip = stub(:db => '/tmp/store.db', :group? => true, :null_object => true)
@cmd.execute!(clip, [], @io)
@io.string.should == "US\t1\t2\nGB\t3\t4\n"
end

end

describe 'with invalid execution arguments' do
Expand Down
3 changes: 3 additions & 0 deletions spec/commands_spec.rb
Expand Up @@ -99,6 +99,9 @@
@clip.should_receive(:opt).
with('t', 'to',
:desc => 'The ending date, inclusive')
@clip.should_receive(:flag).
with('g', 'group',
:desc => 'Group results by country code')

AppStore::Commands::Report.new(@clip)
end
Expand Down
90 changes: 78 additions & 12 deletions spec/store_spec.rb
Expand Up @@ -17,32 +17,32 @@

describe "Adding rows" do
it 'should return true for newly imported rows' do
@store.add(@today, 'USD', 1, 2).should be_true
@store.add(@today, 'US', 1, 2).should be_true
end

it 'should return false for duplicate rows' do
@store.add(@today, 'USD', 1, 2).should be_true
@store.add(@today, 'USD', 1, 2).should be_false
@store.add(@today, 'US', 1, 2).should be_true
@store.add(@today, 'US', 1, 2).should be_false
end
end

describe 'counts' do
before(:each) do
@store.add(@today, 'USD', 10, 20)
@store.add(@today - 1, 'USD', 11, 21)
@store.add(@today, 'GBP', 5, 15)
@store.add(@today - 1, 'GBP', 6, 16)
@store.add(@today, 'FRR', 7, 17)
@store.add(@today, 'US', 10, 20)
@store.add(@today - 1, 'US', 11, 21)
@store.add(@today, 'GB', 5, 15)
@store.add(@today - 1, 'GB', 6, 16)
@store.add(@today, 'FR', 7, 17)
end

it 'should return all rows with no constraints' do
@store.counts.size.should == 5
end

it 'should respect the :country constraint' do
@store.counts(:country => 'USD').size.should == 2
@store.counts(:country => 'GBP').size.should == 2
@store.counts(:country => 'FRR').size.should == 1
@store.counts(:country => 'US').size.should == 2
@store.counts(:country => 'GB').size.should == 2
@store.counts(:country => 'FR').size.should == 1
end

it 'should respect the :to constraint' do
Expand All @@ -60,7 +60,7 @@
@store.counts(:from => @today, :to => @today).size.should == 3
@store.counts(:from => @today,
:to => @today,
:country => 'USD').size.should == 1
:country => 'US').size.should == 1
end

it 'should return the correct fields' do
Expand All @@ -70,7 +70,73 @@
record.should be_respond_to(:install_count)
record.should be_respond_to(:update_count)
end
end

describe 'country_counts' do
before(:each) do
@store.add(@today, 'US', 10, 20)
@store.add(@today - 1, 'US', 11, 21)
@store.add(@today, 'GB', 5, 15)
@store.add(@today - 1, 'GB', 6, 16)
@store.add(@today, 'FR', 7, 17)
end

it 'should return all countries with no constraints' do
map = map_results_by_country(@store.country_counts)
map.size.should == 3
map['FR'].install_count.should == 7
map['FR'].update_count.should == 17
map['GB'].install_count.should == 11
map['GB'].update_count.should == 31
map['US'].install_count.should == 21
map['US'].update_count.should == 41
end

it 'should respect the :country constraint' do
map = map_results_by_country(@store.country_counts(:country => 'US'))
map.size.should == 1
map['US'].install_count.should == 21
map['US'].update_count.should == 41
end

it 'should respect the :to constraint' do
r1 = map_results_by_country(@store.country_counts(:to => @today))
r1.size.should == 3
r1.keys.sort.should == %w(FR GB US)
r1['GB'].install_count.should == 11
r1['GB'].update_count.should == 31

r2 = map_results_by_country(@store.country_counts(:to => @today - 1))
r2.size.should == 2
r2.keys.sort.should == %w(GB US)
r2['GB'].install_count.should == 6
r2['GB'].update_count.should == 16
end

it 'should respect the :from constraint' do
r1 = map_results_by_country(@store.country_counts(:from => @today))
r1.size.should == 3
r1.keys.sort.should == %w(FR GB US)
r1['US'].install_count.should == 10
r1['US'].update_count.should == 20

r2 = map_results_by_country(@store.country_counts(:from => @today - 1))
r2.size.should == 3
r2.keys.sort.should == %w(FR GB US)
r2['US'].install_count.should == 21
r2['US'].update_count.should == 41
end

it 'should return the correct fields' do
record = @store.country_counts.first
record.should be_respond_to(:country)
record.should be_respond_to(:install_count)
record.should be_respond_to(:update_count)
end
end

def map_results_by_country(results)
Hash[*results.map { |result| [result.country, result] }.flatten]
end

end

0 comments on commit e4de4b3

Please sign in to comment.