Pluf Framework

Pluf Framework Commit Details


Date:2010-03-24 16:15:29 (14 years 8 months ago)
Author:Loic d'Anterroches
Branch:develop, master
Commit:00fd702b875e22644cf1d791ed984dbc2cc9693e
Parents: 8345dbaaab7d4ac54fa4d83a9b01af2d38cb2a2b
Message:Added a funnel tracking system.

Changes:

File differences

src/Pluf/AB.php
164164
165165
166166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
167231
168232
169233
......
179243
180244
181245
246
247
248
249
182250
183251
184252
......
196264
197265
198266
267
268
269
270
271
272
199273
200274
201275
}
/**
* Register a property set for the user.
*
* This allows you to segment your users with these properties.
*
* @param $request Pluf_HTTP_Request
* @param $props array Properties
*/
public static function register($request, $props)
{
$pabuid = (isset($request->pabuid)) ?
$request->pabuid :
self::getUid($request);
if ($pabuid == 'bot') {
return;
}
$request->pabuid = $pabuid;
$request->pabprops = array_merge($request->pabprops, $props);
}
/**
* Track a funnel.
*
* The array of properties can be used to track different A/B
* testing cases.
*
* The list of properties must be the same at all the steps of the
* funnel, you cannot pass array('gender' => 'M') at step 1 and
* array('age' => 32) at step 2. You need to pass both of them at
* all steps.
*
* @param $funnel string Name of the funnel
* @param $step int Step in the funnel, from 1 to n
* @param $stepname string Readable name for the step
* @param $request Pluf_HTTP_Request Request object
* @param $props array Array of properties associated with the funnel (array())
*/
public static function trackFunnel($funnel, $step, $stepname, $request, $props=array())
{
$pabuid = (isset($request->pabuid)) ?
$request->pabuid :
self::getUid($request);
if ($pabuid == 'bot') {
return;
}
$request->pabuid = $pabuid;
$cache = Pluf_Cache::factory();
$key = 'pluf_ab_funnel_'.crc32($funnel.'#'.$step.'#'.$pabuid);
if ($cache->get($key, false)) {
return; // The key is valid 60s not to track 2 steps within 60s
}
$cache->set($key, '1', 60);
$what = array(
'f' => $funnel,
's' => $step,
'sn' => $stepname,
't' => (int) gmdate('Ymd', $request->time),
'u' => $pabuid,
'p' => array_merge($request->pabprops, $props),
);
$db = self::getDb();
$db->funnellogs->insert($what);
}
/**
* Process the response of a view.
*
* If the request has no cookie and the request has a pabuid, set
and $request->pabuid != 'bot') {
$response->cookies['pabuid'] = $request->pabuid;
}
if (isset($request->pabprops) and count($request->pabprops)
and $request->pabuid != 'bot') {
$response->cookies['pabprops'] = Pluf_Sign::dumps($request->pabprops, null, true);
}
return $response;
}
self::check_uid($request->COOKIE['pabuid'])) {
$request->pabuid = $request->COOKIE['pabuid'];
}
if (isset($request->COOKIE['pabprops'])) {
try {
$request->pabprops = Pluf_Sign::loads($request->COOKIE['pabprops']);
} catch (Exception $e) {
}
}
return false;
}
src/Pluf/AB/Funnel.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of Plume Framework, a simple PHP Application Framework.
# Copyright (C) 2001-2010 Loic d'Anterroches and contributors.
#
# Plume Framework is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Plume Framework is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Funnel statistics.
*
* Funnels are easy to track but not that easy to generate statistics
* out of them.
*
* Stats are compiled "by GMT day", so you can track your funnel per
* day, week or more. Stats are put in cache in the "funnels" collection.
*
*/
class Pluf_AB_Funnel
{
/**
* Returns the list of funnels.
*
* @return array Funnels
*/
public static function getFunnels()
{
$db = Pluf_AB::getDb();
foreach (array('f', 't') as $k) {
// Once created, it will return immediately in the future
// calls so the overhead is negligeable.
$db->funnellogs->ensureIndex(array($k => 1),
array('background' => true));
}
$nf = $db->command(array('distinct' => 'funnellogs', 'key' => 'f'));
if ((int) $nf['ok'] == 1) {
sort($nf['values']);
return $nf['values'];
}
return array();
}
/**
* Get stats for a given funnel.
*
* @param $funnel string Funnel
* @param $period string Time period 'yesterday', ('today'), '7days', 'all'
* @param $prop string Property to filter (null)
*/
public static function getStats($funnel, $period='today', $prop=null)
{
$db = Pluf_AB::getDb();
$steps = array();
for ($i=1;$i<=20;$i++) {
$steps[$i] = array();
}
switch ($period) {
case 'yesterday':
$q = array('t' => array('$eq' => (int) gmdate('Ymd', time()-86400)));
break;
case 'today':
$q = array('t' => (int) gmdate('Ymd'));
break;
case '7days':
$q = array('t' => array('$gte' => (int) gmdate('Ymd', time()-604800)));
break;
case 'all':
default:
$q = array();
break;
}
$q['f'] = $funnel;
$uids = array();
// With very big logs, we will need to find by schunks, this
// will be very easy to adapt.
foreach ($db->funnellogs->find($q) as $log) {
if (!isset($uids[$log['u'].'##'.$log['s']])) {
$uids[$log['u'].'##'.$log['s']] = true;
$step = $log['s'];
$steps[$step]['name'] = $log['sn'];
if ($prop and !isset($steps[$step]['props'])) {
$steps[$step]['props'] = array();
}
$steps[$step]['total'] = (isset($steps[$step]['total'])) ?
$steps[$step]['total'] + 1 : 1;
if ($prop) {
$steps[$step]['props'][$log['p'][$prop]] = (isset($steps[$step]['props'][$log['p'][$prop]])) ?
$steps[$step]['props'][$log['p'][$prop]] + 1 : 1;
}
}
}
// Now, compile the stats for steps 2 to n
for ($i=2;$i<=20;$i++) {
if ($steps[$i] and $steps[$i-1]) {
//$steps[$i]['conv'] = sprintf('%d', (float)$steps[$i-1]['total']/$steps[$i]['total']*100.0);
$steps[$i]['conv'] = sprintf('%01.2f%%', (float)$steps[$i-1]['total']/$steps[$i]['total']*100.0);
$steps[$i]['conv1'] = sprintf('%01.2f%%', (float)$steps[$i-1]['total']/$steps[1]['total']*100.0);
}
}
return $steps;
}
}
src/Pluf/AB/Views.php
8787
8888
8989
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
90122
91123
92124
}
/**
* Display the list of funnels.
*
*/
public $funnels_precond = array(array('Pluf_Precondition::hasPerm',
'Pluf_AB.view-funnels'));
public function funnels($request, $match)
{
$url = Pluf_HTTP_URL_urlForView('pluf_ab_funnels');
$funnels = Pluf_AB_Funnel::getFunnels();
return Pluf_Shortcuts_RenderToResponse('pluf/ab/funnels.html',
array('funnels' => $funnels,
),
$request);
}
/**
* Display a given funnel stats.
*
*/
public $funnel_precond = array(array('Pluf_Precondition::hasPerm',
'Pluf_AB.view-funnels'));
public function funnel($request, $match)
{
$stats = Pluf_AB_Funnel::getStats($match[1]);
return Pluf_Shortcuts_RenderToResponse('pluf/ab/funnel.html',
array('stats' => $stats,
'funnel' => $match[1],
),
$request);
}
/**
* A simple view to redirect a user and convert it.
*
* To convert the user for the test 'my_test' and redirect it to
src/Pluf/templates/pluf/ab/base.html
1313
1414
1515
16
16
1717
1818
1919
......
6666
6767
6868
69
70
71
72
6973
7074
7175
7276
77
78
79
80
81
82
83
84
85
86
87
88
89
90
7391
7492
7593
background: #fff;
font-family: Lucida Grande, Verdana, sans-serif;
padding: 1em 2em;
margin-left: 100px;
magrin-left: 100px;
width: 600px;
}
font-size: 0.8em;
}
.right {
text-align: right;
}
p.note {
margin-top: 2em;
}
.percent {
display: block;
float: left;
height: 10px;
width: 100%;
background-color: #e9b96e;
border: 1px solid #888a85;
}
.percent span {
background-color: #3465a4;
display:block;
float:left;
height:10px;
}
{/literal}
</style>
</head>
src/Pluf/templates/pluf/ab/funnel.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{extends "pluf/ab/base.html"}
{block body}
<h1>Funnel {$funnel}</h1>
{assign $i=1}
{foreach $stats as $step}{if $step}
{if $i>1}
<p class="right">{$step['total']} ({$step['conv']})</p>
{/if}
<h2>Step {$i}: {$step['name']}</h2>
<p>
{if $i>1}
<span class="percent"><span style="width:{$step['conv1']}">&nbsp;</span></span>
{else}
<span class="percent"><span style="width:100%">&nbsp;</span></span>
{/if}<br />{assign $t = $step['total']}
{blocktrans $t}{$t} unique visitor.{plural}{$t} uniques visitors.{/blocktrans}
</p>
{assign $i += 1}
{/if}{/foreach}
<hr />
{assign $i = $i-1}
<p><strong>{$stats[$i]['total']}</strong> out of {$stats['1']['total']} visitors reached step {$i} of this funnel.
This is a completion rate of <strong>{$stats[$i]['conv1']}</strong>.</p>
<p class="note">
Note that if a user skip a given step, this can make the results a bit
off.
</p>
{/block}
src/Pluf/templates/pluf/ab/funnels.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{extends "pluf/ab/base.html"}
{block body}
<h1>Funnel Dashboard</h1>
{if count($funnels)}
<ul>{foreach $funnels as $funnel}
<li><a href="{url 'pluf_ab_funnel', array($funnel)}">{$funnel}</a></li>
{/foreach}</ul>
{else}
<p>You no funnel analysis running at the moment.</p>
{/if}
{/block}

Archive Download the corresponding diff file

Branches

Tags

Number of commits:
Page rendered in 0.08996s using 13 queries.