From 15531a4430c9e64023b4fca8acba9cedd9ddd79f Mon Sep 17 00:00:00 2001 From: ABX Date: Wed, 4 Jan 2017 17:23:21 +0100 Subject: [PATCH 1/8] Added analytics service in /helpers. This services supports basic page tracking (automatically triggered on view change, so the method never has to be called manually) and event tracking. --- .../templates/sources/main/_main.constants.ts | 7 +- .../app/templates/sources/main/_main.run.ts | 19 ++++- .../helpers/analytics/analytics.service.ts | 70 +++++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 generators/app/templates/sources/main/helpers/analytics/analytics.service.ts diff --git a/generators/app/templates/sources/main/_main.constants.ts b/generators/app/templates/sources/main/_main.constants.ts index fce3809..cb433d5 100644 --- a/generators/app/templates/sources/main/_main.constants.ts +++ b/generators/app/templates/sources/main/_main.constants.ts @@ -5,6 +5,7 @@ export interface IApplicationConfig { version: string; environment: IApplicationEnvironment; supportedLanguages: Array; + analyticsAccount: string; } export interface IApplicationEnvironment { @@ -56,7 +57,11 @@ let config: IApplicationConfig = { supportedLanguages: [ 'en-US', 'fr-FR' - ] + ], + + // Google Analytics account. Leave null to not have any analytics active. + // Typical values take the form 'UA-########-1'. + analyticsAccount: null }; diff --git a/generators/app/templates/sources/main/_main.run.ts b/generators/app/templates/sources/main/_main.run.ts index e07eeca..4e1e9cb 100644 --- a/generators/app/templates/sources/main/_main.run.ts +++ b/generators/app/templates/sources/main/_main.run.ts @@ -11,6 +11,7 @@ import {ILogger, LoggerService} from 'helpers/logger/logger'; */ function main($window: ng.IWindowService, $locale: ng.ILocaleService, + $location: ng.ILocationService, $rootScope: any, $state: angular.ui.IStateService, <% if (props.target !== 'web') { -%> @@ -26,7 +27,8 @@ function main($window: ng.IWindowService, <% if (props.target !== 'web') { -%> logger: LoggerService, <% } -%> - restService: RestService) { + restService: RestService, + analyticsService: AnalyticsService) { /* * Root view model @@ -84,6 +86,21 @@ function main($window: ng.IWindowService, updateTitle($state.current.data ? $state.current.data.title : null); }); + /** + * Enables tracking by analytics service. + */ + // HACK : ignore the first $viewContentLoaded event because it's actually fired once when uiView is instantiated, + // and then it's fired a second time after is has been linked. This is "by design" :-/ + // (http://stackoverflow.com/questions/31000417/angular-js-viewcontentloaded-loading-twice-on-initial-homepage-load) + let loadedOnce = false; + vm.$on('$viewContentLoaded', function () { + if (!loadedOnce) { + loadedOnce = true; + } else if (analyticsService) { + analyticsService.trackPage($location.url()); + } + }); + init(); /* diff --git a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts new file mode 100644 index 0000000..dceb267 --- /dev/null +++ b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts @@ -0,0 +1,70 @@ +import app from 'main.module'; +import {ILogger, LoggerService} from 'helpers/logger/logger'; + +/** + * Analytics service: insert Google Analytics library in the page. + */ +export class AnalyticsService { + + private analyticsAreActive = false; + + constructor(private $window: ng.IWindowService, + private config: IApplicationConfig, + logger: LoggerService) { + + this.logger = logger.getLogger('analyticsService'); + + this.init(); + } + + private createGoogleAnalyticsObject(i, s, o, g, r, a?, m?) { + i.GoogleAnalyticsObject = r; + i[r] = i[r] || function () { + (i[r].q = i[r].q || []).push(arguments); + }; + i[r].l = new Date(); + a = s.createElement(o); + m = s.getElementsByTagName(o)[0]; + a.async = 1; + a.src = g; + m.parentNode.insertBefore(a, m); + } + + /** + * Tracks a page change in google analytics. + * @param {String} url The url of the new page. + */ + trackPage (url: string) { + if (this.analyticsAreActive) { + var urlWithoutParams = url; + var split = url.split('?'); + if (split.length > 1) { + urlWithoutParams = split[0]; + } + this.$window.googleAnalytics('send', 'pageview', urlWithoutParams); + } + } + + /** + * Sends a track event to google analytics. + * @param {String} category The category to be sent. + * @param {String} action The action to be sent. + * @param {String=} label The label to be sent. + */ + trackEvent (category: string, action: string, label?: string) { + if (this.analyticsAreActive) { + this.$window.googleAnalytics('send', 'event', category, action, label); + } + } + + init(): void { + if (config.analyticsAccount !== null) { + var analyticsScriptUrl = '//www.google-analytics.com/analytics.js'; + this.createGoogleAnalyticsObject(window, document, 'script', analyticsScriptUrl, 'googleAnalytics'); + this.$window.googleAnalytics('create', config.analyticsAccount, 'auto'); + this.analyticsAreActive = true; + } + } +} + +app.service('analyticsService', AnalyticsService); \ No newline at end of file From 324a6203996842538e4d64d2c2bd8e06b952d277 Mon Sep 17 00:00:00 2001 From: ABX Date: Wed, 4 Jan 2017 17:51:15 +0100 Subject: [PATCH 2/8] Fixed some Yeoman and Typescript issues. --- .../app/templates/sources/main/_main.run.ts | 1 + .../helpers/analytics/analytics.service.ts | 39 ++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/generators/app/templates/sources/main/_main.run.ts b/generators/app/templates/sources/main/_main.run.ts index 4e1e9cb..9f5a3dc 100644 --- a/generators/app/templates/sources/main/_main.run.ts +++ b/generators/app/templates/sources/main/_main.run.ts @@ -1,6 +1,7 @@ import app from 'main.module'; import {IApplicationConfig} from 'main.constants'; import {RestService} from 'helpers/rest/rest.service'; +import {AnalyticsService} from 'helpers/analytics/analytics.service'; <% if (props.target !== 'web') { -%> import {ILogger, LoggerService} from 'helpers/logger/logger'; <% } -%> diff --git a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts index dceb267..de17515 100644 --- a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts +++ b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts @@ -6,6 +6,7 @@ import {ILogger, LoggerService} from 'helpers/logger/logger'; */ export class AnalyticsService { + private logger: ILogger; private analyticsAreActive = false; constructor(private $window: ng.IWindowService, @@ -17,27 +18,14 @@ export class AnalyticsService { this.init(); } - private createGoogleAnalyticsObject(i, s, o, g, r, a?, m?) { - i.GoogleAnalyticsObject = r; - i[r] = i[r] || function () { - (i[r].q = i[r].q || []).push(arguments); - }; - i[r].l = new Date(); - a = s.createElement(o); - m = s.getElementsByTagName(o)[0]; - a.async = 1; - a.src = g; - m.parentNode.insertBefore(a, m); - } - /** * Tracks a page change in google analytics. * @param {String} url The url of the new page. */ trackPage (url: string) { if (this.analyticsAreActive) { - var urlWithoutParams = url; - var split = url.split('?'); + let urlWithoutParams = url; + let split = url.split('?'); if (split.length > 1) { urlWithoutParams = split[0]; } @@ -57,14 +45,27 @@ export class AnalyticsService { } } - init(): void { - if (config.analyticsAccount !== null) { - var analyticsScriptUrl = '//www.google-analytics.com/analytics.js'; + private init(): void { + if (this.config.analyticsAccount !== null) { + let analyticsScriptUrl = '//www.google-analytics.com/analytics.js'; this.createGoogleAnalyticsObject(window, document, 'script', analyticsScriptUrl, 'googleAnalytics'); this.$window.googleAnalytics('create', config.analyticsAccount, 'auto'); this.analyticsAreActive = true; } } + + private createGoogleAnalyticsObject(i, s, o, g, r, a?, m?) { + i.GoogleAnalyticsObject = r; + i[r] = i[r] || function () { + (i[r].q = i[r].q || []).push(arguments); + }; + i[r].l = new Date(); + a = s.createElement(o); + m = s.getElementsByTagName(o)[0]; + a.async = 1; + a.src = g; + m.parentNode.insertBefore(a, m); + } } -app.service('analyticsService', AnalyticsService); \ No newline at end of file +app.service('analyticsService', AnalyticsService); From 6832800bea11499938aeca441ab029429ffe6dc2 Mon Sep 17 00:00:00 2001 From: ABX Date: Wed, 4 Jan 2017 18:11:44 +0100 Subject: [PATCH 3/8] More Typescript magic because my window is better than their window. --- .../main/helpers/analytics/analytics.service.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts index de17515..bfaf56d 100644 --- a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts +++ b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts @@ -1,6 +1,10 @@ import app from 'main.module'; import {ILogger, LoggerService} from 'helpers/logger/logger'; +interface IWindowWithAnalytics extends ng.IWindowService { + googleAnalytics: any; +} + /** * Analytics service: insert Google Analytics library in the page. */ @@ -9,7 +13,7 @@ export class AnalyticsService { private logger: ILogger; private analyticsAreActive = false; - constructor(private $window: ng.IWindowService, + constructor(private $window: IWindowWithAnalytics, private config: IApplicationConfig, logger: LoggerService) { @@ -49,12 +53,12 @@ export class AnalyticsService { if (this.config.analyticsAccount !== null) { let analyticsScriptUrl = '//www.google-analytics.com/analytics.js'; this.createGoogleAnalyticsObject(window, document, 'script', analyticsScriptUrl, 'googleAnalytics'); - this.$window.googleAnalytics('create', config.analyticsAccount, 'auto'); + this.$window.googleAnalytics('create', this.config.analyticsAccount, 'auto'); this.analyticsAreActive = true; } } - private createGoogleAnalyticsObject(i, s, o, g, r, a?, m?) { + private createGoogleAnalyticsObject(i: any, s: any, o: any, g: any, r: any, a?: any, m?: any) { i.GoogleAnalyticsObject = r; i[r] = i[r] || function () { (i[r].q = i[r].q || []).push(arguments); From 555fe0156e5a1225be940ac9d8fc93620a762a10 Mon Sep 17 00:00:00 2001 From: ABX Date: Wed, 4 Jan 2017 18:22:55 +0100 Subject: [PATCH 4/8] Added missing type import. Build should be all good now. --- .../sources/main/helpers/analytics/analytics.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts index bfaf56d..624c241 100644 --- a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts +++ b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts @@ -1,5 +1,6 @@ import app from 'main.module'; import {ILogger, LoggerService} from 'helpers/logger/logger'; +import {IApplicationConfig} from 'main.constants' interface IWindowWithAnalytics extends ng.IWindowService { googleAnalytics: any; From d06d2f64dffa3399eecb9aa81824cc688b198b41 Mon Sep 17 00:00:00 2001 From: Antoine Bursaux Date: Wed, 4 Jan 2017 18:34:10 +0100 Subject: [PATCH 5/8] Added missing semicolon. --- .../sources/main/helpers/analytics/analytics.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts index 624c241..db04b74 100644 --- a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts +++ b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts @@ -1,6 +1,6 @@ import app from 'main.module'; import {ILogger, LoggerService} from 'helpers/logger/logger'; -import {IApplicationConfig} from 'main.constants' +import {IApplicationConfig} from 'main.constants'; interface IWindowWithAnalytics extends ng.IWindowService { googleAnalytics: any; From 4b8be83b10a8b489238ee97e53c243ad283fc949 Mon Sep 17 00:00:00 2001 From: Antoine Bursaux Date: Thu, 5 Jan 2017 14:12:19 +0100 Subject: [PATCH 6/8] Made some changes according to code review from pull request. --- .../templates/sources/main/_main.constants.ts | 8 ++++---- .../helpers/analytics/analytics.service.ts | 20 ++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/generators/app/templates/sources/main/_main.constants.ts b/generators/app/templates/sources/main/_main.constants.ts index cb433d5..0f3d629 100644 --- a/generators/app/templates/sources/main/_main.constants.ts +++ b/generators/app/templates/sources/main/_main.constants.ts @@ -5,7 +5,7 @@ export interface IApplicationConfig { version: string; environment: IApplicationEnvironment; supportedLanguages: Array; - analyticsAccount: string; + googleAnayticsId: string; } export interface IApplicationEnvironment { @@ -59,9 +59,9 @@ let config: IApplicationConfig = { 'fr-FR' ], - // Google Analytics account. Leave null to not have any analytics active. - // Typical values take the form 'UA-########-1'. - analyticsAccount: null + // Google Analytics account. Leave null to not have any analytics active. Typical values take the form 'UA-########-1'. + // You can use something along the lines of < this.environment.debug ? null : 'UA-########-1' > + googleAnayticsId: null }; diff --git a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts index db04b74..1bf5159 100644 --- a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts +++ b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts @@ -2,8 +2,10 @@ import app from 'main.module'; import {ILogger, LoggerService} from 'helpers/logger/logger'; import {IApplicationConfig} from 'main.constants'; +const analyticsScriptUrl = '//www.google-analytics.com/analytics.js'; + interface IWindowWithAnalytics extends ng.IWindowService { - googleAnalytics: any; + ga: any; } /** @@ -34,7 +36,7 @@ export class AnalyticsService { if (split.length > 1) { urlWithoutParams = split[0]; } - this.$window.googleAnalytics('send', 'pageview', urlWithoutParams); + this.$window.ga('send', 'pageview', urlWithoutParams); } } @@ -46,15 +48,19 @@ export class AnalyticsService { */ trackEvent (category: string, action: string, label?: string) { if (this.analyticsAreActive) { - this.$window.googleAnalytics('send', 'event', category, action, label); + this.$window.ga('send', 'event', category, action, label); + let logMessage = 'Event tracked: ' + category + ' | ' + action; + if (label) { + logMessage += ' | ' + label; + } + this.logger.log(logMessage); } } private init(): void { - if (this.config.analyticsAccount !== null) { - let analyticsScriptUrl = '//www.google-analytics.com/analytics.js'; - this.createGoogleAnalyticsObject(window, document, 'script', analyticsScriptUrl, 'googleAnalytics'); - this.$window.googleAnalytics('create', this.config.analyticsAccount, 'auto'); + if (this.config.googleAnayticsId !== null) { + this.createGoogleAnalyticsObject(this.$window, document, 'script', analyticsScriptUrl, 'ga'); + this.$window.ga('create', this.config.googleAnayticsId, 'auto'); this.analyticsAreActive = true; } } From 71d093514646e247dc7d262c1086d729ac5fae60 Mon Sep 17 00:00:00 2001 From: Antoine Bursaux Date: Thu, 5 Jan 2017 14:12:19 +0100 Subject: [PATCH 7/8] Made some changes according to code review from pull request. --- .../templates/sources/main/_main.constants.ts | 5 +++-- .../helpers/analytics/analytics.service.ts | 20 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/generators/app/templates/sources/main/_main.constants.ts b/generators/app/templates/sources/main/_main.constants.ts index cb433d5..cbc463c 100644 --- a/generators/app/templates/sources/main/_main.constants.ts +++ b/generators/app/templates/sources/main/_main.constants.ts @@ -5,7 +5,7 @@ export interface IApplicationConfig { version: string; environment: IApplicationEnvironment; supportedLanguages: Array; - analyticsAccount: string; + googleAnayticsId: string; } export interface IApplicationEnvironment { @@ -61,7 +61,8 @@ let config: IApplicationConfig = { // Google Analytics account. Leave null to not have any analytics active. // Typical values take the form 'UA-########-1'. - analyticsAccount: null + // Advice : this value may be handled by the gulp build task to use different accounts for development and production. + googleAnayticsId: null }; diff --git a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts index db04b74..1bf5159 100644 --- a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts +++ b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts @@ -2,8 +2,10 @@ import app from 'main.module'; import {ILogger, LoggerService} from 'helpers/logger/logger'; import {IApplicationConfig} from 'main.constants'; +const analyticsScriptUrl = '//www.google-analytics.com/analytics.js'; + interface IWindowWithAnalytics extends ng.IWindowService { - googleAnalytics: any; + ga: any; } /** @@ -34,7 +36,7 @@ export class AnalyticsService { if (split.length > 1) { urlWithoutParams = split[0]; } - this.$window.googleAnalytics('send', 'pageview', urlWithoutParams); + this.$window.ga('send', 'pageview', urlWithoutParams); } } @@ -46,15 +48,19 @@ export class AnalyticsService { */ trackEvent (category: string, action: string, label?: string) { if (this.analyticsAreActive) { - this.$window.googleAnalytics('send', 'event', category, action, label); + this.$window.ga('send', 'event', category, action, label); + let logMessage = 'Event tracked: ' + category + ' | ' + action; + if (label) { + logMessage += ' | ' + label; + } + this.logger.log(logMessage); } } private init(): void { - if (this.config.analyticsAccount !== null) { - let analyticsScriptUrl = '//www.google-analytics.com/analytics.js'; - this.createGoogleAnalyticsObject(window, document, 'script', analyticsScriptUrl, 'googleAnalytics'); - this.$window.googleAnalytics('create', this.config.analyticsAccount, 'auto'); + if (this.config.googleAnayticsId !== null) { + this.createGoogleAnalyticsObject(this.$window, document, 'script', analyticsScriptUrl, 'ga'); + this.$window.ga('create', this.config.googleAnayticsId, 'auto'); this.analyticsAreActive = true; } } From 5c84ec427850fed6e841a56dba1ed1f97266dc4b Mon Sep 17 00:00:00 2001 From: Antoine Bursaux Date: Thu, 5 Jan 2017 16:54:34 +0100 Subject: [PATCH 8/8] Moved analytics ID from Config to Environment. --- .../app/templates/sources/main/_main.constants.ts | 14 +++++++------- .../main/helpers/analytics/analytics.service.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/generators/app/templates/sources/main/_main.constants.ts b/generators/app/templates/sources/main/_main.constants.ts index cbc463c..cf51494 100644 --- a/generators/app/templates/sources/main/_main.constants.ts +++ b/generators/app/templates/sources/main/_main.constants.ts @@ -5,11 +5,11 @@ export interface IApplicationConfig { version: string; environment: IApplicationEnvironment; supportedLanguages: Array; - googleAnayticsId: string; } export interface IApplicationEnvironment { debug: boolean; + googleAnayticsId: string; server: IServerConfig; } @@ -20,6 +20,10 @@ let environment = { local: { debug: true, + // Google Analytics account. Leave null to not have any analytics active. + // Typical values are of the form 'UA-########-1', where each # is a digit. + googleAnayticsId: null, + // REST backend configuration, used for all web services using restService server: { url: '', @@ -28,6 +32,7 @@ let environment = { }, production: { debug: false, + googleAnayticsId: null, server: { <% if (props.target === 'web') { -%> url: '', @@ -57,12 +62,7 @@ let config: IApplicationConfig = { supportedLanguages: [ 'en-US', 'fr-FR' - ], - - // Google Analytics account. Leave null to not have any analytics active. - // Typical values take the form 'UA-########-1'. - // Advice : this value may be handled by the gulp build task to use different accounts for development and production. - googleAnayticsId: null + ] }; diff --git a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts index 1bf5159..a23f33e 100644 --- a/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts +++ b/generators/app/templates/sources/main/helpers/analytics/analytics.service.ts @@ -1,6 +1,6 @@ import app from 'main.module'; import {ILogger, LoggerService} from 'helpers/logger/logger'; -import {IApplicationConfig} from 'main.constants'; +import {IApplicationEnvironment} from 'main.constants'; const analyticsScriptUrl = '//www.google-analytics.com/analytics.js'; @@ -17,7 +17,7 @@ export class AnalyticsService { private analyticsAreActive = false; constructor(private $window: IWindowWithAnalytics, - private config: IApplicationConfig, + private config: IApplicationEnvironment, logger: LoggerService) { this.logger = logger.getLogger('analyticsService');