mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
[CST-6753] track Google Analytics statistic only if user accepts cookie consents
This commit is contained in:
@@ -14,6 +14,8 @@ import {clone, cloneDeep} from 'lodash';
|
||||
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
|
||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
|
||||
import { ANONYMOUS_STORAGE_NAME_KLARO } from './klaro-configuration';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
|
||||
describe('BrowserKlaroService', () => {
|
||||
const trackingIdProp = 'google.analytics.key';
|
||||
@@ -42,7 +44,9 @@ describe('BrowserKlaroService', () => {
|
||||
let findByPropertyName;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new EPerson();
|
||||
user = Object.assign(new EPerson(), {
|
||||
uuid: 'test-user'
|
||||
});
|
||||
|
||||
translateService = getMockTranslateService();
|
||||
ePersonService = jasmine.createSpyObj('ePersonService', {
|
||||
@@ -219,6 +223,40 @@ describe('BrowserKlaroService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSavedPreferences', () => {
|
||||
let scheduler: TestScheduler;
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
});
|
||||
|
||||
describe('when no user is autheticated', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service as any, 'getUser$').and.returnValue(observableOf(undefined));
|
||||
});
|
||||
|
||||
it('should return the cookie consents object', () => {
|
||||
scheduler.schedule(() => service.getSavedPreferences().subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(cookieService.get).toHaveBeenCalledWith(ANONYMOUS_STORAGE_NAME_KLARO);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user is autheticated', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service as any, 'getUser$').and.returnValue(observableOf(user));
|
||||
});
|
||||
|
||||
it('should return the cookie consents object', () => {
|
||||
scheduler.schedule(() => service.getSavedPreferences().subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(cookieService.get).toHaveBeenCalledWith('klaro-' + user.uuid);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('setSettingsForUser when there are changes', () => {
|
||||
const cookieConsent = { test: 'testt' };
|
||||
const cookieConsentString = '{test: \'testt\'}';
|
||||
|
@@ -121,6 +121,23 @@ export class BrowserKlaroService extends KlaroService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return saved preferences stored in the klaro cookie
|
||||
*/
|
||||
getSavedPreferences(): Observable<any> {
|
||||
return this.getUser$().pipe(
|
||||
map((user: EPerson) => {
|
||||
let storageName;
|
||||
if (isEmpty(user)) {
|
||||
storageName = ANONYMOUS_STORAGE_NAME_KLARO;
|
||||
} else {
|
||||
storageName = this.getStorageName(user.uuid);
|
||||
}
|
||||
return this.cookieService.get(storageName);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize configuration for the logged in user
|
||||
* @param user The authenticated user
|
||||
|
@@ -12,6 +12,8 @@ export const HAS_AGREED_END_USER = 'dsHasAgreedEndUser';
|
||||
*/
|
||||
export const ANONYMOUS_STORAGE_NAME_KLARO = 'klaro-anonymous';
|
||||
|
||||
export const GOOGLE_ANALYTICS_KLARO_KEY = 'google-analytics';
|
||||
|
||||
/**
|
||||
* Klaro configuration
|
||||
* For more information see https://kiprotect.com/docs/klaro/annotated-config
|
||||
@@ -113,7 +115,7 @@ export const klaroConfiguration: any = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'google-analytics',
|
||||
name: GOOGLE_ANALYTICS_KLARO_KEY,
|
||||
purposes: ['statistical'],
|
||||
required: false,
|
||||
cookies: [
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Abstract class representing a service for handling Klaro consent preferences and UI
|
||||
*/
|
||||
@@ -11,7 +13,12 @@ export abstract class KlaroService {
|
||||
abstract initialize();
|
||||
|
||||
/**
|
||||
* Shows a the dialog with the current consent preferences
|
||||
* Shows a dialog with the current consent preferences
|
||||
*/
|
||||
abstract showSettings();
|
||||
|
||||
/**
|
||||
* Return saved preferences stored in the klaro cookie
|
||||
*/
|
||||
abstract getSavedPreferences(): Observable<any>;
|
||||
}
|
||||
|
@@ -1,8 +1,12 @@
|
||||
import { GoogleAnalyticsService } from './google-analytics.service';
|
||||
import { Angulartics2GoogleTagManager } from 'angulartics2';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { GoogleAnalyticsService } from './google-analytics.service';
|
||||
import { ConfigurationDataService } from '../core/data/configuration-data.service';
|
||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { ConfigurationProperty } from '../core/shared/configuration-property.model';
|
||||
import { KlaroService } from '../shared/cookies/klaro.service';
|
||||
import { GOOGLE_ANALYTICS_KLARO_KEY } from '../shared/cookies/klaro-configuration';
|
||||
|
||||
describe('GoogleAnalyticsService', () => {
|
||||
const trackingIdProp = 'google.analytics.key';
|
||||
@@ -12,6 +16,7 @@ describe('GoogleAnalyticsService', () => {
|
||||
let service: GoogleAnalyticsService;
|
||||
let angularticsSpy: Angulartics2GoogleTagManager;
|
||||
let configSpy: ConfigurationDataService;
|
||||
let klaroServiceSpy: jasmine.SpyObj<KlaroService>;
|
||||
let scriptElementMock: any;
|
||||
let srcSpy: any;
|
||||
let innerHTMLSpy: any;
|
||||
@@ -31,6 +36,10 @@ describe('GoogleAnalyticsService', () => {
|
||||
'startTracking',
|
||||
]);
|
||||
|
||||
klaroServiceSpy = jasmine.createSpyObj('KlaroService', {
|
||||
'getSavedPreferences': jasmine.createSpy('getSavedPreferences')
|
||||
});
|
||||
|
||||
configSpy = createConfigSuccessSpy(trackingIdTestValue);
|
||||
|
||||
scriptElementMock = {
|
||||
@@ -53,7 +62,11 @@ describe('GoogleAnalyticsService', () => {
|
||||
body: bodyElementSpy,
|
||||
});
|
||||
|
||||
service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy);
|
||||
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
|
||||
GOOGLE_ANALYTICS_KLARO_KEY: true
|
||||
}));
|
||||
|
||||
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy );
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
@@ -73,7 +86,11 @@ describe('GoogleAnalyticsService', () => {
|
||||
findByPropertyName: createFailedRemoteDataObject$(),
|
||||
});
|
||||
|
||||
service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy);
|
||||
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
|
||||
GOOGLE_ANALYTICS_KLARO_KEY: true
|
||||
}));
|
||||
|
||||
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy);
|
||||
});
|
||||
|
||||
it('should NOT add a script to the body', () => {
|
||||
@@ -91,7 +108,10 @@ describe('GoogleAnalyticsService', () => {
|
||||
describe('when the tracking id is empty', () => {
|
||||
beforeEach(() => {
|
||||
configSpy = createConfigSuccessSpy();
|
||||
service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy);
|
||||
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
|
||||
[GOOGLE_ANALYTICS_KLARO_KEY]: true
|
||||
}));
|
||||
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy);
|
||||
});
|
||||
|
||||
it('should NOT add a script to the body', () => {
|
||||
@@ -105,7 +125,55 @@ describe('GoogleAnalyticsService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the tracking id is non-empty', () => {
|
||||
describe('when google-analytics cookie preferences are not existing', () => {
|
||||
beforeEach(() => {
|
||||
configSpy = createConfigSuccessSpy(trackingIdTestValue);
|
||||
klaroServiceSpy.getSavedPreferences.and.returnValue(of({}));
|
||||
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, 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 google-analytics cookie preferences are set to false', () => {
|
||||
beforeEach(() => {
|
||||
configSpy = createConfigSuccessSpy(trackingIdTestValue);
|
||||
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
|
||||
[GOOGLE_ANALYTICS_KLARO_KEY]: false
|
||||
}));
|
||||
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, 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 both google-analytics cookie and the tracking id are non-empty', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
configSpy = createConfigSuccessSpy(trackingIdTestValue);
|
||||
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
|
||||
[GOOGLE_ANALYTICS_KLARO_KEY]: true
|
||||
}));
|
||||
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy);
|
||||
});
|
||||
|
||||
it('should create a script tag whose innerHTML contains the tracking id', () => {
|
||||
service.addTrackingIdToPage();
|
||||
expect(documentSpy.createElement).toHaveBeenCalledTimes(2);
|
||||
|
@@ -1,9 +1,14 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
|
||||
import { Angulartics2GoogleTagManager } from 'angulartics2';
|
||||
import { combineLatest } from 'rxjs';
|
||||
|
||||
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';
|
||||
import { KlaroService } from '../shared/cookies/klaro.service';
|
||||
import { GOOGLE_ANALYTICS_KLARO_KEY } from '../shared/cookies/klaro-configuration';
|
||||
|
||||
/**
|
||||
* Set up Google Analytics on the client side.
|
||||
@@ -15,9 +20,11 @@ export class GoogleAnalyticsService {
|
||||
constructor(
|
||||
// private angulartics: Angulartics2GoogleAnalytics,
|
||||
private angulartics: Angulartics2GoogleTagManager,
|
||||
private klaroService: KlaroService,
|
||||
private configService: ConfigurationDataService,
|
||||
@Inject(DOCUMENT) private document: any,
|
||||
) { }
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method once when Angular initializes on the client side.
|
||||
@@ -26,16 +33,27 @@ export class GoogleAnalyticsService {
|
||||
* page and starts tracking.
|
||||
*/
|
||||
addTrackingIdToPage(): void {
|
||||
this.configService.findByPropertyName('google.analytics.key').pipe(
|
||||
const googleKey$ = this.configService.findByPropertyName('google.analytics.key').pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
).subscribe((remoteData) => {
|
||||
);
|
||||
combineLatest([this.klaroService.getSavedPreferences(), googleKey$])
|
||||
.subscribe(([preferences, remoteData]) => {
|
||||
// make sure user has accepted Google Analytics consents
|
||||
if (isEmpty(preferences) || isEmpty(preferences[GOOGLE_ANALYTICS_KLARO_KEY]) || !preferences[GOOGLE_ANALYTICS_KLARO_KEY]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we got a success response from the backend
|
||||
if (!remoteData.hasSucceeded) { return; }
|
||||
if (!remoteData.hasSucceeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trackingId = remoteData.payload.values[0];
|
||||
|
||||
// make sure we received a tracking id
|
||||
if (isEmpty(trackingId)) { return; }
|
||||
if (isEmpty(trackingId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add GTag snippet to page
|
||||
const keyScript = this.document.createElement('script');
|
||||
|
Reference in New Issue
Block a user