Skip to content

Commit

Permalink
Create report for password reset flow (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmalkin committed Aug 14, 2012
1 parent 2e624ba commit c37d3a3
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 10 deletions.
6 changes: 6 additions & 0 deletions server/config/config.json
Expand Up @@ -26,6 +26,12 @@
[ "2 - Choose password", "user.user_staged" ],
[ "3 - Email verified", "user.user_confirmed" ],
[ "4 - Logged in (assertion generated)", "assertion_generated" ]
],
"password_reset": [
[ "1 - Enter email", "screen.set_password" ],
[ "2 - Choose new password", "screen.check_registration" ],
[ "3 - Email verified", "generate_assertion" ],
[ "4 - Logged in (assertion generated)", "assertion_generated" ]
]
},
"milestones": [
Expand Down
9 changes: 9 additions & 0 deletions server/lib/api.js
Expand Up @@ -142,3 +142,12 @@ exports.new_user_time = function(req, res) {
});
};

exports.password_reset = function(req, res) {
var start = getIntParamFromURL(req.url, 'start'),
end = getIntParamFromURL(req.url, 'end');

reports.password_reset(start, end, function(result) {
resultToResponse(result, res);
});
};

32 changes: 32 additions & 0 deletions server/lib/data.js
Expand Up @@ -192,3 +192,35 @@ exports.newUserStepNames = function() {
return step[0];
});
};

/**
* Returns the names of all steps in the password reset flow that were completed
* in the given data point.
*/
exports.passwordResetSteps = function(datum) {
var steps = [];
var events = eventList(datum);

if( (events.indexOf('screen.check_registration') === -1) ||
(events.indexOf('screen.set_password') === -1) ||
(events.indexOf('user.email_staged') !== -1) ||
(events.indexOf('user.user_staged') !== -1) )
{ // not a password reset
return steps;
}

config.flows.password_reset.forEach(function(step) {
if(events.indexOf(step[1]) !== -1) {
steps.push(step[0]);
}
});

return steps;
};

/** Returns the names of all steps in the password reset flow */
exports.passwordResetStepNames = function() {
return config.flows.password_reset.map(function(step) {
return step[0];
});
};
63 changes: 63 additions & 0 deletions server/lib/db.js
Expand Up @@ -75,6 +75,68 @@ var VIEWS = {
}
},

password_reset: {
map: function(doc) {
if(doc.passwordResetSteps.length > 0) {
emit(doc.date, doc.passwordResetSteps);
}
},

reduce: function(keys, values, rereduce) {
if(rereduce) { // Merge the objects that are the results of the reductions
var initial = {
steps: {},
total: 0
};

return values.reduce(function(accumulated, current) {
var steps = Object.keys(current.steps);
steps.forEach(function(step) {
if(! (step in accumulated.steps)) {
accumulated.steps[step] = 0;
}

// The fraction of users who completed this step is the
// weighted average of the results being merged.
var total = accumulated.total + current.total;
accumulated.steps[step] = current.steps[step] * current.total / total +
accumulated.steps[step] * accumulated.total / total;

accumulated.total = total;
});

return accumulated;
}, initial);
} else {
var steps = {};

// Count the number of times each step has been completed
values.forEach(function(userSteps) {
userSteps.forEach(function(step) {
if(! (step in steps)) {
steps[step] = 0;
}

steps[step]++;
});
});

// Compute fraction of users completing steps
var total = values.length;
for(var step in steps) {
if(steps.hasOwnProperty(step)) {
steps[step] /= total;
}
}

return {
steps: steps,
total: total
};
}
}
},

