diff --git a/resources/static/common/js/modules/interaction_data.js b/resources/static/common/js/modules/interaction_data.js index 8f36514dac7ddc5f6f13dccd4d4e6a593f105ef5..181db90d64aedef85291e5ad943019de8a4133ed 100644 --- a/resources/static/common/js/modules/interaction_data.js +++ b/resources/static/common/js/modules/interaction_data.js @@ -28,6 +28,7 @@ BrowserID.Modules.InteractionData = (function() { var bid = BrowserID, model = bid.Models.InteractionData, network = bid.Network, + storage = bid.Storage, complete = bid.Helpers.complete, dom = bid.DOM, sc; @@ -98,13 +99,64 @@ BrowserID.Modules.InteractionData = (function() { if (self.sessionContextHandled) return; self.sessionContextHandled = true; + publishPreviousSession.call(self, result); + } + + function publishPreviousSession(result) { // Publish any outstanding data. Unless this is a continuation, previous // 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. + // + // beginSampling must happen afterwards, since we need to send and + // then scrub out the previous sessions data. - model.stageCurrent(); - publishStored.call(self); + var self = this; + + function onComplete() { + model.stageCurrent(); + publishStored.call(self); + beginSampling.call(self, result); + } + + // if we were orphaned last time, but user is now authenticated, + // lets see if their action end in success, and if so, + // remove the orphaned flag + // + // actions: + // - user_staged => is authenticated? + // - email_staged => email count is higher? + // + // See https://github.com/mozilla/browserid/issues/1827 + var current = model.getCurrent(); + if (current && current.orphaned) { + var events = current.event_stream || []; + if (hasEvent(events, MediatorToKPINameTable.user_staged)) { + network.checkAuth(function(auth) { + if (!!auth) { + current.orphaned = false; + model.setCurrent(current); + } + complete(onComplete); + }); + } else if (hasEvent(events, MediatorToKPINameTable.email_staged)) { + if ((storage.getEmailCount() || 0) > (current.number_emails || 0)) { + current.orphaned = false; + model.setCurrent(current); + } + complete(onComplete); + } else { + // oh well, an orphan it is + complete(onComplete); + } + } else { + // not an orphan, move along + complete(onComplete); + } + } + + function beginSampling(result) { + var self = this; // set the sample rate as defined by the server. It's a value // between 0..1, integer or float, and it specifies the percentage @@ -152,6 +204,19 @@ BrowserID.Modules.InteractionData = (function() { self.initialEventStream = null; self.samplesBeingStored = true; + + } + + function indexOfEvent(eventStream, eventName) { + for(var event, i = 0; event = eventStream[i]; ++i) { + if(event[0] === eventName) return i; + } + + return -1; + } + + function hasEvent(eventStream, eventName) { + return indexOfEvent(eventStream, eventName) !== -1; } function onKPIData(msg, result) { diff --git a/resources/static/test/cases/common/js/modules/interaction_data.js b/resources/static/test/cases/common/js/modules/interaction_data.js index 1beb33008293d297a40f88cabcd519e042959ef8..eadf72fa0db55e4fa480598c97a47f340104f2ff 100644 --- a/resources/static/test/cases/common/js/modules/interaction_data.js +++ b/resources/static/test/cases/common/js/modules/interaction_data.js @@ -1,5 +1,5 @@ /*jshint browser: true, forin: true, laxbreak: true */ -/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true, asyncTest: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/. */ @@ -9,6 +9,7 @@ var bid = BrowserID, testHelpers = bid.TestHelpers, network = bid.Network, + storage = bid.Storage, model = bid.Models.InteractionData, xhr = bid.Mocks.xhr, mediator = bid.Mediator, @@ -36,18 +37,20 @@ controller = BrowserID.Modules.InteractionData.create(); controller.start(config); - controller.setNameTable({ - before_session_context: null, - after_session_context: null, - session1_before_session_context: null, - session1_after_session_context: null, - session2_before_session_context: null, - session2_after_session_context: null, - initial_string_name: "translated_name", - initial_function_name: function(msg, data) { - return "function_translation." + msg; - } - }); + if (setKPINameTable) { + controller.setNameTable({ + before_session_context: null, + after_session_context: null, + session1_before_session_context: null, + session1_after_session_context: null, + session2_before_session_context: null, + session2_after_session_context: null, + initial_string_name: "translated_name", + initial_function_name: function(msg, data) { + return "function_translation." + msg; + } + }); + } } @@ -69,7 +72,7 @@ // simulate data stored for last session model.push({ timestamp: new Date().getTime() }); - createController(); + createController(true); controller.addEvent("before_session_context"); @@ -122,7 +125,7 @@ }); asyncTest("samplingEnabled set to false - no data collection occurs", function() { - createController({ samplingEnabled: false }); + createController(true, { samplingEnabled: false }); // the initial with_context will send off any stored data, there should be // no stored data. @@ -140,7 +143,7 @@ }); asyncTest("continue: true, data collection permitted on previous session - continue appending data to previous session", function() { - createController(); + createController(true); controller.addEvent("session1_before_session_context"); network.withContext(function() { @@ -150,7 +153,7 @@ // re-get session context. controller = null; network.clearContext(); - createController({ continuation: true }); + createController(true, { continuation: true }); controller.addEvent("session2_before_session_context"); network.withContext(function() { @@ -257,4 +260,124 @@ }); }); + asyncTest("kpi orphans are adopted if user.staged and user is signed in", function() { + // 1. user.user_staged + // 2. dialog is orphaned + // 3. user comes back, authenticated + // 4. the orphan found a good home + createController(false); + network.withContext(function() { + // user is staged + controller.addEvent("user_staged"); + // dialog all done, its orphaned, oh noes! think of the kids! + mediator.publish("kpi_data", { + orphaned: true + }); + network.clearContext(); + + + // new page + createController(false); + // make user authenticated + xhr.setContextInfo("auth_level", "password"); + network.withContext(function() { + var request = xhr.getLastRequest('/wsapi/interaction_data'); + var data = JSON.parse(request.data).data[0]; + equal(data.orphaned, false, "orphaned is not sent"); + start(); + }); + }); + }); + + asyncTest("kpi orphans are NOT adopted if NOT user.staged and user is signed in", function() { + // 1. user was not staged + // 2. dialog is orphaned + // 3. user comes back, authenticated + // 4. but he wasn't staged, so dont adopt + createController(false); + network.withContext(function() { + // dialog all done, its orphaned, oh noes! think of the kids! + mediator.publish("kpi_data", { + orphaned: true + }); + network.clearContext(); + + + // new page + createController(false); + // make user authenticated + xhr.setContextInfo("auth_level", "password"); + network.withContext(function() { + var request = xhr.getLastRequest('/wsapi/interaction_data'); + var data = JSON.parse(request.data).data[0]; + equal(data.orphaned, true, "orphaned is sent"); + start(); + }); + }); + }); + + asyncTest("kpi orphans are adopted if add_email and email count increased", function() { + // 1. email_staged + // 2. dialog is orphaned + // 3. email is verified + // 4. user comes back, authenticated + // 5. the orphan found a good home + createController(false); + network.withContext(function() { + // email is staged + controller.addEvent("email_staged"); + // dialog all done, its orphaned, oh noes! think of the kids! + mediator.publish("kpi_data", { + orphaned: true, + number_emails: storage.getEmailCount() || 0 + }); + network.clearContext(); + + // email is verified + storage.addSecondaryEmail("testuser@testuser.org"); + + // new page + createController(false); + // make user authenticated + xhr.setContextInfo("auth_level", "password"); + network.withContext(function() { + var request = xhr.getLastRequest('/wsapi/interaction_data'); + var data = JSON.parse(request.data).data[0]; + equal(data.orphaned, false, "orphaned is not sent"); + start(); + }); + }); + }); + + asyncTest("kpi orphans are NOT adopted if add_email but email count is same", function() { + // 1. email staged + // 2. dialog is orphaned + // 3. user comes back, authenticated + // 4. but no new email, so oprhan is true + createController(false); + network.withContext(function() { + // user is staged + controller.addEvent("email_staged"); + // dialog all done, its orphaned, oh noes! think of the kids! + mediator.publish("kpi_data", { + orphaned: true, + number_emails: storage.getEmailCount() || 0 + }); + network.clearContext(); + + // user never confirms + + // new page + createController(false); + // make user authenticated + xhr.setContextInfo("auth_level", "password"); + network.withContext(function() { + var request = xhr.getLastRequest('/wsapi/interaction_data'); + var data = JSON.parse(request.data).data[0]; + equal(data.orphaned, true, "orphaned is sent"); + start(); + }); + }); + }); + }());