Skip to content

Commit f3f89b3

Browse files
committed
[CST-6753] track Google Analytics statistic only if user accepts cookie consents
1 parent e9a87a6 commit f3f89b3

File tree

6 files changed

+190
-40
lines changed

6 files changed

+190
-40
lines changed

src/app/shared/cookies/browser-klaro.service.spec.ts

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import { AuthService } from '../../core/auth/auth.service';
1010
import { CookieService } from '../../core/services/cookie.service';
1111
import { getTestScheduler } from 'jasmine-marbles';
1212
import { MetadataValue } from '../../core/shared/metadata.models';
13-
import {clone, cloneDeep} from 'lodash';
13+
import { clone, cloneDeep } from 'lodash';
1414
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
15-
import {createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$} from '../remote-data.utils';
15+
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
1616
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
17+
import { ANONYMOUS_STORAGE_NAME_KLARO } from './klaro-configuration';
18+
import { TestScheduler } from 'rxjs/testing';
1719

1820
describe('BrowserKlaroService', () => {
1921
const trackingIdProp = 'google.analytics.key';
@@ -29,7 +31,7 @@ describe('BrowserKlaroService', () => {
2931
let configurationDataService: ConfigurationDataService;
3032
const createConfigSuccessSpy = (...values: string[]) => jasmine.createSpyObj('configurationDataService', {
3133
findByPropertyName: createSuccessfulRemoteDataObject$({
32-
... new ConfigurationProperty(),
34+
...new ConfigurationProperty(),
3335
name: trackingIdProp,
3436
values: values,
3537
}),
@@ -42,7 +44,9 @@ describe('BrowserKlaroService', () => {
4244
let findByPropertyName;
4345

4446
beforeEach(() => {
45-
user = new EPerson();
47+
user = Object.assign(new EPerson(), {
48+
uuid: 'test-user'
49+
});
4650

4751
translateService = getMockTranslateService();
4852
ePersonService = jasmine.createSpyObj('ePersonService', {
@@ -104,7 +108,7 @@ describe('BrowserKlaroService', () => {
104108
services: [{
105109
name: appName,
106110
purposes: [purpose]
107-
},{
111+
}, {
108112
name: googleAnalytics,
109113
purposes: [purpose]
110114
}],
@@ -219,6 +223,40 @@ describe('BrowserKlaroService', () => {
219223
});
220224
});
221225

226+
describe('getSavedPreferences', () => {
227+
let scheduler: TestScheduler;
228+
beforeEach(() => {
229+
scheduler = getTestScheduler();
230+
});
231+
232+
describe('when no user is autheticated', () => {
233+
beforeEach(() => {
234+
spyOn(service as any, 'getUser$').and.returnValue(observableOf(undefined));
235+
});
236+
237+
it('should return the cookie consents object', () => {
238+
scheduler.schedule(() => service.getSavedPreferences().subscribe());
239+
scheduler.flush();
240+
241+
expect(cookieService.get).toHaveBeenCalledWith(ANONYMOUS_STORAGE_NAME_KLARO);
242+
});
243+
});
244+
245+
describe('when user is autheticated', () => {
246+
beforeEach(() => {
247+
spyOn(service as any, 'getUser$').and.returnValue(observableOf(user));
248+
});
249+
250+
it('should return the cookie consents object', () => {
251+
scheduler.schedule(() => service.getSavedPreferences().subscribe());
252+
scheduler.flush();
253+
254+
expect(cookieService.get).toHaveBeenCalledWith('klaro-' + user.uuid);
255+
});
256+
});
257+
});
258+
259+
222260
describe('setSettingsForUser when there are changes', () => {
223261
const cookieConsent = { test: 'testt' };
224262
const cookieConsentString = '{test: \'testt\'}';
@@ -271,40 +309,40 @@ describe('BrowserKlaroService', () => {
271309
});
272310
it('should not filter googleAnalytics when servicesToHide are empty', () => {
273311
const filteredConfig = (service as any).filterConfigServices([]);
274-
expect(filteredConfig).toContain(jasmine.objectContaining({name: googleAnalytics}));
312+
expect(filteredConfig).toContain(jasmine.objectContaining({ name: googleAnalytics }));
275313
});
276314
it('should filter services using names passed as servicesToHide', () => {
277315
const filteredConfig = (service as any).filterConfigServices([googleAnalytics]);
278-
expect(filteredConfig).not.toContain(jasmine.objectContaining({name: googleAnalytics}));
316+
expect(filteredConfig).not.toContain(jasmine.objectContaining({ name: googleAnalytics }));
279317
});
280318
it('should have been initialized with googleAnalytics', () => {
281319
service.initialize();
282-
expect(service.klaroConfig.services).toContain(jasmine.objectContaining({name: googleAnalytics}));
320+
expect(service.klaroConfig.services).toContain(jasmine.objectContaining({ name: googleAnalytics }));
283321
});
284322
it('should filter googleAnalytics when empty configuration is retrieved', () => {
285323
configurationDataService.findByPropertyName = jasmine.createSpy().withArgs(GOOGLE_ANALYTICS_KEY).and.returnValue(
286324
createSuccessfulRemoteDataObject$({
287-
... new ConfigurationProperty(),
325+
...new ConfigurationProperty(),
288326
name: googleAnalytics,
289327
values: [],
290328
}));
291329

292330
service.initialize();
293-
expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({name: googleAnalytics}));
331+
expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics }));
294332
});
295333
it('should filter googleAnalytics when an error occurs', () => {
296334
configurationDataService.findByPropertyName = jasmine.createSpy().withArgs(GOOGLE_ANALYTICS_KEY).and.returnValue(
297335
createFailedRemoteDataObject$('Erro while loading GA')
298336
);
299337
service.initialize();
300-
expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({name: googleAnalytics}));
338+
expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics }));
301339
});
302340
it('should filter googleAnalytics when an invalid payload is retrieved', () => {
303341
configurationDataService.findByPropertyName = jasmine.createSpy().withArgs(GOOGLE_ANALYTICS_KEY).and.returnValue(
304342
createSuccessfulRemoteDataObject$(null)
305343
);
306344
service.initialize();
307-
expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({name: googleAnalytics}));
345+
expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics }));
308346
});
309347
});
310348
});