new_user: {
map: function(doc) {
if(doc.newUserSteps.length > 0) { // Only count new users
Expand Down Expand Up @@ -329,6 +391,7 @@ exports.populateDatabase = function() {

// Pre-compute certain values for the report (not already in the datum)
datum.value.newUserSteps = data.newUserSteps(datum);
datum.value.passwordResetSteps = data.passwordResetSteps(datum);
datum.value.date = data.getDate(datum);

// Handle all kinds of sites-logged-in KPIs
Expand Down
44 changes: 44 additions & 0 deletions server/lib/reports.js
Expand Up @@ -227,3 +227,47 @@ exports.new_user_time = function(start, end, callback) {
callback(dataByStep);
});
};

exports.password_reset = function(start, end, callback) {
var dbOptions = {
group: true
};

// Convert timestamps to dates
if(start) {
dbOptions.startkey = util.getDateStringFromUnixTime(start);
}
if(end) {
dbOptions.endkey = util.getDateStringFromUnixTime(end);
}

db.view('password_reset', dbOptions, function(dataByDate) {
// Pivot data
// (so that it's organized by step, then date; rather than date, step

// Set up container object
var dataByStep = {};
var steps = data.passwordResetStepNames();
steps.forEach(function(step) {
dataByStep[step] = {};
});

dataByDate.forEach(function(datum) {
var date = datum.key;

steps.forEach(function(step) {
var value;
if(! (step in datum.value.steps)) { // No data about this step
// That means no one completed it.
value = 0;
} else {
value = datum.value.steps[step];
}

dataByStep[step][date] = value;
});
});

callback(dataByStep);
});
};
41 changes: 34 additions & 7 deletions static/index.html
Expand Up @@ -25,31 +25,31 @@
margin-top: 15px;
}

#new_user_time .chart {
.step-report .chart {
width: 800px;
margin: 20px auto;
}

#new_user_time .legend {
.step-report .legend {
float: right;
width: 300px;
padding: 10px;
border: 1px solid #000;
}

#new_user_time path {
.step-report path {
stroke-width: 3px;
fill: none;
}
#new_user_time .tick line {
.step-report .tick line {
stroke-width: 1px;
stroke: #999;
}
#new_user_time .tick text {
.step-report .tick text {
fill: #000;
font-size: 10px;
}
#new_user_time .x-tick text {
.step-report .x-tick text {
transform: 'rotate(90, 0, 0)';
}
</style>
Expand All @@ -67,6 +67,9 @@ <h1><a class="brand">KPI Dashboard</a></h1>
<li class="active">
<a href="#new_user_time" data-toggle="tab">New user flow over time</a>
</li>
<li>
<a href="#password_reset" data-toggle="tab">Password reset flow</a>
</li>
<li>
<a href="#new_user" data-toggle="tab">New user flow</a>
</li>
Expand All @@ -79,7 +82,7 @@ <h1><a class="brand">KPI Dashboard</a></h1>
</ul>

<div class="tab-content">
<div id="new_user_time" class="tab-pane active form-inline">
<div id="new_user_time" class="step-report tab-pane active form-inline">
<h2>Can new users log in?</h2>
<p>
Tracks percentage of new users proceeding through each step of the sign-in flow over time.
Expand All @@ -101,6 +104,30 @@ <h4>Legend</h4>
<div class="slider date-slider" style="width: 400px; margin: -5px 0 30px"></div>
</div>

</div>

<div id="password_reset" class="step-report tab-pane form-inline">
<h2>Can users reset their password?</h2>
<p>
Tracks percentage of users proceeding through each step of the password reset flow.
</p>

<div class="chart"></div>

<div class="legend">
<h4>Legend</h4>
</div>

<div class="date-select">
<p>
From
<input type="date" class="start" placeholder="YYYY-MM-DD">
to
<input type="date" class="end" placeholder="YYYY-MM-DD">
</p>
<div class="slider date-slider" style="width: 400px; margin: -5px 0 30px"></div>
</div>

</div>

<div id="new_user" class="tab-pane form-inline">
Expand Down
23 changes: 20 additions & 3 deletions static/js/dashboard.js
Expand Up @@ -42,6 +42,21 @@ var _reports = {
padding: { vertical: 100, horizontal: 0 }
}
},
password_reset:
{
kpi: 'password_reset',
id: '#password_reset',
tab: $('#password_reset'),
dataToSeries: function(d) { return d; },
update: null,
start: dateToTimestamp(EARLIEST_DATE),
end: dateToTimestamp(LATEST_DATE),
dimensions: {
width: 700,
height: 600,
padding: { vertical: 100, horizontal: 0 }
}
},
// Report: new user flow
new_user:
{
Expand Down Expand Up @@ -572,7 +587,7 @@ getData('milestones', {}, function(data) {

// Set up report for new user flow over time

(function(report) {
var stepReport = function(report) {
loadData(report, function() {
// Set up the svg element
var chart = d3.select(report.id + ' .chart')
Expand Down Expand Up @@ -633,7 +648,7 @@ getData('milestones', {}, function(data) {
.text(function(d) { return d; })
;

_reports.new_user_time.update = function(report) {
report.update = function(report) {
var rawData = report.series;

var dates = Object.keys(rawData[steps[0]]).sort();
Expand Down Expand Up @@ -719,7 +734,9 @@ getData('milestones', {}, function(data) {

report.update(report);
});
})(_reports.new_user_time);
};
stepReport(_reports.new_user_time);
stepReport(_reports.password_reset);


// Set up new user flow report
Expand Down

0 comments on commit c37d3a3

Please sign in to comment.