cohortly 0.0.9.1 → 0.0.92
Sign up to get free protection for your applications and to get access to all the features.
- data/app/controllers/cohortly/reports_controller.rb +45 -12
- data/app/models/cohortly/cohorts.rb +8 -0
- data/app/models/cohortly/group_cohort.rb +9 -0
- data/app/models/cohortly/metric.rb +1 -1
- data/app/models/cohortly/period_cohort.rb +24 -0
- data/app/models/cohortly/tag_report.rb +126 -0
- data/app/models/cohortly/user_cohort.rb +18 -0
- data/app/views/cohortly/metrics/_groups.html.erb +1 -1
- data/app/views/cohortly/metrics/_groups_intersect.html.erb +14 -0
- data/app/views/cohortly/metrics/_tags.html.erb +1 -1
- data/app/views/cohortly/reports/index.html.erb +3 -1
- data/app/views/layouts/cohortly/application.html.erb +15 -13
- data/lib/cohortly/engine.rb +4 -2
- data/lib/cohortly/version.rb +1 -1
- data/lib/tasks/run_reports.rake +43 -16
- data/test/dummy/app/models/cohortly/cohorts.rb +15 -0
- data/test/dummy/log/development.log +157 -0
- metadata +10 -6
- data/app/models/cohortly/report.rb +0 -118
- data/app/models/cohortly/report_meta.rb +0 -17
- data/test/dummy/tmp/pids/server.pid +0 -1
@@ -1,25 +1,58 @@
|
|
1
1
|
class Cohortly::ReportsController < Cohortly::CohortlyController
|
2
2
|
def index
|
3
3
|
Cohortly::Metric.send :attr_accessor, :groups
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
Cohortly::Metric.send :attr_accessor, :groups_intersect
|
5
|
+
@metric_search = Cohortly::Metric.new(params[:cohortly_metric])
|
6
|
+
json_res = { }
|
7
|
+
|
8
|
+
if params[:cohortly_metric]
|
9
|
+
tags = @metric_search.tags.any? ? @metric_search.tags : ['upload']
|
10
|
+
groups = @metric_search.groups
|
11
|
+
groups_intersect = @metric_search.groups_intersect
|
12
|
+
|
13
|
+
reports = Cohortly::TagReport.where(:tags => tags).all
|
14
|
+
|
15
|
+
@tag_report = reports.inject(Cohortly::TagReport.new) { |accum, tag_report| accum.merge(tag_report) }
|
16
|
+
|
17
|
+
user_base_func = lambda { |user_ids| user_ids.length }
|
18
|
+
|
19
|
+
if groups || groups_intersect
|
20
|
+
users_constraint = false
|
21
|
+
if groups && groups.any?
|
22
|
+
users_constraint = Cohortly::UserCohort.union(Cohortly::GroupCohort.where(:name => groups).all)
|
23
|
+
end
|
24
|
+
if groups_intersect && groups_intersect.any?
|
25
|
+
group_intersect_users = Cohortly::UserCohort.intersect(Cohortly::GroupCohort.where(:name => groups_intersect).all)
|
26
|
+
users_constraint = users_constraint ? users_constraint & group_intersect_users : group_intersect_users
|
27
|
+
end
|
28
|
+
if users_constraint
|
29
|
+
user_base_func = lambda { |user_ids| (users_constraint & user_ids).length }
|
30
|
+
@tag_report.intersect(users_constraint.map(&:to_s))
|
31
|
+
end
|
32
|
+
end
|
7
33
|
|
8
|
-
|
9
|
-
|
34
|
+
@base_n = Cohortly::PeriodCohort.all.inject({ }) do |base_n, per_coh|
|
35
|
+
base_n.merge( per_coh.name => {
|
36
|
+
:count => user_base_func.call(per_coh.user_ids),
|
37
|
+
:pretty_date => DateTime.strptime( per_coh.name, '%Y-%W').beginning_of_week.strftime('%m-%d-%Y') } )
|
38
|
+
end
|
10
39
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
40
|
+
json_res = {
|
41
|
+
:groups => groups,
|
42
|
+
:groups_intersect => groups_intersect,
|
43
|
+
:tags => tags,
|
44
|
+
:weekly => true,
|
45
|
+
:data => @tag_report.data_without_empty_rows,
|
46
|
+
:base_n => @base_n
|
47
|
+
}
|
15
48
|
end
|
16
|
-
|
17
|
-
|
49
|
+
|
18
50
|
respond_to do |format|
|
19
51
|
format.html
|
20
52
|
format.js {
|
21
|
-
render :json =>
|
53
|
+
render :json => json_res
|
22
54
|
}
|
23
55
|
end
|
24
56
|
end
|
57
|
+
|
25
58
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cohortly
|
2
|
+
class PeriodCohort < Cohortly::UserCohort
|
3
|
+
key :start_time, Time
|
4
|
+
key :weekly, Boolean
|
5
|
+
|
6
|
+
def end_time
|
7
|
+
self.start_time + period
|
8
|
+
end
|
9
|
+
|
10
|
+
def period
|
11
|
+
self.weekly ? 1.week : 1.month
|
12
|
+
end
|
13
|
+
|
14
|
+
def key_pattern
|
15
|
+
self.weekly ? "%Y-%W" : "%Y-%m"
|
16
|
+
end
|
17
|
+
|
18
|
+
def store!
|
19
|
+
self.user_ids = Cohortly::Cohorts.range(self.start_time..self.end_time)
|
20
|
+
self.name = (self.start_time + 3.days).strftime(key_pattern)
|
21
|
+
self.save
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Cohortly
|
2
|
+
class TagReport
|
3
|
+
include MongoMapper::Document
|
4
|
+
|
5
|
+
key :collection_name, String
|
6
|
+
key :last_update_on, Time
|
7
|
+
key :data, Hash
|
8
|
+
key :tags, Array
|
9
|
+
|
10
|
+
def run
|
11
|
+
if self.last_update_on.nil?
|
12
|
+
self.recalc_table
|
13
|
+
else
|
14
|
+
self.update_table
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def tag_query
|
19
|
+
self.tags.any? ? { :tags => self.tags.first } : { }
|
20
|
+
end
|
21
|
+
|
22
|
+
def cell_query(cohort_range, cell_range)
|
23
|
+
{ :created_at => {
|
24
|
+
:$gt => cell_range.begin,
|
25
|
+
:$lt => cell_range.end},
|
26
|
+
:user_start_date => {
|
27
|
+
:$gt => cohort_range.begin,
|
28
|
+
:$lt => cohort_range.end } }.tap { |x|
|
29
|
+
self.tags ? x.merge!( tag_query ) : x
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def start_time
|
34
|
+
Cohortly::Metric.where(tag_query).sort(:user_start_date).limit(1).first.user_start_date.utc.beginning_of_week
|
35
|
+
end
|
36
|
+
|
37
|
+
def cohort_iter(starting_time)
|
38
|
+
cohort_time = starting_time
|
39
|
+
while cohort_time <= Time.now
|
40
|
+
yield cohort_time..(cohort_time + 1.week)
|
41
|
+
cohort_time += 1.week
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def cell_iter(cell_starting_time)
|
46
|
+
cohort_time = start_time
|
47
|
+
while cohort_time <= Time.now
|
48
|
+
cell_time = [cohort_time, cell_starting_time].max
|
49
|
+
while cell_time <= Time.now
|
50
|
+
yield cohort_time..(cohort_time + 1.week), cell_time..(cell_time + 1.week)
|
51
|
+
cell_time += 1.week
|
52
|
+
end
|
53
|
+
cohort_time += 1.week
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def recalc_table
|
58
|
+
self.data = { }
|
59
|
+
self.last_update_on = Time.now
|
60
|
+
self.cell_iter(self.start_time) { |cohort_range, cell_range| self.store_cell(cohort_range, cell_range)}
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_table
|
64
|
+
starting_time = self.last_update_on.utc.beginning_of_week
|
65
|
+
self.last_update_on = Time.now
|
66
|
+
self.cell_iter(starting_time)
|
67
|
+
end
|
68
|
+
|
69
|
+
def store_cell(cohort_range, cell_range)
|
70
|
+
cohort_key = cohort_range.begin.strftime('%Y-%W')
|
71
|
+
cell_key = cell_range.begin.strftime('%Y-%W')
|
72
|
+
p cohort_key + " " + cell_key
|
73
|
+
self.data[cohort_key] ||= { }
|
74
|
+
self.data[cohort_key][cell_key] ||= { }
|
75
|
+
Cohortly::Metric.collection.distinct( :user_id, cell_query(cohort_range, cell_range) ).each do |user_id|
|
76
|
+
self.data[cohort_key][cell_key][user_id.to_s] = 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def merge(tag_report)
|
81
|
+
TagReport::Product.new(:tag_report => self, :tags => self.tags, :data => self.data).merge(tag_report)
|
82
|
+
end
|
83
|
+
|
84
|
+
class Product
|
85
|
+
attr_accessor :tag_report, :tags, :data
|
86
|
+
def initialize(options = { })
|
87
|
+
self.tags = options[:tags]
|
88
|
+
self.tag_report = options[:tag_report]
|
89
|
+
self.data = options[:data]
|
90
|
+
end
|
91
|
+
|
92
|
+
def merge(tag_report)
|
93
|
+
TagReport::Product.new(:tag_report => self.tag_report,
|
94
|
+
:tags => self.tags | tag_report.tags,
|
95
|
+
:data => self.deep_merge(self.data, tag_report.data))
|
96
|
+
end
|
97
|
+
|
98
|
+
def deep_merge(data1, data2)
|
99
|
+
(data1.keys + data2.keys).uniq.inject({}) do |accum, key|
|
100
|
+
if (data1[key] || data2[key]).is_a?(Hash)
|
101
|
+
accum[key] = deep_merge(data1[key] || { }, data2[key] || { })
|
102
|
+
else
|
103
|
+
accum[key] = data1[key] || data2[key]
|
104
|
+
end
|
105
|
+
accum
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# user_ids need to be strings
|
110
|
+
def intersect(user_ids)
|
111
|
+
self.tag_report.cell_iter(self.tag_report.start_time) do |cohort_range, cell_range|
|
112
|
+
cohort_key = cohort_range.begin.strftime('%Y-%W')
|
113
|
+
cell_key = cell_range.begin.strftime('%Y-%W')
|
114
|
+
cell = self.data[cohort_key][cell_key]
|
115
|
+
intersected_ids = cell.keys & user_ids
|
116
|
+
self.data[cohort_key][cell_key] = intersected_ids.inject({ }) { |accum, user_id| accum.merge!(user_id => 1); accum }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def data_without_empty_rows
|
121
|
+
self.data.keys.sort.inject({ }) { |new_data, key| puts ; self.data[key].values.collect(&:length).sum > 0 ? new_data.merge(key => self.data[key]) : new_data }
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Cohortly
|
2
|
+
class UserCohort
|
3
|
+
include MongoMapper::Document
|
4
|
+
key :user_ids, Array
|
5
|
+
key :name, String
|
6
|
+
key :_type, String
|
7
|
+
timestamps!
|
8
|
+
|
9
|
+
def self.intersect(cohorts)
|
10
|
+
cohorts.inject(cohorts.first.user_ids) { |user_ids, cohort| user_ids & cohort.user_ids }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.union(cohorts)
|
14
|
+
cohorts.inject([]) { |user_ids, cohort| user_ids | cohort.user_ids }
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="tags">
|
2
|
-
<% Cohortly::
|
2
|
+
<% Cohortly::Cohorts.group_names.sort.each_slice(3) do |groups| %>
|
3
3
|
<div class="tag_group" style="float:left; padding: 10px;">
|
4
4
|
<% groups.sort.each do |group| %>
|
5
5
|
<div class"tag">
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<div class="tags">
|
2
|
+
<% Cohortly::Cohorts.group_names.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_intersect][]', group,
|
7
|
+
params[:cohortly_metric] && params[:cohortly_metric][:groups_intersect] &&
|
8
|
+
params[:cohortly_metric][:groups_intersect].include?(group) %><label><%= group %></label>
|
9
|
+
</div>
|
10
|
+
<% end %>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
<div style="clear:both;"></div>
|
14
|
+
</div>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="tags">
|
2
|
-
<%
|
2
|
+
<% Cohortly::TagReport.collection.distinct(:tags).sort.each_slice(8) do |tags| %>
|
3
3
|
<div class="tag_group" style="float:left; padding: 10px;">
|
4
4
|
<% tags.sort.each do |tag| %>
|
5
5
|
<div class"tag">
|
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
<%= form_for @metric_search, :url => cohortly_reports_path, :html =>
|
4
4
|
{:method => :get, :class => 'cohortly_report_form'} do |f| %>
|
5
|
-
<h3>Groups</h3>
|
5
|
+
<h3>Groups Union</h3>
|
6
6
|
<%= render :partial => 'cohortly/metrics/groups' %>
|
7
|
+
<h3>Groups Intersect</h3>
|
8
|
+
<%= render :partial => 'cohortly/metrics/groups_intersect' %>
|
7
9
|
<h3>Tags</h3>
|
8
10
|
<%= render :partial => 'cohortly/metrics/tags' %>
|
9
11
|
<%= f.submit 'Get Report'%>
|
@@ -34,9 +34,11 @@
|
|
34
34
|
render: function() {
|
35
35
|
$('.result_table', this.el).html(
|
36
36
|
['<h3>',
|
37
|
-
|
37
|
+
this.model.get('groups') ? 'Groups: ' + this.model.get('groups').join(', ') + ' | ': 'Groups: __ | ',
|
38
38
|
'',
|
39
|
-
|
39
|
+
this.model.get('groups_intersect') ? 'Groups Int: ' + this.model.get('groups_intersect').join(', ') + ' | ': 'Groups Int: __ |',
|
40
|
+
'',
|
41
|
+
this.model.get('tags') ? 'Tags: ' + this.model.get('tags').join(', ') : 'Tags: __ ',
|
40
42
|
'</h3>',
|
41
43
|
'<table class="one-column-emphasis">',
|
42
44
|
'<colgroup><col class="oce-first"></colgroup>',
|
@@ -55,22 +57,23 @@
|
|
55
57
|
].join('');
|
56
58
|
},
|
57
59
|
render_rows: function() {
|
58
|
-
return _(this.model.get('data')).
|
60
|
+
return _(_(this.model.get('data')).keys()).sortBy(function(x)
|
61
|
+
{ return x }).map(this.render_row).join('');
|
59
62
|
},
|
60
|
-
render_row: function(
|
61
|
-
var
|
62
|
-
var base_n = this.model.get('base_n')[row_key];
|
63
|
+
render_row: function(row_key) {
|
64
|
+
var base_n_data = this.model.get('base_n')[row_key];
|
63
65
|
return [
|
64
66
|
'<tr>',
|
65
|
-
'<td>' +
|
66
|
-
'<td>' +
|
67
|
-
this.render_cells(
|
67
|
+
'<td>' + base_n_data.pretty_date + '</td>',
|
68
|
+
'<td>' + base_n_data.count + '</td>',
|
69
|
+
this.render_cells(row_key, base_n_data.count),
|
68
70
|
'</tr>'
|
69
71
|
].join('');
|
70
72
|
},
|
71
|
-
render_cells: function(
|
72
|
-
|
73
|
-
|
73
|
+
render_cells: function(row_key, base_n) {
|
74
|
+
var row = this.model.get('data')[row_key];
|
75
|
+
return _(_(row).keys()).sortBy(function(x){return x}).slice(0,14).map(function(key) {
|
76
|
+
var num_users = _(row[key]).keys().length;
|
74
77
|
var percent = (base_n > 0) ? (num_users / base_n) : 0;
|
75
78
|
return [
|
76
79
|
'<td style="text-align:right;">',
|
@@ -79,7 +82,6 @@
|
|
79
82
|
].join('');
|
80
83
|
}).join('');
|
81
84
|
}
|
82
|
-
|
83
85
|
});
|
84
86
|
|
85
87
|
$(function() {
|
data/lib/cohortly/engine.rb
CHANGED
@@ -9,8 +9,10 @@ module Cohortly
|
|
9
9
|
Cohortly::Metric.connection Mongo::Connection.new(cfg.host, cfg.port)
|
10
10
|
Cohortly::Metric.set_database_name cfg.database
|
11
11
|
Cohortly::Metric.database.authenticate(cfg.username, cfg.password) if(cfg.password)
|
12
|
-
Cohortly::
|
13
|
-
Cohortly::
|
12
|
+
Cohortly::TagReport.connection Cohortly::Metric.connection
|
13
|
+
Cohortly::TagReport.set_database_name cfg.database
|
14
|
+
Cohortly::UserCohort.connection Cohortly::Metric.connection
|
15
|
+
Cohortly::UserCohort.set_database_name cfg.database
|
14
16
|
end
|
15
17
|
end
|
16
18
|
Cohortly::StoredProcedures.store_procedures
|
data/lib/cohortly/version.rb
CHANGED
data/lib/tasks/run_reports.rake
CHANGED
@@ -1,28 +1,55 @@
|
|
1
1
|
namespace :cohortly do
|
2
2
|
namespace :run do
|
3
3
|
desc "run the reports for all the tags"
|
4
|
-
task :
|
5
|
-
Cohortly::
|
6
|
-
Cohortly::Metric.cohort_chart(nil, nil, true)
|
7
|
-
puts "main report"
|
8
|
-
real_tags = (Cohortly::Metric.collection.distinct(:tags) - Cohortly::TagConfig.all_groups)
|
4
|
+
task :recalc_reports => :environment do
|
5
|
+
real_tags = Cohortly::Metric.collection.distinct(:tags)
|
9
6
|
real_tags.each do |tag|
|
10
|
-
Cohortly::
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
real_tags.each do |tag|
|
15
|
-
puts "tag: #{tag} group: #{group}"
|
16
|
-
Cohortly::Metric.cohort_chart([tag], [group], true)
|
17
|
-
end
|
7
|
+
report = Cohortly::TagReport.where(:tags => tag).first
|
8
|
+
report ||= Cohortly::TagReport.new(:tags => [tag])
|
9
|
+
report.recalc_table
|
10
|
+
report.save
|
18
11
|
end
|
12
|
+
|
13
|
+
# the empty report
|
14
|
+
report = Cohortly::TagReport.where(:tags => []).first
|
15
|
+
report ||= Cohortly::TagReport.new(:tags => [])
|
16
|
+
report.recalc_table
|
17
|
+
report.save
|
19
18
|
end
|
20
19
|
|
21
20
|
desc "update all existing reports"
|
22
21
|
task :updates => :environment do
|
23
|
-
Cohortly::
|
24
|
-
|
25
|
-
|
22
|
+
real_tags = Cohortly::Metric.collection.distinct(:tags)
|
23
|
+
real_tags.each do |tag|
|
24
|
+
report = Cohortly::TagReport.where(:tags => tag).first
|
25
|
+
report ||= Cohortly::TagReport.new(:tags => [tag])
|
26
|
+
report.run
|
27
|
+
report.save
|
28
|
+
end
|
29
|
+
|
30
|
+
# the empty report
|
31
|
+
report = Cohortly::TagReport.where(:tags => []).first
|
32
|
+
report ||= Cohortly::TagReport.new(:tags => [])
|
33
|
+
report.run
|
34
|
+
report.save
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "build cohorts"
|
38
|
+
task :build_cohorts => :environment do
|
39
|
+
Cohortly::Cohorts.group_names.each do |name|
|
40
|
+
cohort = Cohortly::GroupCohort.find_or_create_by_name(name)
|
41
|
+
cohort.store!
|
42
|
+
end
|
43
|
+
|
44
|
+
#weekly cohort
|
45
|
+
cur_time = Cohortly::Cohorts.first_user_start_date.utc.beginning_of_week
|
46
|
+
while(cur_time < Time.now) do
|
47
|
+
time_key = (cur_time + 3.days).strftime("%Y-%W")
|
48
|
+
cohort = Cohortly::PeriodCohort.find_or_create_by_name(time_key)
|
49
|
+
cohort.start_time = cur_time
|
50
|
+
cohort.weekly = true
|
51
|
+
cohort.store!
|
52
|
+
cur_time += 1.week
|
26
53
|
end
|
27
54
|
end
|
28
55
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Cohortly
|
2
|
+
class Cohorts
|
3
|
+
def group_names
|
4
|
+
[:rand_1, :rand_2, :rand_3]
|
5
|
+
end
|
6
|
+
|
7
|
+
def group_name(name)
|
8
|
+
Cohortly::Metric.collection.distinct(:user_id, :tag => [name])
|
9
|
+
end
|
10
|
+
|
11
|
+
def range(time_range)
|
12
|
+
Cohortly::Metric.collection.distinct(:user_id, :user_start_time => { :$gte => time_range.begin, :lt => time_range.end })
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -9997,3 +9997,160 @@ Started GET "/cohortly/reports.js?utf8=%E2%9C%93" for 127.0.0.1 at 2011-10-27 00
|
|
9997
9997
|
Processing by Cohortly::ReportsController#index as JS
|
9998
9998
|
Parameters: {"utf8"=>"✓"}
|
9999
9999
|
Completed 200 OK in 166ms (Views: 112.0ms)
|
10000
|
+
|
10001
|
+
|
10002
|
+
Started GET "/" for 127.0.0.1 at 2011-10-30 14:52:13 -0400
|
10003
|
+
Processing by StuffController#index as HTML
|
10004
|
+
Rendered stuff/index.html.erb within layouts/application (1.5ms)
|
10005
|
+
Completed 200 OK in 101ms (Views: 95.9ms)
|
10006
|
+
|
10007
|
+
|
10008
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 14:52:16 -0400
|
10009
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10010
|
+
Completed in 56ms
|
10011
|
+
|
10012
|
+
ArgumentError (wrong number of arguments (0 for 1)):
|
10013
|
+
|
10014
|
+
|
10015
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.1ms)
|
10016
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (68.4ms)
|
10017
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (76.5ms)
|
10018
|
+
|
10019
|
+
|
10020
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 14:54:50 -0400
|
10021
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10022
|
+
Completed in 64ms
|
10023
|
+
|
10024
|
+
NameError (undefined local variable or method `cohort' for #<Cohortly::ReportsController:0x00000100e57a80>):
|
10025
|
+
|
10026
|
+
|
10027
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.1ms)
|
10028
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (56.9ms)
|
10029
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (61.4ms)
|
10030
|
+
|
10031
|
+
|
10032
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 14:55:07 -0400
|
10033
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10034
|
+
Completed in 75ms
|
10035
|
+
|
10036
|
+
NoMethodError (undefined method `stftime' for 2011-04-25 00:00:00 UTC:Time):
|
10037
|
+
|
10038
|
+
|
10039
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.1ms)
|
10040
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (65.4ms)
|
10041
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (70.0ms)
|
10042
|
+
|
10043
|
+
|
10044
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 14:55:18 -0400
|
10045
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10046
|
+
Completed in 52ms
|
10047
|
+
|
10048
|
+
NoMethodError (undefined method `start' for 2011-04-25 00:00:00 UTC..2011-05-02 00:00:00 UTC:Range):
|
10049
|
+
|
10050
|
+
|
10051
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.0ms)
|
10052
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (51.4ms)
|
10053
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (55.7ms)
|
10054
|
+
|
10055
|
+
|
10056
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 14:55:28 -0400
|
10057
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10058
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_groups.html.erb (1.3ms)
|
10059
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_tags.html.erb (1183.8ms)
|
10060
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/reports/index.html.erb within layouts/cohortly/application (1213.2ms)
|
10061
|
+
Completed in 3077ms
|
10062
|
+
|
10063
|
+
ActionView::Template::Error (comparison of String with Array failed):
|
10064
|
+
1: <div class="tags">
|
10065
|
+
2: <% (Cohortly::Metric.collection.distinct(:tags) - Cohortly::TagConfig.all_groups).sort.each_slice(8) do |tags| %>
|
10066
|
+
3: <div class="tag_group" style="float:left; padding: 10px;">
|
10067
|
+
4: <% tags.sort.each do |tag| %>
|
10068
|
+
5: <div class"tag">
|
10069
|
+
|
10070
|
+
|
10071
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.1ms)
|
10072
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (75.2ms)
|
10073
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/template_error.erb within rescues/layout (80.2ms)
|
10074
|
+
|
10075
|
+
|
10076
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 15:06:30 -0400
|
10077
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10078
|
+
Completed in 55ms
|
10079
|
+
|
10080
|
+
NoMethodError (undefined method `utc' for nil:NilClass):
|
10081
|
+
|
10082
|
+
|
10083
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.0ms)
|
10084
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (50.8ms)
|
10085
|
+
Rendered /Users/bhauman/.rvm/gems/ruby-1.9.2-p136@cohortly/gems/actionpack-3.0.4/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (55.3ms)
|
10086
|
+
|
10087
|
+
|
10088
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 15:09:32 -0400
|
10089
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10090
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_groups.html.erb (0.7ms)
|
10091
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_tags.html.erb (1129.6ms)
|
10092
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/reports/index.html.erb within layouts/cohortly/application (1136.1ms)
|
10093
|
+
Completed 200 OK in 2988ms (Views: 1140.6ms)
|
10094
|
+
|
10095
|
+
|
10096
|
+
Started GET "/cohortly/reports.js?utf8=%E2%9C%93" for 127.0.0.1 at 2011-10-30 15:09:43 -0400
|
10097
|
+
Processing by Cohortly::ReportsController#index as JS
|
10098
|
+
Parameters: {"utf8"=>"✓"}
|
10099
|
+
Completed 200 OK in 2108ms (Views: 285.1ms)
|
10100
|
+
|
10101
|
+
|
10102
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 15:10:28 -0400
|
10103
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10104
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_groups.html.erb (0.7ms)
|
10105
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_tags.html.erb (1147.7ms)
|
10106
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/reports/index.html.erb within layouts/cohortly/application (1179.5ms)
|
10107
|
+
Completed 200 OK in 3007ms (Views: 1184.0ms)
|
10108
|
+
|
10109
|
+
|
10110
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 15:11:45 -0400
|
10111
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10112
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_groups.html.erb (0.7ms)
|
10113
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_tags.html.erb (1181.6ms)
|
10114
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/reports/index.html.erb within layouts/cohortly/application (1188.1ms)
|
10115
|
+
Completed 200 OK in 3150ms (Views: 1193.9ms)
|
10116
|
+
|
10117
|
+
|
10118
|
+
Started GET "/cohortly/reports.js?utf8=%E2%9C%93" for 127.0.0.1 at 2011-10-30 15:11:52 -0400
|
10119
|
+
Processing by Cohortly::ReportsController#index as JS
|
10120
|
+
Parameters: {"utf8"=>"✓"}
|
10121
|
+
Completed 200 OK in 2224ms (Views: 299.9ms)
|
10122
|
+
|
10123
|
+
|
10124
|
+
Started GET "/cohortly/reports" for 127.0.0.1 at 2011-10-30 15:12:48 -0400
|
10125
|
+
Processing by Cohortly::ReportsController#index as HTML
|
10126
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_groups.html.erb (0.8ms)
|
10127
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/metrics/_tags.html.erb (1193.8ms)
|
10128
|
+
Rendered /Users/bhauman/workspace/cohortly/app/views/cohortly/reports/index.html.erb within layouts/cohortly/application (1200.4ms)
|
10129
|
+
Completed 200 OK in 3060ms (Views: 1204.8ms)
|
10130
|
+
|
10131
|
+
|
10132
|
+
Started GET "/cohortly/reports.js?utf8=%E2%9C%93" for 127.0.0.1 at 2011-10-30 15:13:09 -0400
|
10133
|
+
Processing by Cohortly::ReportsController#index as JS
|
10134
|
+
Parameters: {"utf8"=>"✓"}
|
10135
|
+
Completed 200 OK in 2218ms (Views: 391.1ms)
|
10136
|
+
|
10137
|
+
|
10138
|
+
Started GET "/" for 127.0.0.1 at 2011-10-30 15:15:58 -0400
|
10139
|
+
Processing by StuffController#index as HTML
|
10140
|
+
Rendered stuff/index.html.erb within layouts/application (1.4ms)
|
10141
|
+
Completed 200 OK in 22ms (Views: 5.5ms)
|
10142
|
+
|
10143
|
+
|
10144
|
+
Started GET "/cohortly/reports.js?utf8=%E2%9C%93" for 127.0.0.1 at 2011-10-30 15:16:02 -0400
|
10145
|
+
Processing by Cohortly::ReportsController#index as JS
|
10146
|
+
Parameters: {"utf8"=>"✓"}
|
10147
|
+
Completed 200 OK in 2273ms (Views: 298.8ms)
|
10148
|
+
DEPRECATION WARNING: ActionController::Routing::Routes is deprecated. Instead, use Rails.application.routes. (called from block in irb_binding at (irb):15)
|
10149
|
+
DEPRECATION WARNING: RAILS_DEFAULT_LOGGER is deprecated. Please use ::Rails.logger. (called from block in irb_binding at (irb):15)
|
10150
|
+
DEPRECATION WARNING: RAILS_ENV is deprecated. Please use ::Rails.env. (called from block in irb_binding at (irb):15)
|
10151
|
+
DEPRECATION WARNING: RAILS_ROOT is deprecated. Please use ::Rails.root.to_s. (called from block in irb_binding at (irb):15)
|
10152
|
+
DEPRECATION WARNING: ActiveSupport::JSON::CircularReferenceError is deprecated! Use ActiveSupport::JSON::Encoding::CircularReferenceError instead. (called from block in irb_binding at (irb):15)
|
10153
|
+
DEPRECATION WARNING: ActiveSupport::JSON::CircularReferenceError is deprecated! Use ActiveSupport::JSON::Encoding::CircularReferenceError instead. (called from block in irb_binding at (irb):15)
|
10154
|
+
DEPRECATION WARNING: ActionController::Routing::Routes is deprecated. Instead, use Rails.application.routes. (called from block in irb_binding at (irb):20)
|
10155
|
+
DEPRECATION WARNING: ActiveSupport::JSON::CircularReferenceError is deprecated! Use ActiveSupport::JSON::Encoding::CircularReferenceError instead. (called from block in irb_binding at (irb):20)
|
10156
|
+
DEPRECATION WARNING: ActiveSupport::JSON::CircularReferenceError is deprecated! Use ActiveSupport::JSON::Encoding::CircularReferenceError instead. (called from block in irb_binding at (irb):20)
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: cohortly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.92
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Bruce Hauman
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-11-02 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -29,11 +29,15 @@ files:
|
|
29
29
|
- app/controllers/cohortly/cohortly_controller.rb
|
30
30
|
- app/controllers/cohortly/metrics_controller.rb
|
31
31
|
- app/controllers/cohortly/reports_controller.rb
|
32
|
+
- app/models/cohortly/cohorts.rb
|
33
|
+
- app/models/cohortly/group_cohort.rb
|
32
34
|
- app/models/cohortly/metric.rb
|
33
|
-
- app/models/cohortly/
|
34
|
-
- app/models/cohortly/report_meta.rb
|
35
|
+
- app/models/cohortly/period_cohort.rb
|
35
36
|
- app/models/cohortly/stored_procedures.rb
|
37
|
+
- app/models/cohortly/tag_report.rb
|
38
|
+
- app/models/cohortly/user_cohort.rb
|
36
39
|
- app/views/cohortly/metrics/_groups.html.erb
|
40
|
+
- app/views/cohortly/metrics/_groups_intersect.html.erb
|
37
41
|
- app/views/cohortly/metrics/_metric.html.erb
|
38
42
|
- app/views/cohortly/metrics/_pagination.html.erb
|
39
43
|
- app/views/cohortly/metrics/_tags.html.erb
|
@@ -58,6 +62,7 @@ files:
|
|
58
62
|
- test/dummy/app/controllers/stuff_controller.rb
|
59
63
|
- test/dummy/app/helpers/application_helper.rb
|
60
64
|
- test/dummy/app/helpers/stuff_helper.rb
|
65
|
+
- test/dummy/app/models/cohortly/cohorts.rb
|
61
66
|
- test/dummy/app/views/layouts/application.html.erb
|
62
67
|
- test/dummy/app/views/stuff/index.html.erb
|
63
68
|
- test/dummy/app/views/stuff/my_stuff.html.erb
|
@@ -97,7 +102,6 @@ files:
|
|
97
102
|
- test/dummy/public/javascripts/vendor/underscore.js
|
98
103
|
- test/dummy/Rakefile
|
99
104
|
- test/dummy/script/rails
|
100
|
-
- test/dummy/tmp/pids/server.pid
|
101
105
|
- test/integration/navigation_test.rb
|
102
106
|
- test/support/integration_case.rb
|
103
107
|
- test/support/test_tag_config.rb
|
@@ -136,6 +140,7 @@ test_files:
|
|
136
140
|
- test/dummy/app/controllers/stuff_controller.rb
|
137
141
|
- test/dummy/app/helpers/application_helper.rb
|
138
142
|
- test/dummy/app/helpers/stuff_helper.rb
|
143
|
+
- test/dummy/app/models/cohortly/cohorts.rb
|
139
144
|
- test/dummy/app/views/layouts/application.html.erb
|
140
145
|
- test/dummy/app/views/stuff/index.html.erb
|
141
146
|
- test/dummy/app/views/stuff/my_stuff.html.erb
|
@@ -175,7 +180,6 @@ test_files:
|
|
175
180
|
- test/dummy/public/javascripts/vendor/underscore.js
|
176
181
|
- test/dummy/Rakefile
|
177
182
|
- test/dummy/script/rails
|
178
|
-
- test/dummy/tmp/pids/server.pid
|
179
183
|
- test/integration/navigation_test.rb
|
180
184
|
- test/support/integration_case.rb
|
181
185
|
- test/support/test_tag_config.rb
|
@@ -1,118 +0,0 @@
|
|
1
|
-
module Cohortly
|
2
|
-
class Report
|
3
|
-
# this is the reduced collection
|
4
|
-
attr_accessor :collection, :weekly, :key_pattern, :groups, :tags, :report_meta
|
5
|
-
def initialize( tags = nil, groups = nil, weekly = true )
|
6
|
-
self.collection = Cohortly::Metric.report_table_name(tags, groups, weekly)
|
7
|
-
self.report_meta = ReportMeta.find_or_create_by_collection_name(self.collection)
|
8
|
-
self.groups = groups
|
9
|
-
self.tags = tags
|
10
|
-
self.weekly = weekly
|
11
|
-
self.key_pattern = self.weekly ? "%Y-%W" : "%Y-%m"
|
12
|
-
end
|
13
|
-
|
14
|
-
def data
|
15
|
-
@data ||= (Cohortly::Metric.database[self.report_meta.store_name].find().collect {|x| x}).sort_by {|x| x['_id'] }
|
16
|
-
end
|
17
|
-
|
18
|
-
def fix_data_lines
|
19
|
-
data.each do |line|
|
20
|
-
period_cohorts_from(line['_id']).collect do |key|
|
21
|
-
if line["value"][key].nil?
|
22
|
-
line["value"][key] = { }
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def offset
|
29
|
-
self.weekly ? 1.week : 1.month
|
30
|
-
end
|
31
|
-
|
32
|
-
def start_key
|
33
|
-
data.first['_id']
|
34
|
-
end
|
35
|
-
|
36
|
-
def end_key
|
37
|
-
time_to_key(Time.now.utc)
|
38
|
-
end
|
39
|
-
|
40
|
-
def time_to_key(time)
|
41
|
-
time.strftime(self.key_pattern)
|
42
|
-
end
|
43
|
-
|
44
|
-
def key_to_time(report_key)
|
45
|
-
DateTime.strptime(report_key, self.key_pattern).to_time.utc
|
46
|
-
end
|
47
|
-
|
48
|
-
def user_count_in_cohort(report_key)
|
49
|
-
params = { :user_start_date => { :$gt => key_to_time(report_key) - 1.week,
|
50
|
-
:$lt => (key_to_time(report_key) )}}
|
51
|
-
params[:tags] = { :$in => groups } if self.groups
|
52
|
-
Cohortly::Metric.collection.distinct(:user_id, params).length
|
53
|
-
end
|
54
|
-
|
55
|
-
def period_cohorts
|
56
|
-
return [] unless data.first
|
57
|
-
start_time = key_to_time(start_key)
|
58
|
-
end_time = key_to_time(end_key)
|
59
|
-
cur_time = start_time
|
60
|
-
res = [start_key]
|
61
|
-
cur_time += self.offset
|
62
|
-
while(cur_time < end_time) do
|
63
|
-
res << time_to_key(cur_time)
|
64
|
-
cur_time += self.offset
|
65
|
-
end
|
66
|
-
res
|
67
|
-
end
|
68
|
-
|
69
|
-
def period_cohorts_from(cohort_key)
|
70
|
-
index = period_cohorts.index(cohort_key)
|
71
|
-
if index
|
72
|
-
period_cohorts[index..-1]
|
73
|
-
else
|
74
|
-
[]
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def report_line(cohort_key)
|
79
|
-
line = data.find {|x| x['_id'] == cohort_key}
|
80
|
-
return [] unless line
|
81
|
-
period_cohorts_from(cohort_key).collect do |key|
|
82
|
-
if line["value"][key]
|
83
|
-
line["value"][key].keys.length
|
84
|
-
else
|
85
|
-
0
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def percent_line(cohort_key)
|
91
|
-
line = report_line(cohort_key)
|
92
|
-
base = user_count_in_cohort(cohort_key)
|
93
|
-
line.collect { |x| (x && base > 0.0 ) ? (x/base.to_f * 100).round : 0 }.unshift base
|
94
|
-
end
|
95
|
-
|
96
|
-
def report_totals
|
97
|
-
period_cohorts.collect do |cohort_key|
|
98
|
-
report_line(cohort_key)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def base_n
|
103
|
-
@base_n ||= self.period_cohorts.inject({ }) { |accum, key| accum[key] = user_count_in_cohort(key); accum }
|
104
|
-
end
|
105
|
-
|
106
|
-
def as_json(*args)
|
107
|
-
fix_data_lines
|
108
|
-
{ :name => report_meta.collection_name,
|
109
|
-
:store_name => report_meta.store_name,
|
110
|
-
:groups => self.groups,
|
111
|
-
:tags => self.tags,
|
112
|
-
:weekly => self.weekly,
|
113
|
-
:data => data,
|
114
|
-
:base_n => base_n
|
115
|
-
}
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module Cohortly
|
2
|
-
class ReportMeta
|
3
|
-
include MongoMapper::Document
|
4
|
-
|
5
|
-
key :collection_name, String
|
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
|
16
|
-
end
|
17
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
5601
|