src/app/shared/cookies/browser-klaro.service.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { EPersonDataService } from '../../core/eperson/eperson-data.service';
1313
import { cloneDeep, debounce } from 'lodash';
1414
import { ANONYMOUS_STORAGE_NAME_KLARO, klaroConfiguration } from './klaro-configuration';
1515
import { Operation } from 'fast-json-patch';
16-
import { getFirstCompletedRemoteData} from '../../core/shared/operators';
16+
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
1717
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
1818

1919
/**
@@ -121,6 +121,23 @@ export class BrowserKlaroService extends KlaroService {
121121
});
122122
}
123123

124+
/**
125+
* Return saved preferences stored in the klaro cookie
126+
*/
127+
getSavedPreferences(): Observable<any> {
128+
return this.getUser$().pipe(
129+
map((user: EPerson) => {
130+
let storageName;
131+
if (isEmpty(user)) {
132+
storageName = ANONYMOUS_STORAGE_NAME_KLARO;
133+
} else {
134+
storageName = this.getStorageName(user.uuid);
135+
}
136+
return this.cookieService.get(storageName);
137+
})
138+
);
139+
}
140+
124141
/**
125142
* Initialize configuration for the logged in user
126143
* @param user The authenticated user

src/app/shared/cookies/klaro-configuration.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const HAS_AGREED_END_USER = 'dsHasAgreedEndUser';
1212
*/
1313
export const ANONYMOUS_STORAGE_NAME_KLARO = 'klaro-anonymous';
1414

15+
export const GOOGLE_ANALYTICS_KLARO_KEY = 'google-analytics';
16+
1517
/**
1618
* Klaro configuration
1719
* For more information see https://kiprotect.com/docs/klaro/annotated-config
@@ -113,7 +115,7 @@ export const klaroConfiguration: any = {
113115
]
114116
},
115117
{
116-
name: 'google-analytics',
118+
name: GOOGLE_ANALYTICS_KLARO_KEY,
117119
purposes: ['statistical'],
118120
required: false,
119121
cookies: [

src/app/shared/cookies/klaro.service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Injectable } from '@angular/core';
22

3+
import { Observable } from 'rxjs';
4+
35
/**
46
* Abstract class representing a service for handling Klaro consent preferences and UI
57
*/
@@ -11,7 +13,12 @@ export abstract class KlaroService {
1113
abstract initialize();
1214

1315
/**
14-
* Shows a the dialog with the current consent preferences
16+
* Shows a dialog with the current consent preferences
1517
*/
1618
abstract showSettings();
19+
20+
/**
21+
* Return saved preferences stored in the klaro cookie
22+
*/
23+
abstract getSavedPreferences(): Observable<any>;
1724
}

