redis_analytics 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +4 -0
- data/Gemfile.lock +57 -0
- data/LICENSE +19 -0
- data/README.md +98 -0
- data/Rakefile +19 -0
- data/bin/GeoIP.dat +0 -0
- data/bin/redis_analytics_dashboard +17 -0
- data/lib/redis_analytics.rb +22 -0
- data/lib/redis_analytics/analytics.rb +231 -0
- data/lib/redis_analytics/config.ru +10 -0
- data/lib/redis_analytics/configuration.rb +71 -0
- data/lib/redis_analytics/dashboard.rb +114 -0
- data/lib/redis_analytics/dashboard/public/css/bootstrap-responsive.min.css +9 -0
- data/lib/redis_analytics/dashboard/public/css/bootstrap.min.css +9 -0
- data/lib/redis_analytics/dashboard/public/css/jquery-jvectormap-1.2.2.css +37 -0
- data/lib/redis_analytics/dashboard/public/css/morris.css +2 -0
- data/lib/redis_analytics/dashboard/public/favicon.ico +0 -0
- data/lib/redis_analytics/dashboard/public/img/glyphicons-halflings-white.png +0 -0
- data/lib/redis_analytics/dashboard/public/img/glyphicons-halflings.png +0 -0
- data/lib/redis_analytics/dashboard/public/javascripts/bootstrap.min.js +6 -0
- data/lib/redis_analytics/dashboard/public/javascripts/jquery-1.9.1.min.js +5 -0
- data/lib/redis_analytics/dashboard/public/javascripts/jquery-jvectormap-1.2.2.min.js +8 -0
- data/lib/redis_analytics/dashboard/public/javascripts/jquery-jvectormap-world-mill-en.js +1 -0
- data/lib/redis_analytics/dashboard/public/javascripts/morris.min.js +1 -0
- data/lib/redis_analytics/dashboard/public/javascripts/raphael-min.js +10 -0
- data/lib/redis_analytics/dashboard/views/activity.erb +0 -0
- data/lib/redis_analytics/dashboard/views/footer.erb +5 -0
- data/lib/redis_analytics/dashboard/views/header.erb +44 -0
- data/lib/redis_analytics/dashboard/views/layout.erb +33 -0
- data/lib/redis_analytics/dashboard/views/visits.erb +176 -0
- data/lib/redis_analytics/dashboard/views/visits_js.erb +265 -0
- data/lib/redis_analytics/helpers.rb +69 -0
- data/lib/redis_analytics/railtie.rb +13 -0
- data/lib/redis_analytics/time_ext.rb +19 -0
- data/lib/redis_analytics/tracker.rb +26 -0
- data/lib/redis_analytics/version.rb +5 -0
- data/redis_analytics.gemspec +37 -0
- data/screenshot.png +0 -0
- data/spec/redis_analytics_spec.rb +57 -0
- data/spec/spec_helper.rb +47 -0
- metadata +216 -0
@@ -0,0 +1,265 @@
|
|
1
|
+
<script type="text/javascript" language="text/javascript">
|
2
|
+
|
3
|
+
visits_area = Morris.Area({
|
4
|
+
element: 'visits_area',
|
5
|
+
xkey: 'date',
|
6
|
+
data: <%=@data[@range][:visits_new_visits_plot].to_json%>,
|
7
|
+
ykeys: ['new_visits', 'returning_visits'],
|
8
|
+
labels: ['New Visits', 'Returning Visits'],
|
9
|
+
ymax: 'auto',
|
10
|
+
//events: ['2013-03-02', '2013-03-03'],
|
11
|
+
grid: true,
|
12
|
+
eventStrokeWidth: 2,
|
13
|
+
eventLineColors: ['#ccc', '#eee'],
|
14
|
+
smooth: true,
|
15
|
+
hideHover: 'auto',
|
16
|
+
parseTime: false,
|
17
|
+
lineWidth: 1,
|
18
|
+
pointSize: 0,
|
19
|
+
// xLabels: 'day',
|
20
|
+
xLabelFormat: function(x) {return '';},
|
21
|
+
|
22
|
+
// hoverCallback: function(index, series)
|
23
|
+
// {
|
24
|
+
// h = series.data[index];
|
25
|
+
// d = Date(h.x);
|
26
|
+
// return(d + '<br>' + h.new_visits);
|
27
|
+
// },
|
28
|
+
pointFillColors: ['#fff'],
|
29
|
+
pointStrokeColors: ['#000'],
|
30
|
+
});
|
31
|
+
|
32
|
+
visits_donut = Morris.Donut({
|
33
|
+
element: 'visits_donut',
|
34
|
+
data: <%=@data[@range][:visits_new_visits_donut].to_json%>
|
35
|
+
});
|
36
|
+
|
37
|
+
referrers_donut = Morris.Donut({
|
38
|
+
element: 'referrers_donut',
|
39
|
+
data: <%=@data[@range][:referrers_donut].to_json%>
|
40
|
+
});
|
41
|
+
|
42
|
+
browsers_donut = Morris.Donut({
|
43
|
+
element: 'browsers_donut',
|
44
|
+
data: <%=@data[@range][:browsers_donut].to_json%>
|
45
|
+
});
|
46
|
+
|
47
|
+
unique_visits_line = Morris.Line({
|
48
|
+
element: 'unique_visits_line',
|
49
|
+
xkey: 'unit',
|
50
|
+
data: <%=@data[@range][:unique_visits].to_json%>,
|
51
|
+
ykeys: ['unique_visits_this', 'unique_visits_last'],
|
52
|
+
labels: ['Current', 'Previous'],
|
53
|
+
ymax: 'auto',
|
54
|
+
hideHover: 'auto',
|
55
|
+
parseTime: false,
|
56
|
+
lineWidth: 1,
|
57
|
+
pointSize: 0,
|
58
|
+
grid: true,
|
59
|
+
pointFillColors: ['#fff'],
|
60
|
+
pointStrokeColors: ['#000'],
|
61
|
+
// hoverCallback: function(index, series)
|
62
|
+
// {
|
63
|
+
// h = series.data[index];
|
64
|
+
// this_week_color = series.lineColors[0];
|
65
|
+
// last_week_color = series.lineColors[1];
|
66
|
+
// html = '';
|
67
|
+
// html += '<font style="color:' + this_week_color + '">This ' + h.unit + ': ' + h.unique_visits_this + '</font>';
|
68
|
+
// html += '<br/>';
|
69
|
+
// html += '<font style="color:' + last_week_color + '">Last ' + h.unit + ': ' + h.unique_visits_last + '</font>';
|
70
|
+
// return html;
|
71
|
+
// },
|
72
|
+
|
73
|
+
});
|
74
|
+
|
75
|
+
// visits_spark = Morris.Line({
|
76
|
+
// element: 'visits_spark',
|
77
|
+
// xkey: 'date',
|
78
|
+
// data: <%=@data[@range][:visits_page_views_plot].to_json%>,
|
79
|
+
// ykeys: ['visits', 'page_views'],
|
80
|
+
// labels: ['Visits', 'Page Views'],
|
81
|
+
// xLabelFormat: function(x){return '';},
|
82
|
+
// yLabelFormat: function(x){return '';},
|
83
|
+
// hoverCallback: function(i,x){return x.data[i].date + '<br/>' + 'Page Views: ' + x.data[i].page_views + '<br/>Visits: ' + x.data[i].visits;},
|
84
|
+
// ymax: 'auto',
|
85
|
+
// ymin: 0,
|
86
|
+
// smooth: true,
|
87
|
+
// hideHover: 'auto',
|
88
|
+
// parseTime: false,
|
89
|
+
// lineWidth: 2,
|
90
|
+
// pointSize: 0,
|
91
|
+
// grid: false,
|
92
|
+
// pointFillColors: ['#fff'],
|
93
|
+
// pointStrokeColors: ['#000']
|
94
|
+
// });
|
95
|
+
|
96
|
+
function setSessionCookie(name,value) {
|
97
|
+
document.cookie = name+"="+value+"; path=/";
|
98
|
+
}
|
99
|
+
|
100
|
+
function changeTimeFrame(range)
|
101
|
+
{
|
102
|
+
var mapObject = $('#world-map').vectorMap('get', 'mapObject');
|
103
|
+
switch(range)
|
104
|
+
{
|
105
|
+
<% Rack::RedisAnalytics.time_range_formats.map{|x| x[0].to_s}.each do |range| %>
|
106
|
+
case '<%=range%>':
|
107
|
+
setSessionCookie("_rarng", "<%=range%>");
|
108
|
+
visits_area.setData(<%=@data[range.to_sym][:visits_new_visits_plot].to_json%>);
|
109
|
+
unique_visits_line.setData(<%=@data[range.to_sym][:unique_visits].to_json%>);
|
110
|
+
|
111
|
+
$("#total_visits")[0].innerHTML = '<%=@data[range.to_sym][:total_visits]%> <small class="muted">visits</small>';
|
112
|
+
$("#total_page_views")[0].innerHTML = '<%=@data[range.to_sym][:total_page_views]%> <small class="muted">page views</small>';
|
113
|
+
$("#page_depth")[0].innerHTML = '<%=parse_float((@data[range.to_sym][:total_page_views].to_f/@data[range.to_sym][:total_visits].to_f).round(2))%> <small class="muted">pages per visit</small>';
|
114
|
+
$("#bounce_rate")[0].innerHTML = '<%=parse_float((((@data[range.to_sym][:total_visits].to_f-@data[range.to_sym][:total_second_page_views].to_f)/@data[range.to_sym][:total_visits].to_f)*100).round(2))%>% <small class="muted">bounce rate</small>';
|
115
|
+
$("#visit_duration")[0].innerHTML = '<%=Time.at(@data[range.to_sym][:avg_visit_time].round(2)).gmtime.strftime("%R:%S")%> <small class="muted">avg time spent</small>';
|
116
|
+
$("#new_visits")[0].innerHTML = '<%=parse_float(((@data[range.to_sym][:total_new_visits].to_f/@data[range.to_sym][:total_visits].to_f)*100).round(2))%>% <small class="muted">new visits</small></h4>';
|
117
|
+
|
118
|
+
visits_donut = Morris.Donut({element: 'visits_donut', data: <%=@data[range.to_sym][:visits_new_visits_donut].to_json%>});
|
119
|
+
browsers_donut = Morris.Donut({element: 'browsers_donut', data: <%=@data[range.to_sym][:browsers_donut].to_json%>});
|
120
|
+
referrers_donut = Morris.Donut({element: 'referrers_donut', data: <%=@data[range.to_sym][:referrers_donut].to_json%>});
|
121
|
+
mapObject.series.regions[0].setValues(<%=@data[range.to_sym][:country_map].to_json%>);
|
122
|
+
|
123
|
+
$("#compare_unit")[0].innerHTML = '<%=range%>';
|
124
|
+
|
125
|
+
<% @data[range.to_sym][:visitor_recency_slices].each_with_index do |((k, v), i), c| %>
|
126
|
+
<% p = ((i.to_f/@data[range.to_sym][:visitor_recency_slices].map{|x| x[1]}.sum) * 100).round rescue '0' %>
|
127
|
+
<%= "$('#recency_#{c}')[0].style.width = '#{p}%';" %>
|
128
|
+
<%= "$('#recency_#{c}_num')[0].title = '#{i.to_i} visits';" %>
|
129
|
+
<%= "$('#recency_#{c}_cent')[0].innerHTML = '#{p}';" %>
|
130
|
+
<% end %>
|
131
|
+
|
132
|
+
break;
|
133
|
+
<% end %>
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
// Map Overlay
|
138
|
+
|
139
|
+
var geoLocationData = <%=@data[@range][:country_map].to_json%>;
|
140
|
+
|
141
|
+
$(function(){
|
142
|
+
$('#world-map').vectorMap({
|
143
|
+
map: 'world_mill_en',
|
144
|
+
min: 0,
|
145
|
+
backgroundColor: '#fff',
|
146
|
+
regionStyle: {
|
147
|
+
initial: {
|
148
|
+
fill: '#ddd',
|
149
|
+
"fill-opacity": 1,
|
150
|
+
stroke: 'none',
|
151
|
+
"stroke-width": 0,
|
152
|
+
"stroke-opacity": 1
|
153
|
+
},
|
154
|
+
hover: {
|
155
|
+
fill: '#bbb',
|
156
|
+
"fill-opacity": 0.8
|
157
|
+
},
|
158
|
+
selected: {
|
159
|
+
fill: 'yellow'
|
160
|
+
},
|
161
|
+
selectedHover: {
|
162
|
+
}
|
163
|
+
},
|
164
|
+
series: {
|
165
|
+
regions: [{
|
166
|
+
scale: ['#C8EEFF', '#0071A4'],
|
167
|
+
values: geoLocationData,
|
168
|
+
normalizeFunction: 'polynomial'
|
169
|
+
}]
|
170
|
+
},
|
171
|
+
onLabelShow: function(e, el, code){
|
172
|
+
el.html(el.html()+' (GDP - '+geoLocationData[code]+')');
|
173
|
+
}
|
174
|
+
});
|
175
|
+
});
|
176
|
+
|
177
|
+
function onDownload() {
|
178
|
+
document.location = 'data:Application/octet-stream,' +
|
179
|
+
encodeURIComponent(convertToCSV(<%=@data[@range][:visits_new_visits_plot].to_json%>));
|
180
|
+
}
|
181
|
+
|
182
|
+
function convertToCSV(objArray) {
|
183
|
+
var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
|
184
|
+
var str = '';
|
185
|
+
|
186
|
+
for (var i = 0; i < array.length; i++) {
|
187
|
+
var line = '';
|
188
|
+
for (var index in array[i]) {
|
189
|
+
if (line != '') line += ','
|
190
|
+
|
191
|
+
line += array[i][index];
|
192
|
+
}
|
193
|
+
|
194
|
+
str += line + '\r\n';
|
195
|
+
}
|
196
|
+
|
197
|
+
return str;
|
198
|
+
};
|
199
|
+
|
200
|
+
$('#visits_modal').on('show', function () {
|
201
|
+
|
202
|
+
colors = Highcharts.getOptions().colors;
|
203
|
+
|
204
|
+
// create the chart when all data is loaded
|
205
|
+
$(function(){
|
206
|
+
|
207
|
+
$('#visits_detail').highcharts('StockChart', {
|
208
|
+
chart: {
|
209
|
+
},
|
210
|
+
|
211
|
+
// tooltip: {
|
212
|
+
// pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
|
213
|
+
// valueDecimals: 2
|
214
|
+
// },
|
215
|
+
yAxis: {
|
216
|
+
allowDecimals: false
|
217
|
+
},
|
218
|
+
xAxis: {
|
219
|
+
allowDecimals: false
|
220
|
+
},
|
221
|
+
|
222
|
+
series: [{
|
223
|
+
name: 'Visits',
|
224
|
+
data: <%=@data[:all_visits].to_json%>
|
225
|
+
}]
|
226
|
+
});
|
227
|
+
});
|
228
|
+
});
|
229
|
+
|
230
|
+
|
231
|
+
$('#unique_visits_modal').on('show', function () {
|
232
|
+
|
233
|
+
colors = Highcharts.getOptions().colors;
|
234
|
+
|
235
|
+
// create the chart when all data is loaded
|
236
|
+
$(function(){
|
237
|
+
|
238
|
+
$('#unique_visits_detail').highcharts('StockChart', {
|
239
|
+
chart: {
|
240
|
+
},
|
241
|
+
|
242
|
+
// tooltip: {
|
243
|
+
// pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
|
244
|
+
// valueDecimals: 2
|
245
|
+
// },
|
246
|
+
yAxis: {
|
247
|
+
allowDecimals: false
|
248
|
+
},
|
249
|
+
xAxis: {
|
250
|
+
allowDecimals: false
|
251
|
+
},
|
252
|
+
|
253
|
+
series: [{
|
254
|
+
name: 'Unique Visits',
|
255
|
+
data: <%=@data[:all_unique_visits].to_json%>
|
256
|
+
}]
|
257
|
+
});
|
258
|
+
});
|
259
|
+
});
|
260
|
+
|
261
|
+
</script>
|
262
|
+
|
263
|
+
<style>
|
264
|
+
.modal {width:940px;left:36%;}
|
265
|
+
</style>
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module RedisAnalytics
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
FORMAT_SPECIFIER = [['%Y', 365], ['%m', 30], ['%d', 24], ['%H', 60], ['%M', 60]]
|
8
|
+
GRANULARITY = ['yearly', 'monthly', 'dayly', 'hourly', 'minutely']
|
9
|
+
|
10
|
+
# all methods are private unless explicitly declared public
|
11
|
+
private
|
12
|
+
|
13
|
+
def method_missing(meth, *args, &block)
|
14
|
+
if meth.to_s =~ /^(minute|hour|dai|day|month|year)ly_(new_visits|visits|page_views|second_page_views|unique_visits|visit_time|ratio_recency|ratio_browsers|ratio_platforms|ratio_devices|ratio_country|ratio_referrers)$/
|
15
|
+
granularity = ($1 == 'dai' ? 'day' : $1) + 'ly'
|
16
|
+
type = $2
|
17
|
+
data(granularity, type, *args)
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def data(granularity, type, from_date, options = {})
|
24
|
+
aggregate = options[:aggregate] || false
|
25
|
+
x = granularity[0..-3]
|
26
|
+
# puts "DATA: #{x} (#{type})"
|
27
|
+
to_date = (options[:to_date] || Time.now).send("end_of_#{x}")
|
28
|
+
i = from_date.send("beginning_of_#{x}") #unless x == 'minute'
|
29
|
+
|
30
|
+
# puts "FROM: #{i} to #{to_date}"
|
31
|
+
union = []
|
32
|
+
time = []
|
33
|
+
begin
|
34
|
+
union << "#{Rack::RedisAnalytics.redis_namespace}:#{type}:#{i.strftime(FORMAT_SPECIFIER[0..GRANULARITY.index(granularity)].map{|x| x[0]}.join('_'))}"
|
35
|
+
time << i
|
36
|
+
i += 1.send(x) # FORMAT_SPECIFIER[GRANULARITY.index(granularity)..-1].map{|x| x[1]}.inject{|p,x| p*=x; p}
|
37
|
+
end while i <= to_date
|
38
|
+
# puts "UNION #{union.inspect}"
|
39
|
+
seq = get_next_seq
|
40
|
+
if type =~ /unique/
|
41
|
+
if aggregate
|
42
|
+
union_key = "#{Rack::RedisAnalytics.redis_namespace}:#{seq}"
|
43
|
+
Rack::RedisAnalytics.redis_connection.sunionstore(union_key, union)
|
44
|
+
Rack::RedisAnalytics.redis_connection.expire(union_key, 100)
|
45
|
+
return Rack::RedisAnalytics.redis_connection.scard(union_key)
|
46
|
+
else
|
47
|
+
return time.zip(union.map{|x| Rack::RedisAnalytics.redis_connection.scard(x)})
|
48
|
+
end
|
49
|
+
elsif type =~ /ratio/
|
50
|
+
if aggregate
|
51
|
+
union_key = "#{Rack::RedisAnalytics.redis_namespace}:#{seq}"
|
52
|
+
Rack::RedisAnalytics.redis_connection.zunionstore(union_key, union)
|
53
|
+
Rack::RedisAnalytics.redis_connection.expire(union_key, 100)
|
54
|
+
return Rack::RedisAnalytics.redis_connection.zrange(union_key, 0, -1, :with_scores => true)
|
55
|
+
else
|
56
|
+
return time.zip(union.map{|x| Rack::RedisAnalytics.redis_connection.zrange(x,0,-1, :with_scores => true)})
|
57
|
+
end
|
58
|
+
else
|
59
|
+
time.zip(Rack::RedisAnalytics.redis_connection.mget(*union).map(&:to_i))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_next_seq
|
64
|
+
seq = Rack::RedisAnalytics.redis_connection.incr("#{Rack::RedisAnalytics.redis_namespace}:#SEQUENCER")
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Rack
|
2
|
+
module RedisAnalytics
|
3
|
+
class Railtie < Rails::Railtie
|
4
|
+
initializer "redis_analytics.middleware" do |app|
|
5
|
+
app.config.middleware.use "Rack::RedisAnalytics::Tracker"
|
6
|
+
end
|
7
|
+
|
8
|
+
initializer "redis_analytics.view_helpers" do
|
9
|
+
ActionController::Base.send :include, Rack::RedisAnalytics::Helpers
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module TimeExtensions
|
2
|
+
%w[ round floor ceil ].each do |_method|
|
3
|
+
define_method _method do |*args|
|
4
|
+
seconds = args.first || 60
|
5
|
+
Time.at((self.to_f / seconds).send(_method) * seconds)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def end_of_minute
|
10
|
+
change(:sec => 59, :usec => 999999.999)
|
11
|
+
end
|
12
|
+
|
13
|
+
def beginning_of_minute
|
14
|
+
change(:sec => 0, :usec => 0)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Time.send :include, TimeExtensions
|
19
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rack
|
2
|
+
module RedisAnalytics
|
3
|
+
class Tracker
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = Rack::Builder.new do
|
7
|
+
map '/' do
|
8
|
+
run Analytics.new(app)
|
9
|
+
end
|
10
|
+
|
11
|
+
if defined? Dashboard and RedisAnalytics.dashboard_endpoint
|
12
|
+
map RedisAnalytics.dashboard_endpoint do
|
13
|
+
run Dashboard.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
@app.call(env)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'redis_analytics/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "redis_analytics"
|
7
|
+
spec.version = Rack::RedisAnalytics::VERSION
|
8
|
+
spec.date = '2013-02-15'
|
9
|
+
spec.authors = ["Schubert Cardozo"]
|
10
|
+
spec.email = ["cardozoschubert@gmail.com"]
|
11
|
+
spec.homepage = "https://github.com/saturnine/redis_analytics"
|
12
|
+
spec.summary = %q{A gem that provides a Redis based web analytics solution for your rack-compliant apps}
|
13
|
+
spec.description = %q{A gem that provides a Redis based web analytics solution for your rack-compliant apps. It gives you detailed analytics about visitors, unique visitors, browsers, OS, visitor recency, traffic sources and more}
|
14
|
+
|
15
|
+
spec.rubyforge_project = "redis_analytics"
|
16
|
+
|
17
|
+
spec.files = Dir.glob("**/*")
|
18
|
+
|
19
|
+
spec.executables = ['redis_analytics_dashboard']
|
20
|
+
spec.default_executable = 'redis_analytics_dashboard'
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_runtime_dependency('rack', '>= 1.4.0')
|
24
|
+
spec.add_runtime_dependency('redis', '>= 3.0.2')
|
25
|
+
spec.add_runtime_dependency('browser', '>= 0.1.6')
|
26
|
+
spec.add_runtime_dependency('sinatra', '>= 1.3.3')
|
27
|
+
spec.add_runtime_dependency('geoip', '>= 1.2.1')
|
28
|
+
spec.add_runtime_dependency('json', '>= 1.7.7')
|
29
|
+
spec.add_runtime_dependency('activesupport', '>= 3.2.0')
|
30
|
+
|
31
|
+
spec.add_development_dependency('rake', '>= 10.0.3')
|
32
|
+
spec.add_development_dependency('rspec', '>= 2.11.0')
|
33
|
+
spec.add_development_dependency('mocha', '>= 0.12.7')
|
34
|
+
spec.add_development_dependency('rack-test', '>= 0.6.2')
|
35
|
+
|
36
|
+
spec.required_ruby_version = '>= 1.9.2'
|
37
|
+
end
|
data/screenshot.png
ADDED
Binary file
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rack::RedisAnalytics::Analytics do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
def app
|
7
|
+
Rack::Builder.app do
|
8
|
+
use Rack::RedisAnalytics::Analytics
|
9
|
+
run Proc.new { |env| [200, {'Content-Type' => 'text/html'}, "Hello!"] }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
@redis_connection = Rack::RedisAnalytics.redis_connection
|
15
|
+
clear_cookies
|
16
|
+
# @redis_connection.flushdb
|
17
|
+
end
|
18
|
+
|
19
|
+
# Spec for Cookies
|
20
|
+
|
21
|
+
context "when a user makes 2 visits and the visit cookie and returning user cookie are not expired" do
|
22
|
+
it "it should count as the same visit in the cookie" do
|
23
|
+
t1 = Time.now
|
24
|
+
Time.stubs(:now).returns(t1)
|
25
|
+
get '/'
|
26
|
+
last_response.original_headers['Set-Cookie'].should =~ Regexp.new("#{Rack::RedisAnalytics.visit_cookie_name}=1\.1\.#{t1.to_i}\.#{t1.to_i}")
|
27
|
+
last_response.original_headers['Set-Cookie'].should =~ Regexp.new("#{Rack::RedisAnalytics.returning_user_cookie_name}=1\.#{t1.to_i}\.#{t1.to_i}")
|
28
|
+
t2 = t1 + 5 # just adding 5 seconds
|
29
|
+
Time.stubs(:now).returns(t2)
|
30
|
+
get '/'
|
31
|
+
last_response.original_headers['Set-Cookie'].should =~ Regexp.new("#{Rack::RedisAnalytics.visit_cookie_name}=1\.1\.#{t1.to_i}\.#{t2.to_i}")
|
32
|
+
last_response.original_headers['Set-Cookie'].should =~ Regexp.new("#{Rack::RedisAnalytics.returning_user_cookie_name}=1\.#{t1.to_i}\.#{t2.to_i}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when a user makes 2 visits, but visit cookie and returning user cookie are both non-existent" do
|
37
|
+
it "should count as a separate and new visit in the cookie" do
|
38
|
+
t1 = Time.now
|
39
|
+
Time.stubs(:now).returns(t1)
|
40
|
+
get '/'
|
41
|
+
last_response.original_headers['Set-Cookie'].should =~ Regexp.new("#{Rack::RedisAnalytics.visit_cookie_name}=1\.1\.#{t1.to_i}\.#{t1.to_i}")
|
42
|
+
last_response.original_headers['Set-Cookie'].should =~ Regexp.new("#{Rack::RedisAnalytics.returning_user_cookie_name}=1\.#{t1.to_i}.#{t1.to_i}")
|
43
|
+
clear_cookies
|
44
|
+
|
45
|
+
t2 = t1 + 5 # just adding 5 seconds
|
46
|
+
Time.stubs(:now).returns(t2)
|
47
|
+
get '/'
|
48
|
+
last_response.original_headers['Set-Cookie'].should =~ Regexp.new("#{Rack::RedisAnalytics.visit_cookie_name}=2\.2\.#{t2.to_i}\.#{t2.to_i}")
|
49
|
+
last_response.original_headers['Set-Cookie'].should =~ Regexp.new("#{Rack::RedisAnalytics.returning_user_cookie_name}=2\.#{t2.to_i}\.#{t2.to_i}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when a user makes 2 visits, and visit cookie is expired but the returning user cookie exists" do
|
54
|
+
it "should count as a separate visit but not a new visit"
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|