cohortly 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,13 +4,22 @@ class Cohortly::ReportsController < Cohortly::CohortlyController
4
4
  @metric_search = Cohortly::Metric.new(params[:cohortly_metric])
5
5
  tags = @metric_search.tags.any? ? @metric_search.tags : nil
6
6
  groups = @metric_search.groups
7
- @report_name = Cohortly::Metric.report_table_name(tags, groups, true)
7
+
8
+ @report_name = Cohortly::Metric.report_table_name(tags, groups, true)
8
9
  # run this in background would be better
9
- Cohortly::Metric.weekly_cohort_chart_for_tag(tags, groups)
10
+
11
+ if Cohortly::Metric.respond_to? :delay
12
+ Cohortly::Metric.delay.weekly_cohort_chart_for_tag(tags, groups)
13
+ else
14
+ Cohortly::Metric.weekly_cohort_chart_for_tag(tags, groups)
15
+ end
10
16
  @report = Cohortly::Report.new( tags, groups, true )
17
+
11
18
  respond_to do |format|
12
19
  format.html
13
- format.js { render :json => @report }
20
+ format.js {
21
+ render :json => @report
22
+ }
14
23
  end
15
24
  end
16
25
  end
@@ -12,6 +12,7 @@ module Cohortly
12
12
  timestamps!
13
13
 
14
14
  ensure_index :tags
15
+ ensure_index :user_start_date
15
16
 
16
17
  def self.store!(args)
17
18
  data = args[4]
@@ -34,14 +35,21 @@ module Cohortly
34
35
  collection_name = self.report_table_name(tags, groups, weekly)
35
36
  # incremental map_reduce pattern
36
37
  meta = Cohortly::ReportMeta.find_or_create_by_collection_name(collection_name)