src/app/statistics/google-analytics.service.spec.ts

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import { GoogleAnalyticsService } from './google-analytics.service';
21
import { Angulartics2GoogleTagManager } from 'angulartics2';
2+
import { of } from 'rxjs';
3+
4+
import { GoogleAnalyticsService } from './google-analytics.service';
35
import { ConfigurationDataService } from '../core/data/configuration-data.service';
46
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
57
import { ConfigurationProperty } from '../core/shared/configuration-property.model';
8+
import { KlaroService } from '../shared/cookies/klaro.service';
9+
import { GOOGLE_ANALYTICS_KLARO_KEY } from '../shared/cookies/klaro-configuration';
610

711
describe('GoogleAnalyticsService', () => {
812
const trackingIdProp = 'google.analytics.key';
@@ -12,6 +16,7 @@ describe('GoogleAnalyticsService', () => {
1216
let service: GoogleAnalyticsService;
1317
let angularticsSpy: Angulartics2GoogleTagManager;
1418
let configSpy: ConfigurationDataService;
19+
let klaroServiceSpy: jasmine.SpyObj<KlaroService>;
1520
let scriptElementMock: any;
1621
let srcSpy: any;
1722
let innerHTMLSpy: any;
@@ -31,6 +36,10 @@ describe('GoogleAnalyticsService', () => {
3136
'startTracking',
3237
]);
3338

39+
klaroServiceSpy = jasmine.createSpyObj('KlaroService', {
40+
'getSavedPreferences': jasmine.createSpy('getSavedPreferences')
41+
});
42+
3443
configSpy = createConfigSuccessSpy(trackingIdTestValue);
3544

3645
scriptElementMock = {
@@ -53,7 +62,11 @@ describe('GoogleAnalyticsService', () => {
5362
body: bodyElementSpy,
5463
});
5564

56-
service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy);
65+
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
66+
GOOGLE_ANALYTICS_KLARO_KEY: true
67+
}));
68+
69+
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy );
5770
});
5871

5972
it('should be created', () => {
@@ -73,7 +86,11 @@ describe('GoogleAnalyticsService', () => {
7386
findByPropertyName: createFailedRemoteDataObject$(),
7487
});
7588

76-
service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy);
89+
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
90+
GOOGLE_ANALYTICS_KLARO_KEY: true
91+
}));
92+
93+
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy);
7794
});
7895

7996
it('should NOT add a script to the body', () => {
@@ -91,7 +108,49 @@ describe('GoogleAnalyticsService', () => {
91108
describe('when the tracking id is empty', () => {
92109
beforeEach(() => {
93110
configSpy = createConfigSuccessSpy();
94-
service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy);
111+
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
112+
[GOOGLE_ANALYTICS_KLARO_KEY]: true
113+
}));
114+
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy);
115+
});
116+
117+
it('should NOT add a script to the body', () => {
118+
service.addTrackingIdToPage();
119+
expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(0);
120+
});
121+
122+
it('should NOT start tracking', () => {
123+
service.addTrackingIdToPage();
124+
expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0);
125+
});
126+
});
127+
128+
describe('when google-analytics cookie preferences are not existing', () => {
129+
beforeEach(() => {
130+
configSpy = createConfigSuccessSpy(trackingIdTestValue);
131+
klaroServiceSpy.getSavedPreferences.and.returnValue(of({}));
132+
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy);
133+
});
134+
135+
it('should NOT add a script to the body', () => {
136+
service.addTrackingIdToPage();
137+
expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(0);
138+
});
139+
140+
it('should NOT start tracking', () => {
141+
service.addTrackingIdToPage();
142+
expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0);
143+
});
144+
});
145+
146+
147+
describe('when google-analytics cookie preferences are set to false', () => {
148+
beforeEach(() => {
149+
configSpy = createConfigSuccessSpy(trackingIdTestValue);
150+
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
151+
[GOOGLE_ANALYTICS_KLARO_KEY]: false
152+
}));
153+
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy);
95154
});
96155

97156
it('should NOT add a script to the body', () => {
@@ -105,7 +164,16 @@ describe('GoogleAnalyticsService', () => {
105164
});
106165
});
107166

108-
describe('when the tracking id is non-empty', () => {
167+
describe('when both google-analytics cookie and the tracking id are non-empty', () => {
168+
169+
beforeEach(() => {
170+
configSpy = createConfigSuccessSpy(trackingIdTestValue);
171+
klaroServiceSpy.getSavedPreferences.and.returnValue(of({
172+
[GOOGLE_ANALYTICS_KLARO_KEY]: true
173+
}));
174+
service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy);
175+
});
176+
109177
it('should create a script tag whose innerHTML contains the tracking id', () => {
110178
service.addTrackingIdToPage();
111179
expect(documentSpy.createElement).toHaveBeenCalledTimes(2);

0 commit comments

Comments
 (0)