diff --git a/app/assets/javascripts/account/AccountController.js.coffee b/app/assets/javascripts/account/AccountController.js.coffee index ce5ad9d31e..195ef25531 100644 --- a/app/assets/javascripts/account/AccountController.js.coffee +++ b/app/assets/javascripts/account/AccountController.js.coffee @@ -37,6 +37,11 @@ AccountController = ( $scope.passwordRegex = /^(?=.*[0-9])(?=.*[a-zA-Z])(.+){8,}$/ $scope.emailRegex = SharedService.emailRegex + $scope.logBackLinkClick = -> + path = window.location.pathname + listingId = path.split('/')[2] + AnalyticsService.trackApplicationStart(listingId || "", null, "Continue with application from save and exit") + $scope.accountForm = -> # pick up which ever one is defined (the other will be undefined) $scope.form.signIn || diff --git a/app/assets/javascripts/account/directives/back-to-application-link.html.slim b/app/assets/javascripts/account/directives/back-to-application-link.html.slim index 4f55a79736..34bd666d56 100644 --- a/app/assets/javascripts/account/directives/back-to-application-link.html.slim +++ b/app/assets/javascripts/account/directives/back-to-application-link.html.slim @@ -1,5 +1,5 @@ .back-link-container.small-only-text-center ng-if="rememberedShortFormState" - a.back-link ui-sref="{{rememberedShortFormState}}" + a.back-link(ui-sref="{{rememberedShortFormState}}" ng-click="logBackLinkClick()") span.ui-icon.ui-static.ui-medium.i-primary svg use xlink:href="#i-left" diff --git a/app/assets/javascripts/config/angularInitialize.js.coffee b/app/assets/javascripts/config/angularInitialize.js.coffee index 41c2ec8ea2..21bd422f1d 100644 --- a/app/assets/javascripts/config/angularInitialize.js.coffee +++ b/app/assets/javascripts/config/angularInitialize.js.coffee @@ -136,6 +136,9 @@ onConfirm: -> # fires only if user clicks 'ok' to leave page # reloads this stateChangeStart method with skipConfirm true + AnalyticsService.trackApplicationAbandon(ShortFormApplicationService.listing.listingID, null, "Leaving for " + toState.name) + $window.removeEventListener('unload', $rootScope.onUnload) + toParams.skipConfirm = true $state.go(toState.name, toParams) ) diff --git a/app/assets/javascripts/config/angularRoutes.js.coffee b/app/assets/javascripts/config/angularRoutes.js.coffee index 42de8276c7..533c74c1bb 100644 --- a/app/assets/javascripts/config/angularRoutes.js.coffee +++ b/app/assets/javascripts/config/angularRoutes.js.coffee @@ -592,8 +592,8 @@ ] application: [ # 'listing' is part of the params so that application waits for listing (above) to resolve - '$q', '$stateParams', '$state', 'ShortFormApplicationService', 'AccountService', 'AutosaveService', 'listing' - ($q, $stateParams, $state, ShortFormApplicationService, AccountService, AutosaveService, listing) -> + '$q', '$stateParams', '$state', 'ShortFormApplicationService', 'AccountService', 'AutosaveService', 'AnalyticsService', 'listing' + ($q, $stateParams, $state, ShortFormApplicationService, AccountService, AutosaveService, AnalyticsService, listing) -> deferred = $q.defer() # if the user just clicked the language switcher, don't reload the whole route @@ -620,6 +620,7 @@ if ShortFormApplicationService.application.status == 'Submitted' # send them to their review page if the application is already submitted + AnalyticsService.trackApplicationComplete(listing.Id, AccountService.loggedInUser?.id || null, "Application already submitted") $state.go('dahlia.short-form-review', {id: ShortFormApplicationService.application.id}) else if ShortFormApplicationService.application.autofill == true $state.go('dahlia.short-form-application.autofill-preview', {id: listing.Id, lang: $stateParams.lang}) diff --git a/app/assets/javascripts/shared/AnalyticsService.js.coffee b/app/assets/javascripts/shared/AnalyticsService.js.coffee index 7f39bb66f2..ef3a8e9bc8 100644 --- a/app/assets/javascripts/shared/AnalyticsService.js.coffee +++ b/app/assets/javascripts/shared/AnalyticsService.js.coffee @@ -6,15 +6,32 @@ AnalyticsService = ($state) -> Service = {} Service.timer = {} + Service.resetProperties = { + label: undefined + event: undefined + event_timestamp: undefined + category: undefined + action: undefined + origin: undefined + reason: undefined + eventTimeout: undefined + fieldId: undefined + user_id: undefined + time_to_abandon: undefined + time_to_submit: undefined + listing_id: undefined + } + Service.trackEvent = (event, properties) -> dataLayer = window.dataLayer || [] - unless properties.label + combinedProperties = Object.assign({}, Service.resetProperties, properties) + unless combinedProperties.label # by default, grab the end of the URL e.g. the "contact" from "/x/y/z/contact" current_path = _.first(_.last($state.current.url.split('/')).split('?')) - properties.label = current_path - properties.event = event - properties.event_timestamp = Date.now() - dataLayer.push(properties) + combinedProperties.label = current_path + combinedProperties.event = event + combinedProperties.event_timestamp = Date.now() + dataLayer.push(combinedProperties) # Tracks the current page as the user navigates Service.trackCurrentPage = -> @@ -69,19 +86,19 @@ AnalyticsService = ($state) -> Service.trackEvent('Form Message', params) Service.trackApplicationStart = (listingId, userId = null, origin = null) -> - params = { category: "Application", action: 'Application Start', user_id: userId, listing_id: listingId, origin: origin } + params = { user_id: userId, listing_id: listingId, origin: origin } Service.createApplicationTimer(listingId) - Service.trackEvent('Analytics Message', params) + Service.trackEvent('Application_Start', params) - Service.trackApplicationComplete = (listingId, userId = null) -> + Service.trackApplicationComplete = (listingId, userId = null, reason = null) -> time_to_submit = Service.getApplicationDuration(listingId) - params = { category: "Application", action: 'Application Complete', user_id: userId, listing_id: listingId, time_to_submit: time_to_submit } - Service.trackEvent('Analytics Message', params) + params = { user_id: userId, listing_id: listingId, time_to_submit: time_to_submit, reason: reason } + Service.trackEvent('Application_Complete', params) Service.trackApplicationAbandon = (listingId, userId = null, reason = null) -> time_to_abandon = Service.getApplicationDuration(listingId) - params = { category: "Application", action: 'Application Abandon', user_id: userId, listing_id: listingId, time_to_abandon: time_to_abandon, reason: reason } - Service.trackEvent('Analytics Message', params) + params = { user_id: userId, listing_id: listingId, time_to_abandon: time_to_abandon, reason: reason } + Service.trackEvent('Application_Abandon', params) # Distinct from trackFormFieldError, this function is called when there is a validation error on the form # For example, if the user has an income that is out of bounds diff --git a/app/assets/javascripts/short-form/ShortFormApplicationController.js.coffee b/app/assets/javascripts/short-form/ShortFormApplicationController.js.coffee index 500fdbf167..d95b975db6 100644 --- a/app/assets/javascripts/short-form/ShortFormApplicationController.js.coffee +++ b/app/assets/javascripts/short-form/ShortFormApplicationController.js.coffee @@ -110,12 +110,19 @@ ShortFormApplicationController = ( $scope.resetAndReplaceApp = ShortFormApplicationService.resetAndReplaceApp + $scope.onUnload = (e) -> + # We want to track the user's actual exit after confirming that they want to leave the page + # Note that this is not perfect, as this will also capture application reloads which doesn't necessarily constitute an abandonment + AnalyticsService.trackApplicationAbandon($scope.listing.Id, AccountService.loggedInUser?.id || null, "Generic Exit") + + $scope.atShortFormState = -> ShortFormApplicationService.isShortFormPage($state.current) if $scope.atShortFormState() && !$window.jasmine && !window.protractor # don't add this onbeforeunload inside of jasmine tests $window.addEventListener 'beforeunload', ShortFormApplicationService.onExit + $window.addEventListener 'unload', $scope.onUnload $scope.submitForm = -> form = $scope.form.applicationForm @@ -887,7 +894,7 @@ ShortFormApplicationController = ( .then( -> ShortFormNavigationService.isLoading(false) ShortFormNavigationService.goToApplicationPage('dahlia.short-form-application.confirmation') - AnalyticsService.trackApplicationComplete($scope.listing.Id, AccountService.loggedInUser?.id || null,) + AnalyticsService.trackApplicationComplete($scope.listing.Id, AccountService.loggedInUser?.id || null) ).catch( -> ShortFormNavigationService.isLoading(false) ) @@ -902,7 +909,9 @@ ShortFormApplicationController = ( # if redirecting to the React my-applications page, disable the "Leave site?" popup if $window.ACCOUNT_INFORMATION_PAGES_REACT is "true" $window.removeEventListener('beforeunload', ShortFormApplicationService.onExit) + $window.removeEventListener('unload', $scope.onUnload) + AnalyticsService.trackApplicationAbandon($scope.listing.Id, AccountService.loggedInUser.id, 'Logged In Save and Finish Later') # ShortFormNavigationService.isLoading(false) will happen after My Apps are loaded # go to my applications without tracking Form Success $scope.go('dahlia.my-applications', {skipConfirm: true}) @@ -911,6 +920,7 @@ ShortFormApplicationController = ( ) else ShortFormNavigationService.isLoading(false) + AnalyticsService.trackApplicationAbandon($scope.listing.Id, null, 'Logged Out Save and Finish Later') # go to Create Account without tracking Form Success $scope.go('dahlia.short-form-application.create-account') @@ -970,6 +980,7 @@ ShortFormApplicationController = ( if $scope.appIsSubmitted(previousApp) # My Applications page is now in React, prevent the "Leave Site?" popup when redirecting $window.removeEventListener('beforeunload', ShortFormApplicationService.onExit) + $window.removeEventListener('unload', $scope.onUnload) doubleSubmit = !! $scope.appIsSubmitted($scope.application) if $window.ACCOUNT_INFORMATION_PAGES_REACT is "true" currentUrl = window.location.origin @@ -982,6 +993,7 @@ ShortFormApplicationController = ( if doubleSubmit # As we rebuilt the My Applications page in React we were not able to figure out a way to trigger the Double Submit Modal. # We are leaving the code here both to document past behavior and to protect the application in case somehow the modal is triggered + AnalyticsService.trackApplicationAbandon($scope.listing.Id, AccountService.loggedInUser?.id || null, 'Double Submitted') newUrl += "doubleSubmit=true" window.location.href = newUrl diff --git a/app/assets/javascripts/short-form/ShortFormApplicationService.js.coffee b/app/assets/javascripts/short-form/ShortFormApplicationService.js.coffee index a5b6d849ae..c05e86852e 100644 --- a/app/assets/javascripts/short-form/ShortFormApplicationService.js.coffee +++ b/app/assets/javascripts/short-form/ShortFormApplicationService.js.coffee @@ -932,6 +932,9 @@ ShortFormApplicationService = ( # this will setup Service.application with the loaded data Service.resetApplicationData(formattedApp) + if !_.isEmpty(data.application) && !_.isEmpty(Service.application) && Service.application.status.match(/draft/i) && Service.application.id + AnalyticsService.trackApplicationStart(data?.application?.listingID, data?.application?.primaryApplicant?.webAppID, 'Continue with these details') + # one last step, reconcile any uploaded files with your saved member + preference data if !_.isEmpty(Service.application) && Service.application.status.match(/draft/i) Service.refreshPreferences('all')