Skip to content
This repository has been archived by the owner on May 10, 2019. It is now read-only.

Commit

Permalink
Merge branch 'dev' into blur_interaction_data
Browse files Browse the repository at this point in the history
  • Loading branch information
jedp committed May 22, 2012
2 parents 9ba1347 + c243137 commit 3d6fb56
Show file tree
Hide file tree
Showing 11 changed files with 459 additions and 261 deletions.
3 changes: 3 additions & 0 deletions lib/static_resources.js
Expand Up @@ -80,6 +80,9 @@ var dialog_js = und.flatten([
'/shared/history.js',
'/shared/state_machine.js',

'/shared/models/models.js',
'/shared/models/interaction_data.js',

'/shared/modules/interaction_data.js',

'/dialog/resources/internal_api.js',
Expand Down
163 changes: 163 additions & 0 deletions resources/static/shared/models/interaction_data.js
@@ -0,0 +1,163 @@
/*globals BrowserID: true */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

BrowserID.Models.InteractionData = (function() {
"use strict";

var bid = BrowserID,
storage = bid.getStorage(),
network = bid.Network,
complete = bid.Helpers.complete;

function getInteractionData() {
var interactionData;
try {
interactionData = JSON.parse(storage.interaction_data);
} catch(e) {
}

return interactionData || {};
}

function setInteractionData(data) {
try {
storage.interaction_data = JSON.stringify(data);
} catch(e) {
storage.removeItem("interaction_data");
}
}

function push(newData) {
stageCurrent();

var interactionData = getInteractionData();
interactionData.current = newData;

setInteractionData(interactionData);
}

function getCurrent() {
var interactionData = getInteractionData();

return interactionData.current;
}

function setCurrent(data) {
var interactionData = getInteractionData();
interactionData.current = data;
setInteractionData(interactionData);
}

function stageCurrent() {
// Push existing current data to the staged list. This allows
// us to get/clear the staged list without affecting the current data.
var interactionData = getInteractionData();

if (interactionData.current) {
var staged = interactionData.staged = interactionData.staged || [];
staged.unshift(interactionData.current);

delete interactionData.current;

setInteractionData(interactionData);
}
}

function getStaged() {
var interactionData = getInteractionData();
return interactionData.staged || [];
}

function clearStaged() {
var interactionData = getInteractionData();
delete interactionData.staged;
setInteractionData(interactionData);
}

// We'll try to publish past interaction data to the server if it exists.
// The psuedo transactional model employed here is to attempt to post, and
// only once we receive a server response do we purge data. We don't
// care if the post is a success or failure as this data is not
// critical to the functioning of the system (and some failure scenarios
// simply won't resolve with retries - like corrupt data, or too much
// data)
function publishStaged(oncomplete) {
var data = getStaged();

// XXX: should we even try to post data if it's larger than some reasonable
// threshold?
if (data && data.length !== 0) {
network.sendInteractionData(data, function() {
clearStaged();
complete(oncomplete, true);
}, function(status) {
// if the server returns a 413 error, (too much data posted), then
// let's clear our local storage and move on. This does mean we
// loose some interaction data, but it shouldn't be statistically
// significant.
if (status && status.network && status.network.status === 413) {
clearStaged();
}
complete(oncomplete, false);
});
}
else {
complete(oncomplete, false);
}
}

return {
/**
* add a new interaction blob to localstorage, this will *push* any stored
* blobs to the 'staged' backlog, and happens when a new dialog interaction
* begins.
* @method push
* @param {object} data - an object to push onto the queue
* @returns nada
*/
push: push,
/**
* read the interaction data blob associated with the current interaction
* @method getCurrent
* @returns a JSON object containing the latest interaction data blob
*/
getCurrent: getCurrent,
/**
* overwrite the interaction data blob associated with the current interaction
* @method setCurrent
* @param {object} data - the object to overwrite current with
*/
setCurrent: setCurrent,
/**
* Shift any "current" data into the staged list. No data will be listed
* as current afterwards.
* @method stageCurrent
*/
stageCurrent: stageCurrent,
/**
* get all past saved interaction data (returned as a JSON array), excluding
* the "current" data (that which is being collected now).
* @method getStaged
* @returns an array, possibly of length zero if no past interaction data is
* available
*/
getStaged: getStaged,
/**
* publish staged data. Staged data will be cleared if successfully posted
* to server or if server returns 413 - too much data.
* @param {function} [oncomplete] - function to call when complete. Called
* with true if data was successfully sent to server, false otw.
* @method publishStaged
*/
publishStaged: publishStaged,
/**
* clear all interaction data, except the current, in-progress
* collection.
* @method clearStaged()
*/
clearStaged: clearStaged
};

}());
7 changes: 7 additions & 0 deletions resources/static/shared/models/models.js
@@ -0,0 +1,7 @@
/*globals BrowserID: true */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

BrowserID.Models = {};

105 changes: 49 additions & 56 deletions resources/static/shared/modules/interaction_data.js
Expand Up @@ -24,7 +24,7 @@

BrowserID.Modules.InteractionData = (function() {
var bid = BrowserID,
storage = bid.Storage.interactionData,
model = bid.Models.InteractionData,
network = bid.Network,
complete = bid.Helpers.complete,
dom = bid.DOM,
Expand All @@ -41,7 +41,9 @@ BrowserID.Modules.InteractionData = (function() {
// session data must be published independently of whether the current
// dialog session is allowed to sample data. This is because the original
// dialog session has already decided whether to collect data.
publishStored();

model.stageCurrent();
publishStored.call(self);

// set the sample rate as defined by the server. It's a value
// between 0..1, integer or float, and it specifies the percentage
Expand Down Expand Up @@ -78,63 +80,63 @@ BrowserID.Modules.InteractionData = (function() {
// as soon as the first session_context completes for the next dialog
// session. Use a push because old data *may not* have been correctly
// published to a down server or erroring web service.
storage.push(currentData);
model.push(currentData);

self.initialEventStream = null;

self.samplesBeingStored = true;
}

// At every load, after session_context returns, we'll try to publish
// past interaction data to the server if it exists. The psuedo
// transactional model employed here is to attempt to post, and only
// once we receive a server response do we purge data. We don't
// care if the post is a success or failure as this data is not
// critical to the functioning of the system (and some failure scenarios
// simply won't resolve with retries - like corrupt data, or too much
// data)
// At every load, after session_context returns, try to publish the previous
// data. We have to wait until session_context completes so that we have
// a csrf token to send.
function publishStored(oncomplete) {
var data = storage.get();

// XXX: should we even try to post data if it's larger than some reasonable
// threshold?
if (data && data.length !== 0) {
network.sendInteractionData(data, function() {
storage.clear();
complete(oncomplete, true);
}, function(status) {
// if the server returns a 413 error, (too much data posted), then
// let's clear our local storage and move on. This does mean we
// loose some interaction data, but it shouldn't be statistically
// significant.
if (status && status.network && status.network.status === 413) {
storage.clear();
}
complete(oncomplete, false);
});
}
else {
complete(oncomplete, false);
}
var self=this;

model.publishStaged(function(status) {
var msg = status ? "interaction_data_send_complete" : "interaction_data_send_error";
self.publish(msg);
complete(oncomplete, status);
});
}


function addEvent(eventName) {
var self=this;

if (self.samplingEnabled === false) return;

var eventData = [ eventName, new Date() - self.startTime ];
if (self.samplesBeingStored) {
var d = storage.current() || {};
var d = model.getCurrent() || {};
if (!d.event_stream) d.event_stream = [];
d.event_stream.push(eventData);
storage.setCurrent(d);
model.setCurrent(d);
} else {
self.initialEventStream.push(eventData);
}
}

function getCurrent() {
var self=this;
if(self.samplingEnabled === false) return;

if (self.samplesBeingStored) {
return model.getCurrent();
}
}

function getCurrentEventStream() {
var self=this;
if(self.samplingEnabled === false) return;

if (self.samplesBeingStored) {
return model.getCurrent().event_stream;
}
else {
return self.initialEventStream;
}
}

var Module = bid.Modules.PageModule.extend({
start: function(options) {
options = options || {};
Expand All @@ -147,29 +149,28 @@ BrowserID.Modules.InteractionData = (function() {
// a continuation, samplingEnabled will be decided on the first "
// context_info" event, which corresponds to the first time
// 'session_context' returns from the server.
// samplingEnabled flag ignored for a continuation.
self.samplingEnabled = options.samplingEnabled;

// continuation means the users dialog session is continuing, probably
// due to a redirect to an IdP and then a return after authentication.
if (options.continuation) {
var previousData = storage.current();

var samplingEnabled = self.samplingEnabled = !!previousData.event_stream;
if (samplingEnabled) {
// There will be no current data if the previous session was not
// allowed to save.
var previousData = model.getCurrent();
if (previousData) {
self.startTime = Date.parse(previousData.local_timestamp);

if (typeof self.samplingEnabled === "undefined") {
self.samplingEnabled = samplingEnabled;
}

// instead of waiting for session_context to start appending data to
// localStorage, start saving into localStorage now.
self.samplesBeingStored = true;
self.samplingEnabled = self.samplesBeingStored = true;
}
else {
// If there was no previous event stream, that means data collection
// If there was no previous data, that means data collection
// was not allowed for the previous session. Return with no further
// action, data collection is not allowed for this session either.
self.samplingEnabled = false;
return;
}
}
Expand All @@ -179,7 +180,7 @@ BrowserID.Modules.InteractionData = (function() {
// The initialEventStream is used to store events until onSessionContext
// is called. Once onSessionContext is called and it is known whether
// the user's data will be saved, initialEventStream will either be
// discarded or added to the data set that is saved to localStorage.
// discarded or added to the data set that is saved to localmodel.
self.initialEventStream = [];
self.samplesBeingStored = false;

Expand All @@ -194,16 +195,8 @@ BrowserID.Modules.InteractionData = (function() {
},

addEvent: addEvent,

getCurrentStoredData: function() {
var und;
return this.samplesBeingStored ? storage.current() : und;
},

getEventStream: function() {
return this.samplesBeingStored ? storage.current().event_stream : this.initialEventStream || [];
},

getCurrent: getCurrent,
getCurrentEventStream: getCurrentEventStream,
publishStored: publishStored
});

Expand Down

0 comments on commit 3d6fb56

Please sign in to comment.