37
- query[:created_at] = { :$gt => meta.last_update_on.utc } if meta.last_update_on
38
+
39
+ if meta.last_update_on
40
+ query[:created_at] = { :$gt => meta.last_update_on.utc }
41
+ out_collection = { :reduce => meta.store_name }
42
+ else
43
+ out_collection = meta.store_name
44
+ end
45
+
46
+ meta.last_update_on = Time.now.utc
47
+ meta.save
38
48
  self.collection.map_reduce(weekly ? self.week_map : self.month_map,
39
49
  self.reduce,
40
- { :out => meta.last_update_on ? { :reduce => collection_name } : collection_name,
50
+ { :out => out_collection,
41
51
  :raw => true,
42
52
  :query => query})
43
- meta.last_update_on = Time.now.utc
44
- meta.save
45
53
  end
46
54
 
47
55
  def self.cohort_chart_for_tag(tags = nil, groups = nil)
@@ -1,9 +1,10 @@
1
1
  module Cohortly
2
2
  class Report
3
3
  # this is the reduced collection
4
- attr_accessor :collection, :weekly, :key_pattern, :groups, :tags
4
+ attr_accessor :collection, :weekly, :key_pattern, :groups, :tags, :report_meta
5
5
  def initialize( tags = nil, groups = nil, weekly = true )
6
6
  self.collection = Cohortly::Metric.report_table_name(tags, groups, weekly)
7
+ self.report_meta = ReportMeta.find_or_create_by_collection_name(self.collection)
7
8
  self.groups = groups
8
9
  self.tags = tags
9
10
  self.weekly = weekly
@@ -11,7 +12,7 @@ module Cohortly
11
12
  end
12
13
 
13
14
  def data
14
- @data ||= (Cohortly::Metric.database[self.collection].find().collect {|x| x}).sort_by {|x| x['_id'] }
15
+ @data ||= (Cohortly::Metric.database[self.report_meta.store_name].find().collect {|x| x}).sort_by {|x| x['_id'] }
15
16
  end
16
17
 
17
18
  def fix_data_lines
@@ -45,8 +46,8 @@ module Cohortly
45
46
  end
46
47
 
47
48
  def user_count_in_cohort(report_key)
48
- params = { :user_start_date => { :$gt => key_to_time(report_key) - self.offset,
49
- :$lt => (key_to_time(report_key))}}
49
+ params = { :user_start_date => { :$gt => key_to_time(report_key),
50
+ :$lt => (key_to_time(report_key) + 1.week)}}
50
51
  params[:tags] = { :$in => groups } if self.groups
51
52
  Cohortly::Metric.collection.distinct(:user_id, params).length
52
53
  end
@@ -56,8 +57,9 @@ module Cohortly
56
57
  start_time = key_to_time(start_key)
57
58
  end_time = key_to_time(end_key)
58
59
  cur_time = start_time
59
- res = []
60
- while(cur_time <= end_time) do
60
+ res = [start_key]
61
+ cur_time += self.offset
62
+ while(cur_time < end_time) do
61
63
  res << time_to_key(cur_time)
62
64
  cur_time += self.offset
63
65
  end
@@ -99,7 +101,12 @@ module Cohortly
99
101
 
100
102
  def as_json(*args)
101
103
  fix_data_lines
102
- { :data => data,
104
+ { :name => report_meta.collection_name,
105
+ :store_name => report_meta.store_name,
106
+ :groups => self.groups,
107
+ :tags => self.tags,
108
+ :weekly => self.weekly,
109
+ :data => data,
103
110
  :base_n => base_n
104
111
  }
105
112
  end
@@ -4,5 +4,14 @@ module Cohortly
4
4
 
5
5
  key :collection_name, String
6
6
  key :last_update_on, Time
7
+
8
+ def store_name
9
+ "cohortly_report_#{self.id}"
10
+ end
11
+
12
+ def run
13
+ args = Metric.report_name_to_args(self.collection_name)
14
+ Cohortly::Metric.cohort_chart(*args)
15
+ end
7
16
  end
8
17
  end
@@ -1,9 +1,14 @@
1
- <div class="groups">
2
- <% Cohortly::TagConfig.all_groups.each do |group| %>
3
- <span class"group">
4
- <%= check_box_tag 'cohortly_metric[groups][]', group,
5
- params[:cohortly_metric] && params[:cohortly_metric][:groups] &&
6
- params[:cohortly_metric][:groups].include?(group) %><label><%= group %></label>
7
- </span>
1
+ <div class="tags">
2
+ <% Cohortly::TagConfig.all_groups.sort.each_slice(3) do |groups| %>
3
+ <div class="tag_group" style="float:left; padding: 10px;">
4
+ <% groups.sort.each do |group| %>
5
+ <div class"tag">
6
+ <%= check_box_tag 'cohortly_metric[groups][]', group,
7
+ params[:cohortly_metric] && params[:cohortly_metric][:groups] &&
8
+ params[:cohortly_metric][:groups].include?(group) %><label><%= group %></label>
9
+ </div>
10
+ <% end %>
11
+ </div>
8
12
  <% end %>
13
+ <div style="clear:both;"></div>
9
14
  </div>
@@ -4,5 +4,5 @@
4
4
  <td><%= metric.created_at.strftime("%m/%d/%Y %H:%M") %></td>
5
5
  <td><%= metric.controller %></td>
6
6
  <td><%= metric.action %></td>
7
- <td><%= metric.tags.join(', ') %></td>
7
+ <td><%= (metric.tags - Cohortly::TagConfig.all_groups).sort.join(', ') %></td>
8
8
  </tr>
@@ -1,7 +1,7 @@
1
1
  <div class="tags">
2
- <% (Cohortly::Metric.collection.distinct(:tags) - Cohortly::TagConfig.all_groups).each_slice(8) do |tags| %>
2
+ <% (Cohortly::Metric.collection.distinct(:tags) - Cohortly::TagConfig.all_groups).sort.each_slice(8) do |tags| %>
3
3
  <div class="tag_group" style="float:left; padding: 10px;">
4
- <% tags.each do |tag| %>
4
+ <% tags.sort.each do |tag| %>
5
5
  <div class"tag">
6
6
  <%= check_box_tag 'cohortly_metric[tags][]', tag,
7
7
  params[:cohortly_metric] && params[:cohortly_metric][:tags] &&
@@ -5,8 +5,8 @@
5
5
  <%= csrf_meta_tag %>
6
6
 
7
7
  <%= javascript_include_tag 'https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js' %>
8
- <%= javascript_include_tag 'http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.1.7/underscore-min.js' %>
9
- <%= javascript_include_tag 'http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.5.3/backbone-min.js' %>
8
+ <%= javascript_include_tag 'vendor/underscore.js' %>
9
+ <%= javascript_include_tag 'vendor/backbone.js' %>
10
10
 
11
11
  <script type="text/javascript">
12
12
  var Cohortly = {};
@@ -24,14 +24,21 @@
24
24
  'click input[type=submit]': 'submitQuery'
25
25
  },
26
26
  submitQuery: function(e) {
27
+ $('.result_table', this.el).html('<h1>Loading .......</h1>');
27
28
  $.get( '/cohortly/reports.js?' + $(this.el).serialize(), _(function(data) {
28
29
  this.model.set(data);
30
+ this.render();
29
31
  }).bind(this), 'json')
30
32
  e.preventDefault();
31
33
  },
32
34
  render: function() {
33
35
  $('.result_table', this.el).html(
34
- ['<table class="one-column-emphasis">',
36
+ ['<h3>',
37
+ this.model.get('groups') ? 'Groups: ' + this.model.get('groups').join(', ') + ' | ': 'Groups: __ | ',
38
+ '',
39
+ this.model.get('tags') ? 'Tags: ' + this.model.get('tags').join(', ') : 'Tags: __ ',
40
+ '</h3>',
41
+ '<table class="one-column-emphasis">',
35
42
  '<colgroup><col class="oce-first"></colgroup>',
36
43
  this.render_header(),
37
44
  this.render_rows(),
@@ -43,7 +50,7 @@
43
50
  '<thead><tr>',
44
51
  '<th>Week</th>',
45
52
  '<th>N</th>',
46
- _.range(1,13).map(function(x){ return '<th>W' + x + '</th>'; }).join(''),
53
+ _.range(1,15).map(function(x){ return '<th>W' + x + '</th>'; }).join(''),
47
54
  '</tr></thead>'
48
55
  ].join('');
49
56
  },
@@ -62,7 +69,7 @@
62
69
  ].join('');
63
70
  },
64
71
  render_cells: function(row, base_n) {
65
- return _(_(row.value).keys()).sortBy(function(x){return x}).slice(0,12).map(function(key) {
72
+ return _(_(row.value).keys()).sortBy(function(x){return x}).slice(0,14).map(function(key) {
66
73
  var num_users = _(row.value[key]).keys().length;
67
74
  var percent = (base_n > 0) ? (num_users / base_n) : 0;
68
75
  return [
@@ -1,3 +1,3 @@
1
1
  module Cohortly
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -1,23 +1,29 @@
1
1
  namespace :cohortly do
2
2
  namespace :run do
3
3
  desc "run the reports for all the tags"
4
- task :reports => :environment do
5
- report_names = Cohortly::Metric.database.collections.select { |c| c.name =~ /^cohortly_report/ }.collect &:name
6
- report_names.each do |name|
7
- args = Cohortly::Metric.report_name_to_args(name)
8
- Cohortly::Metric.cohort_chart(*args)
9
- puts name
10
- end
11
- (Cohortly::Metric.collection.distinct(:tags) - Cohortly::TagConfig.all_groups).each do |tag|
4
+ task :reseed_reports => :environment do
5
+ Cohortly::ReportMeta.delete_all
6
+ Cohortly::Metric.cohort_chart(nil, nil, true)
7
+ puts "main report"
8
+ real_tags = (Cohortly::Metric.collection.distinct(:tags) - Cohortly::TagConfig.all_groups)
9
+ real_tags.each do |tag|
12
10
  Cohortly::Metric.cohort_chart([tag], nil, true)
13
11
  puts "tag: #{tag}"
14
12
  end
15
13
  Cohortly::TagConfig.all_groups.each do |group|
16
- Cohortly::TagConfig.all_tags.each do |tag|
14
+ real_tags.each do |tag|
17
15
  puts "tag: #{tag} group: #{group}"
18
16
  Cohortly::Metric.cohort_chart([tag], [group], true)
19
17
  end
20
18
  end
21
19
  end
20
+
21
+ desc "update all existing reports"
22
+ task :updates => :environment do
23
+ Cohortly::ReportMeta.all.each do |rep|
24
+ puts rep.collection_name
25
+ rep.run
26
+ end
27
+ end
22
28
  end
23
29
  end
@@ -66,11 +66,12 @@ class CohortlyTest < ActiveSupport::TestCase
66
66
  end
67
67
 
68
68
  test "one day of data" do
69
- payload = { :user_start_date => Time.now,
69
+ payload = { :user_start_date => Time.now.utc,
70
70
  :user_id => 5,
71
71
  :user_email => "jordon@example.com",
72
72
  :controller => "session",
73
- :action => "login"
73
+ :action => "login",
74
+ :created_at => Time.now.utc
74
75
  }
75
76
 
76
77
  ActiveSupport::Notifications.instrument("cohortly.event", payload)
@@ -81,7 +82,7 @@ class CohortlyTest < ActiveSupport::TestCase
81
82
 
82
83
  test "weekly" do
83
84
  Cohortly::Metric.delete_all
84
- setup_weekly_data_to_report_on
85
+ weekly_data
85
86
  Cohortly::Metric.weekly_cohort_chart_for_tag
86
87
 
87
88
  report = Cohortly::Report.new()
@@ -95,9 +96,9 @@ class CohortlyTest < ActiveSupport::TestCase
95
96
  assert_equal report.time_to_key(Time.utc(2011,8)), '2011-31'
96
97
  assert_equal report.time_to_key(Time.utc(2011,1)), '2011-00'
97
98
  assert_equal report.start_key, report.time_to_key(Time.now.utc - 15.weeks)
98
- assert_equal report.period_cohorts.length, 16
99
+ assert_equal report.period_cohorts.length, 15
99
100
 
100
- assert_equal report.report_totals, [[16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
101
+ assert_equal report.report_totals, [
101
102
  [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
102
103
  [14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
103
104
  [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
@@ -115,19 +116,25 @@ class CohortlyTest < ActiveSupport::TestCase
115
116
  [1]]
116
117
  end
117
118
 
118
- test "report map reduce" do
119
- setup_data_to_report_on
120
- Cohortly::Metric.cohort_chart_for_tag
121
- assert_equal (Cohortly::Metric.all.collect &:user_id).uniq.length, 136
119
+ test "absolutely correct wwekly data chart" do
120
+ Cohortly::Metric.delete_all
121
+ weekly_data
122
+ Cohortly::Metric.weekly_cohort_chart_for_tag
122
123
 
123
- report = Cohortly::Report.new(nil,nil,false)
124
- assert_equal report.key_to_time('2011-08'), Time.utc(2011, 8)
125
- assert_equal report.time_to_key(Time.utc(2011,8)), '2011-08'
126
- assert_equal report.start_key, (Time.now - 15.months).year.to_s + '-0' + (Time.now - 15.months).month.to_s
127
- assert_equal report.period_cohorts.length, 16
124
+ report = Cohortly::Report.new()
125
+ assert report.weekly
126
+
127
+ time = DateTime.strptime('2011-08', '%Y-%W').utc
128
+ assert_equal report.key_to_time('2011-08'), time
129
+ assert_equal report.key_to_time(report.time_to_key(time)), time
128
130
 
129
- assert_equal report.report_totals, [[16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
130
- [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
131
+
132
+ assert_equal report.time_to_key(Time.utc(2011,8)), '2011-31'
133
+ assert_equal report.time_to_key(Time.utc(2011,1)), '2011-00'
134
+ assert_equal report.start_key, report.time_to_key(Time.now.utc - 15.weeks)
135
+ assert_equal report.period_cohorts.length, 15
136
+
137
+ assert_equal report.report_totals, [[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
131
138
  [14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
132
139
  [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
133
140
  [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
@@ -142,6 +149,35 @@ class CohortlyTest < ActiveSupport::TestCase
142
149
  [3, 2, 1],
143
150
  [2, 1],
144
151
  [1]]
152
+ end
153
+
154
+
155
+ test "report map reduce" do
156
+ setup_data_to_report_on
157
+ Cohortly::Metric.cohort_chart_for_tag
158
+ assert_equal (Cohortly::Metric.all.collect &:user_id).uniq.length, 136
159
+
160
+ report = Cohortly::Report.new(nil,nil,false)
161
+ assert_equal report.key_to_time('2011-08'), Time.utc(2011, 8)
162
+ assert_equal report.time_to_key(Time.utc(2011,8)), '2011-08'
163
+ assert_equal report.start_key, (Time.now - 15.months).year.to_s + '-0' + (Time.now - 15.months).month.to_s
164
+ assert_equal report.period_cohorts.length, 15
165
+
166
+ assert_equal report.report_totals, [[16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2],
167
+ [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2],
168
+ [14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2],
169
+ [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2],
170
+ [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2],
171
+ [11, 10, 9, 8, 7, 6, 5, 4, 3, 2],
172
+ [10, 9, 8, 7, 6, 5, 4, 3, 2],
173
+ [9, 8, 7, 6, 5, 4, 3, 2],
174
+ [8, 7, 6, 5, 4, 3, 2],
175
+ [7, 6, 5, 4, 3, 2],
176
+ [6, 5, 4, 3, 2],
177
+ [5, 4, 3, 2],
178
+ [4, 3, 2],
179
+ [3, 2],
180
+ [2]]
145
181
 
146
182
  end
147
183
 
@@ -149,11 +185,11 @@ class CohortlyTest < ActiveSupport::TestCase
149
185
  setup_weekly_data_to_report_on
150
186
  Cohortly::Metric.weekly_cohort_chart_for_tag()
151
187
  report = Cohortly::Report.new()
152
- start_week = report.start_key
153
- start_week_time = report.key_to_time(report.start_key)
154
- next_week = report.time_to_key(start_week_time + 1.week)
188
+
189
+ start_week_time = report.key_to_time(report.start_key)
190
+ next_week = report.time_to_key(start_week_time + 1.week)
155
191
 
156
- assert_equal report.user_count_in_cohort(start_week), 16
192
+ assert_equal report.user_count_in_cohort(report.start_key), 16
157
193
  assert_equal report.user_count_in_cohort(next_week), 15
158
194
  end
159
195
 
@@ -161,8 +197,8 @@ class CohortlyTest < ActiveSupport::TestCase
161
197
  setup_weekly_data_to_report_on
162
198
  Cohortly::Metric.weekly_cohort_chart_for_tag
163
199
  report = Cohortly::Report.new
164
- line = report.percent_line(report.start_key)
165
- assert_equal line, [16, 100, 94, 88, 81, 75, 69, 63, 56, 50, 44, 38, 31, 25, 19, 13, 6]
200
+ line = report.percent_line(report.time_to_key(report.key_to_time(report.start_key) + 1.week ))
201
+ assert_equal line, [15, 100, 93, 87, 80, 73, 67, 60, 53, 47, 40, 33, 27, 20, 13]
166
202
  end
167
203
 
168
204
  test "javascript day of year" do
@@ -172,19 +208,23 @@ class CohortlyTest < ActiveSupport::TestCase
172
208
  end
173
209
 
174
210
  test "javascript week of year" do
211
+
175
212
  StoredProcedures.store_procedures
176
213
  assert_equal Time.now.utc.strftime('%W').to_i, StoredProcedures.execute(:week_of_year, Time.now.utc)
177
214
  assert_equal (Time.now.utc + 1.week).strftime('%W').to_i, StoredProcedures.execute(:week_of_year, Time.now.utc + 1.week)
215
+ week_end_minus_15 = Time.now.end_of_week - 15.hours
216
+ # these are one second off of eachother bleh :P
178
217
  30.times { |x|
179
- assert_equal (Time.now.utc + x.days).strftime('%W').to_i, StoredProcedures.execute(:week_of_year, Time.now.utc + x.days)
218
+ assert_equal (week_end_minus_15 + x.hours + 1.second).utc.strftime('%W').to_i, StoredProcedures.execute(:week_of_year, week_end_minus_15 + x.hours)
180
219
  }
181
220
  end
182
221
 
183
222
  test "javascript time to week key" do
184
223
  StoredProcedures.store_procedures
185
224
  assert_equal Time.now.utc.strftime('%Y-%W'), StoredProcedures.execute(:week_key, Time.now.utc)
225
+ week_end_minus_15 = Time.now.end_of_week - 15.hours
186
226
  30.times { |x|
187
- assert_equal (Time.now.utc + x.days).strftime('%Y-%W'), StoredProcedures.execute(:week_key, Time.now.utc + x.days)
227
+ assert_equal (week_end_minus_15 + x.days + 1.second).strftime('%Y-%W'), StoredProcedures.execute(:week_key, week_end_minus_15 + x.days)
188
228
  }
189
229
  end
190
230
 
@@ -196,6 +236,35 @@ class CohortlyTest < ActiveSupport::TestCase
196
236
  assert_equal Metric.report_name_to_args("cohortly_report-tags=rand_1:rand_5-monthly"), [['rand_1', 'rand_5'], nil, false]
197
237
  end
198
238
 
239
+ def weekly_data_generate
240
+ data = (1..15).to_a.reverse.collect {|x| (1..x).to_a.reverse}
241
+ user_id_level = 0
242
+ data.collect do |ds|
243
+ start_date = ds.first.weeks.ago;
244
+ user_base = user_id_level * 1000
245
+ ds.each do |d|
246
+ occured_on = d.weeks.ago
247
+ d.times { |user_id| yield user_base + user_id + 1, start_date, occured_on }
248
+ end
249
+ user_id_level += 1
250
+ end
251
+ end
252
+
253
+ def weekly_data
254
+ payload = {
255
+ :controller => "session",
256
+ :action => "login",
257
+ :add_tags => ['login']
258
+ }
259
+ weekly_data_generate do |user_id, started_on, occurred_on|
260
+ payload[:user_id] = user_id
261
+ payload[:username] = "user-#{user_id}"
262
+ payload[:user_start_date] = started_on
263
+ payload[:created_at] = occurred_on
264
+ Cohortly::Metric.store! [nil, nil, nil, nil, payload]
265
+ end
266
+ end
267
+
199
268
  def setup_data_to_report_on
200
269
  payload = { :user_start_date => Time.now,
201
270
  :user_id => 5,