Commit a7476257 authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #311 from hypothesis/auth-analytics

Adding auth metrics for login, logout, and signup requested
parents d1063fb8 955923c0
......@@ -41,7 +41,7 @@ var globalGAOptions = function(win, settings){
*/
// @ngInject
function analytics($analytics, $window, settings) {
var options = globalGAOptions($window, settings);
var options = $window ? globalGAOptions($window, settings) : {};
return {
......@@ -67,6 +67,10 @@ function analytics($analytics, $window, settings) {
HIGHLIGHT_CREATED: 'highlightCreated',
HIGHLIGHT_UPDATED: 'highlightUpdated',
HIGHLIGHT_DELETED: 'highlightDeleted',
LOGIN_FAILURE: 'loginFailure',
LOGIN_SUCCESS: 'loginSuccessful',
LOGOUT_FAILURE: 'logoutFailure',
LOGOUT_SUCCESS: 'logoutSuccessful',
PAGE_NOTE_CREATED: 'pageNoteCreated',
PAGE_NOTE_UPDATED: 'pageNoteUpdated',
PAGE_NOTE_DELETED: 'pageNoteDeleted',
......@@ -74,6 +78,7 @@ function analytics($analytics, $window, settings) {
REPLY_UPDATED: 'replyUpdated',
REPLY_DELETED: 'replyDeleted',
SIDEBAR_OPENED: 'sidebarOpened',
SIGN_UP_REQUESTED: 'signUpRequested',
},
};
}
......
......@@ -26,7 +26,7 @@ function authStateFromUserID(userid) {
// @ngInject
module.exports = function AppController(
$document, $location, $rootScope, $route, $scope,
$window, annotationUI, auth, bridge, drafts, features, frameSync, groups,
$window, analytics, annotationUI, auth, bridge, drafts, features, frameSync, groups,
serviceUrl, session, settings, streamer
) {
......@@ -109,6 +109,10 @@ module.exports = function AppController(
scrollToView('login-form');
};
$scope.signUp = function(){
analytics.track(analytics.events.SIGN_UP_REQUESTED);
};
// Display the dialog for sharing the current page
$scope.share = function () {
$scope.shareDialog.visible = true;
......
......@@ -24,6 +24,10 @@ module.exports = {
* Called when the user clicks on the "Log in" text.
*/
onLogin: '&',
/**
* Called when the user clicks on the "Sign Up" text.
*/
onSignUp: '&',
/**
* Called when the user clicks on the "Log out" text.
*/
......
......@@ -3,13 +3,14 @@
var angular = require('angular');
// @ngInject
function Controller($scope, $timeout, flash, session, formRespond, serviceUrl) {
function Controller($scope, $timeout, analytics, flash, session, formRespond, serviceUrl) {
var pendingTimeout = null;
function success(data) {
if (data.userid) {
$scope.$emit('auth', null, data);
}
analytics.track(analytics.events.LOGIN_SUCCESS);
angular.copy({}, $scope.model);
......@@ -30,6 +31,8 @@ function Controller($scope, $timeout, flash, session, formRespond, serviceUrl) {
'Please try again later!';
}
analytics.track(analytics.events.LOGIN_FAILURE);
return formRespond(form, errors, reason);
}
......
......@@ -17,6 +17,10 @@ class MockSession
mockFlash = info: sandbox.spy()
mockFormRespond = sandbox.spy()
mockAnalytics = {
track: sandbox.stub(),
events: require('../../analytics')().events,
}
describe 'loginForm.Controller', ->
$scope = null
......@@ -33,6 +37,7 @@ describe 'loginForm.Controller', ->
beforeEach module ($provide) ->
$provide.value '$timeout', sandbox.spy()
$provide.value 'analytics', mockAnalytics
$provide.value 'flash', mockFlash
$provide.value 'session', new MockSession()
$provide.value 'formRespond', mockFormRespond
......@@ -49,6 +54,7 @@ describe 'loginForm.Controller', ->
afterEach ->
sandbox.restore()
mockAnalytics.track.reset()
describe '#submit()', ->
it 'should call session methods on submit', ->
......@@ -138,6 +144,48 @@ describe 'loginForm.Controller', ->
$scope.$destroy()
assert.calledWith $scope.$emit, 'auth', 'cancel'
describe 'auth analytics', ->
it 'should not track login errors for local validation errors', ->
auth.submit
$name: 'login'
$valid: false
$setValidity: sandbox.stub()
assert.notCalled mockAnalytics.track
it 'should track login error when server sends a reason', ->
# Make a mock session that returns an error response with a "reason" but
# no "errors" in the JSON object.
reason = 'Argh, crashed! :|'
myMockSession = new MockSession()
myMockSession.register = (data, callback, errback) ->
errback({data: {reason: reason}})
$promise: {finally: sandbox.stub()}
# Get an AuthController object with our mock session.
authCtrl = $controller(
'loginFormController', {$scope:$scope, session:myMockSession})
form = {$name: 'register', $valid: true}
authCtrl.submit(form)
assert.calledOnce(mockAnalytics.track)
assert.calledWith(mockAnalytics.track, mockAnalytics.events.LOGIN_FAILURE)
it 'should emit an auth event once authenticated', ->
form =
$name: 'login'
$valid: true
$setValidity: sandbox.stub()
sandbox.spy $scope, '$emit'
auth.submit(form)
assert.calledOnce(mockAnalytics.track)
assert.calledWith(mockAnalytics.track, mockAnalytics.events.LOGIN_SUCCESS)
describe 'timeout', ->
it 'should happen after a period of inactivity', ->
sandbox.spy $scope, '$broadcast'
......
......@@ -9,6 +9,7 @@ module.exports = function () {
isSidebar: '<',
onShowHelpPanel: '&',
onLogin: '&',
onSignUp: '&',
onLogout: '&',
onSharePage: '&',
searchController: '<',
......
......@@ -43,7 +43,7 @@ function sessionActions(options) {
*
* @ngInject
*/
function session($http, $resource, $rootScope, annotationUI, auth,
function session($http, $q, $resource, $rootScope, analytics, annotationUI, auth,
flash, raven, settings, store) {
// Headers sent by every request made by the session service.
var headers = {};
......@@ -208,10 +208,14 @@ function session($http, $resource, $rootScope, annotationUI, auth,
auth.clearCache();
}).catch(function (err) {
flash.error('Log out failed');
throw err;
analytics.track(analytics.events.LOGOUT_FAILURE);
return $q.reject(new Error(err));
}).then(function(){
analytics.track(analytics.events.LOGOUT_SUCCESS);
});
}
return {
dismissSidebarTutorial: dismissSidebarTutorial,
load: resource.load,
......
......@@ -2,6 +2,7 @@
<top-bar
auth="auth"
on-login="login()"
on-sign-up="signUp()"
on-logout="logout()"
on-share-page="share()"
on-show-help-panel="helpPanel.visible = true"
......
......@@ -3,7 +3,7 @@
ng-if="vm.newStyle && vm.auth.status === 'unknown'"></span>
<span class="login-text"
ng-if="vm.newStyle && vm.auth.status === 'logged-out'">
<a href="{{vm.serviceUrl('signup')}}" target="_blank" h-branding="highlightColor">Sign up</a>
<a href="{{vm.serviceUrl('signup')}}" ng-click="vm.onSignUp()" target="_blank" h-branding="highlightColor">Sign up</a>
/ <a href="" ng-click="vm.onLogin()" h-branding="highlightColor">Log in</a>
</span>
<div ng-if="vm.newStyle"
......
......@@ -56,7 +56,8 @@
new-style="true"
on-show-help-panel="onShowHelpPanel()"
on-login="onLogin()"
on-logout="onLogout()">
on-logout="onLogout()"
on-sign-up="onSignUp()">
</login-control>
</div>
</div>
......@@ -69,7 +69,7 @@ describe('AppController', function () {
fakeAnalytics = {
track: sandbox.stub(),
events: {},
events: require('../analytics')().events,
};
fakeAuth = {};
......@@ -256,6 +256,12 @@ describe('AppController', function () {
assert.calledOnce(fakeRoute.reload);
});
it('tracks sign up requests in analytics', function () {
createController();
$scope.signUp();
assert.calledWith(fakeAnalytics.track, fakeAnalytics.events.SIGN_UP_REQUESTED);
});
describe('#login()', function () {
it('shows the login dialog if not using a third-party service', function () {
// If no third-party annotation service is in use then it should show the
......
......@@ -10,6 +10,7 @@ describe('session', function () {
var $httpBackend;
var $rootScope;
var fakeAnalytics;
var fakeAuth;
var fakeFlash;
var fakeRaven;
......@@ -27,6 +28,10 @@ describe('session', function () {
sandbox = sinon.sandbox.create();
var state = {};
fakeAnalytics = {
track: sinon.stub(),
events: require('../analytics')().events,
};
var fakeAnnotationUI = {
getState: function () {
return {session: state};
......@@ -53,6 +58,7 @@ describe('session', function () {
};
mock.module('h', {
analytics: fakeAnalytics,
annotationUI: fakeAnnotationUI,
auth: fakeAuth,
flash: fakeFlash,
......@@ -296,9 +302,10 @@ describe('session', function () {
});
describe('#logout()', function () {
var postExpectation;
beforeEach(function () {
var url = 'https://test.hypothes.is/root/app?__formid__=logout';
$httpBackend.expectPOST(url).respond(200, {
var logoutUrl = 'https://test.hypothes.is/root/app?__formid__=logout';
postExpectation = $httpBackend.expectPOST(logoutUrl).respond(200, {
model: {
userid: 'logged-out-id',
},
......@@ -318,5 +325,22 @@ describe('session', function () {
});
$httpBackend.flush();
});
it('tracks successful logout actions in analytics', function () {
session.logout().then(function () {
assert.calledWith(fakeAnalytics.track, fakeAnalytics.events.LOGOUT_SUCCESS);
});
$httpBackend.flush();
});
it('tracks unsuccessful logout actions in analytics', function () {
postExpectation.respond(500);
session.logout().catch(function(){
assert.calledWith(fakeAnalytics.track, fakeAnalytics.events.LOGOUT_FAILURE);
});
$httpBackend.flush();
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment