mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 06:53:03 +00:00
Merge pull request #1014 from atmire/w2p-76634_google-analytics-tracking-id-from-backend
W2p 76634 google analytics tracking id from backend
This commit is contained in:
@@ -32,6 +32,7 @@ import { storeModuleConfig } from './app.reducer';
|
||||
import { LocaleService } from './core/locale/locale.service';
|
||||
import { authReducer } from './core/auth/auth.reducer';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import {GoogleAnalyticsService} from './statistics/google-analytics.service';
|
||||
|
||||
let comp: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
@@ -48,38 +49,40 @@ describe('App component', () => {
|
||||
});
|
||||
}
|
||||
|
||||
const defaultTestBedConf = {
|
||||
imports: [
|
||||
CommonModule,
|
||||
StoreModule.forRoot(authReducer, storeModuleConfig),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
}),
|
||||
],
|
||||
declarations: [AppComponent], // declare the test component
|
||||
providers: [
|
||||
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
||||
{ provide: MetadataService, useValue: new MetadataServiceMock() },
|
||||
{ provide: Angulartics2GoogleAnalytics, useValue: new AngularticsProviderMock() },
|
||||
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
|
||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||
{ provide: Router, useValue: new RouterMock() },
|
||||
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
||||
{ provide: MenuService, useValue: menuService },
|
||||
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||
{ provide: LocaleService, useValue: getMockLocaleService() },
|
||||
provideMockStore({ initialState }),
|
||||
AppComponent,
|
||||
RouteService
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
};
|
||||
|
||||
// waitForAsync beforeEach
|
||||
beforeEach(waitForAsync(() => {
|
||||
return TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
StoreModule.forRoot(authReducer, storeModuleConfig),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
}),
|
||||
],
|
||||
declarations: [AppComponent], // declare the test component
|
||||
providers: [
|
||||
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
||||
{ provide: MetadataService, useValue: new MetadataServiceMock() },
|
||||
{ provide: Angulartics2GoogleAnalytics, useValue: new AngularticsProviderMock() },
|
||||
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
|
||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||
{ provide: Router, useValue: new RouterMock() },
|
||||
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
||||
{ provide: MenuService, useValue: menuService },
|
||||
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||
{ provide: LocaleService, useValue: getMockLocaleService() },
|
||||
provideMockStore({ initialState }),
|
||||
AppComponent,
|
||||
RouteService
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
return TestBed.configureTestingModule(defaultTestBedConf);
|
||||
}));
|
||||
|
||||
// synchronous beforeEach
|
||||
@@ -113,4 +116,31 @@ describe('App component', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when GoogleAnalyticsService is provided', () => {
|
||||
let googleAnalyticsSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
||||
TestBed.resetTestingModule();
|
||||
TestBed.configureTestingModule(defaultTestBedConf);
|
||||
googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [
|
||||
'addTrackingIdToPage',
|
||||
]);
|
||||
TestBed.overrideProvider(GoogleAnalyticsService, {useValue: googleAnalyticsSpy});
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create component', () => {
|
||||
expect(comp).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('the constructor', () => {
|
||||
it('should call googleAnalyticsService.addTrackingIdToPage()', () => {
|
||||
expect(googleAnalyticsSpy.addTrackingIdToPage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -33,6 +33,7 @@ import { models } from './core/core.module';
|
||||
import { LocaleService } from './core/locale/locale.service';
|
||||
import { hasValue } from './shared/empty.util';
|
||||
import { KlaroService } from './shared/cookies/klaro.service';
|
||||
import {GoogleAnalyticsService} from './statistics/google-analytics.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-app',
|
||||
@@ -70,7 +71,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
private menuService: MenuService,
|
||||
private windowService: HostWindowService,
|
||||
private localeService: LocaleService,
|
||||
@Optional() private cookiesService: KlaroService
|
||||
@Optional() private cookiesService: KlaroService,
|
||||
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
||||
) {
|
||||
|
||||
/* Use models object so all decorators are actually called */
|
||||
@@ -84,7 +86,10 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
// set the current language code
|
||||
this.localeService.setCurrentLanguageCode();
|
||||
|
||||
angulartics2GoogleAnalytics.startTracking();
|
||||
// analytics
|
||||
if (hasValue(googleAnalyticsService)) {
|
||||
googleAnalyticsService.addTrackingIdToPage();
|
||||
}
|
||||
angulartics2DSpace.startTracking();
|
||||
|
||||
metadata.listenForRouteChange();
|
||||
|
128
src/app/statistics/google-analytics.service.spec.ts
Normal file
128
src/app/statistics/google-analytics.service.spec.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { GoogleAnalyticsService } from './google-analytics.service';
|
||||
import {Angulartics2GoogleAnalytics} from 'angulartics2/ga';
|
||||
import {ConfigurationDataService} from '../core/data/configuration-data.service';
|
||||
import {createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$} from '../shared/remote-data.utils';
|
||||
import {ConfigurationProperty} from '../core/shared/configuration-property.model';
|
||||
|
||||
describe('GoogleAnalyticsService', () => {
|
||||
const trackingIdProp = 'google.analytics.key';
|
||||
const trackingIdTestValue = 'mock-tracking-id';
|
||||
const innerHTMLTestValue = 'mock-script-inner-html';
|
||||
let service: GoogleAnalyticsService;
|
||||
let angularticsSpy: Angulartics2GoogleAnalytics;
|
||||
let configSpy: ConfigurationDataService;
|
||||
let scriptElementMock: any;
|
||||
let innerHTMLSpy: any;
|
||||
let bodyElementSpy: HTMLBodyElement;
|
||||
let documentSpy: Document;
|
||||
|
||||
const createConfigSuccessSpy = (...values: string[]) => jasmine.createSpyObj('configurationDataService', {
|
||||
findByPropertyName: createSuccessfulRemoteDataObject$({
|
||||
... new ConfigurationProperty(),
|
||||
name: trackingIdProp,
|
||||
values: values,
|
||||
}),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
angularticsSpy = jasmine.createSpyObj('angulartics2GoogleAnalytics', [
|
||||
'startTracking',
|
||||
]);
|
||||
|
||||
configSpy = createConfigSuccessSpy(trackingIdTestValue);
|
||||
|
||||
scriptElementMock = {
|
||||
set innerHTML(newVal) { /* noop */ },
|
||||
get innerHTML() { return innerHTMLTestValue; }
|
||||
};
|
||||
|
||||
innerHTMLSpy = spyOnProperty(scriptElementMock, 'innerHTML', 'set');
|
||||
|
||||
bodyElementSpy = jasmine.createSpyObj('body', {
|
||||
appendChild: scriptElementMock,
|
||||
});
|
||||
|
||||
documentSpy = jasmine.createSpyObj('document', {
|
||||
createElement: scriptElementMock,
|
||||
}, {
|
||||
body: bodyElementSpy,
|
||||
});
|
||||
|
||||
service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('addTrackingIdToPage()', () => {
|
||||
it(`should request the ${trackingIdProp} property`, () => {
|
||||
service.addTrackingIdToPage();
|
||||
expect(configSpy.findByPropertyName).toHaveBeenCalledTimes(1);
|
||||
expect(configSpy.findByPropertyName).toHaveBeenCalledWith(trackingIdProp);
|
||||
});
|
||||
|
||||
describe('when the request fails', () => {
|
||||
beforeEach(() => {
|
||||
configSpy = jasmine.createSpyObj('configurationDataService', {
|
||||
findByPropertyName: createFailedRemoteDataObject$(),
|
||||
});
|
||||
|
||||
service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy);
|
||||
});
|
||||
|
||||
it('should NOT add a script to the body', () => {
|
||||
service.addTrackingIdToPage();
|
||||
expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should NOT start tracking', () => {
|
||||
service.addTrackingIdToPage();
|
||||
expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the request succeeds', () => {
|
||||
describe('when the tracking id is empty', () => {
|
||||
beforeEach(() => {
|
||||
configSpy = createConfigSuccessSpy();
|
||||
service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy);
|
||||
});
|
||||
|
||||
it('should NOT add a script to the body', () => {
|
||||
service.addTrackingIdToPage();
|
||||
expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should NOT start tracking', () => {
|
||||
service.addTrackingIdToPage();
|
||||
expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the tracking id is non-empty', () => {
|
||||
it('should create a script tag whose innerHTML contains the tracking id', () => {
|
||||
service.addTrackingIdToPage();
|
||||
expect(documentSpy.createElement).toHaveBeenCalledTimes(1);
|
||||
expect(documentSpy.createElement).toHaveBeenCalledWith('script');
|
||||
|
||||
// sanity check
|
||||
expect(documentSpy.createElement('script')).toBe(scriptElementMock);
|
||||
|
||||
expect(innerHTMLSpy).toHaveBeenCalledTimes(1);
|
||||
expect(innerHTMLSpy.calls.argsFor(0)[0]).toContain(trackingIdTestValue);
|
||||
});
|
||||
|
||||
it('should add a script to the body', () => {
|
||||
service.addTrackingIdToPage();
|
||||
expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should start tracking', () => {
|
||||
service.addTrackingIdToPage();
|
||||
expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
52
src/app/statistics/google-analytics.service.ts
Normal file
52
src/app/statistics/google-analytics.service.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import {Inject, Injectable} from '@angular/core';
|
||||
import {Angulartics2GoogleAnalytics} from 'angulartics2/ga';
|
||||
import {ConfigurationDataService} from '../core/data/configuration-data.service';
|
||||
import {getFirstCompletedRemoteData} from '../core/shared/operators';
|
||||
import {isEmpty} from '../shared/empty.util';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
|
||||
/**
|
||||
* Set up Google Analytics on the client side.
|
||||
* See: {@link addTrackingIdToPage}.
|
||||
*/
|
||||
@Injectable()
|
||||
export class GoogleAnalyticsService {
|
||||
|
||||
constructor(
|
||||
private angulartics: Angulartics2GoogleAnalytics,
|
||||
private configService: ConfigurationDataService,
|
||||
@Inject(DOCUMENT) private document: any,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Call this method once when Angular initializes on the client side.
|
||||
* It requests a Google Analytics tracking id from the rest backend
|
||||
* (property: google.analytics.key), adds the tracking snippet to the
|
||||
* page and starts tracking.
|
||||
*/
|
||||
addTrackingIdToPage(): void {
|
||||
this.configService.findByPropertyName('google.analytics.key').pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
).subscribe((remoteData) => {
|
||||
// make sure we got a success response from the backend
|
||||
if (!remoteData.hasSucceeded) { return; }
|
||||
|
||||
const trackingId = remoteData.payload.values[0];
|
||||
|
||||
// make sure we received a tracking id
|
||||
if (isEmpty(trackingId)) { return; }
|
||||
|
||||
// add trackingId snippet to page
|
||||
const keyScript = this.document.createElement('script');
|
||||
keyScript.innerHTML = `(function(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=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', '${trackingId}', 'auto');`;
|
||||
this.document.body.appendChild(keyScript);
|
||||
|
||||
// start tracking
|
||||
this.angulartics.startTracking();
|
||||
});
|
||||
}
|
||||
}
|
@@ -23,7 +23,6 @@ export interface GlobalConfig extends Config {
|
||||
notifications: INotificationBoardOptions;
|
||||
submission: SubmissionConfig;
|
||||
universal: UniversalConfig;
|
||||
gaTrackingId: string;
|
||||
logDirectory: string;
|
||||
debug: boolean;
|
||||
defaultLanguage: string;
|
||||
|
@@ -132,8 +132,6 @@ export const environment: GlobalConfig = {
|
||||
async: true,
|
||||
time: false
|
||||
},
|
||||
// Google Analytics tracking id
|
||||
gaTrackingId: '',
|
||||
// Log directory
|
||||
logDirectory: '.',
|
||||
// NOTE: will log all redux actions and transfers in console
|
||||
|
@@ -110,8 +110,6 @@ export const environment: Partial<GlobalConfig> = {
|
||||
async: true,
|
||||
time: false
|
||||
},
|
||||
// Google Analytics tracking id
|
||||
gaTrackingId: '',
|
||||
// Log directory
|
||||
logDirectory: '.',
|
||||
// NOTE: will log all redux actions and transfers in console
|
||||
|
@@ -6,7 +6,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { bootloader } from '@angularclass/bootloader';
|
||||
|
||||
import { load as loadWebFont } from 'webfontloader';
|
||||
import { hasValue, isNotEmpty } from './app/shared/empty.util';
|
||||
import { hasValue } from './app/shared/empty.util';
|
||||
|
||||
import { BrowserAppModule } from './modules/app/browser-app.module';
|
||||
|
||||
@@ -25,25 +25,9 @@ export function main() {
|
||||
}
|
||||
});
|
||||
|
||||
addGoogleAnalytics();
|
||||
|
||||
return platformBrowserDynamic().bootstrapModule(BrowserAppModule, {preserveWhitespaces:true});
|
||||
}
|
||||
|
||||
function addGoogleAnalytics() {
|
||||
// Add google analytics if key is present in config
|
||||
const trackingId = environment.gaTrackingId;
|
||||
if (isNotEmpty(trackingId)) {
|
||||
const keyScript = document.createElement('script');
|
||||
keyScript.innerHTML = `(function(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=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');`
|
||||
+ 'ga(\'create\', \'' + environment.gaTrackingId + '\', \'auto\');';
|
||||
document.body.appendChild(keyScript);
|
||||
}
|
||||
}
|
||||
|
||||
// support async tag or hmr
|
||||
if (hasValue(environment.universal) && environment.universal.preboot === false) {
|
||||
bootloader(main);
|
||||
|
@@ -30,6 +30,7 @@ import {
|
||||
LocationToken
|
||||
} from '../../app/core/services/browser-hard-redirect.service';
|
||||
import { LocaleService } from '../../app/core/locale/locale.service';
|
||||
import {GoogleAnalyticsService} from '../../app/statistics/google-analytics.service';
|
||||
|
||||
export const REQ_KEY = makeStateKey<string>('req');
|
||||
|
||||
@@ -99,6 +100,10 @@ export function getRequest(transferState: TransferState): any {
|
||||
provide: HardRedirectService,
|
||||
useClass: BrowserHardRedirectService,
|
||||
},
|
||||
{
|
||||
provide: GoogleAnalyticsService,
|
||||
useClass: GoogleAnalyticsService,
|
||||
},
|
||||
{
|
||||
provide: LocationToken,
|
||||
useFactory: locationProvider,
|
||||
|
Reference in New Issue
Block a user