Skip to content

Commit 6adbfb7

Browse files
authored
Merge pull request #90 from dsteelma-umd/feature/LIBDRUM-954
LIBDRUM-954. Incorporated 4Science Matomo patch into DRUM https://umd-dit.atlassian.net/browse/LIBDRUM-954
2 parents 61cdf5f + e6ba5b8 commit 6adbfb7

20 files changed

Lines changed: 477 additions & 52 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
"ng2-file-upload": "5.0.0",
114114
"ng2-nouislider": "^2.0.0",
115115
"ngx-infinite-scroll": "^16.0.0",
116+
"ngx-matomo-client": "^6.4.1",
116117
"ngx-pagination": "6.0.3",
117118
"ngx-skeleton-loader": "^9.0.0",
118119
"ngx-ui-switch": "^14.1.0",

src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.spec.ts

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
/* eslint-disable import-newlines/enforce */
33
/* eslint-disable simple-import-sort/imports */
44
// End Customization
5-
import { CommonModule } from '@angular/common';
5+
import {
6+
CommonModule,
7+
Location,
8+
} from '@angular/common';
69
import { PLATFORM_ID } from '@angular/core';
710
import {
811
ComponentFixture,
@@ -20,13 +23,15 @@ import { of as observableOf } from 'rxjs';
2023
import { RESTRICTED_ACCESS_MODULE_PATH } from '../../app-routing-paths';
2124
// End UMD Customization
2225
import { AuthService } from '../../core/auth/auth.service';
26+
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
2327
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
2428
import { SignpostingDataService } from '../../core/data/signposting-data.service';
2529
import { HardRedirectService } from '../../core/services/hard-redirect.service';
2630
import { ServerResponseService } from '../../core/services/server-response.service';
2731
import { Bitstream } from '../../core/shared/bitstream.model';
2832
import { FileService } from '../../core/shared/file.service';
2933
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
34+
import { MatomoService } from '../../statistics/matomo.service';
3035
import { BitstreamDownloadPageComponent } from './bitstream-download-page.component';
3136

3237
describe('BitstreamDownloadPageComponent', () => {
@@ -39,10 +44,13 @@ describe('BitstreamDownloadPageComponent', () => {
3944
let hardRedirectService: HardRedirectService;
4045
let activatedRoute;
4146
let router;
47+
let location: Location;
48+
let dsoNameService: DSONameService;
4249

4350
let bitstream: Bitstream;
4451
let serverResponseService: jasmine.SpyObj<ServerResponseService>;
4552
let signpostingDataService: jasmine.SpyObj<SignpostingDataService>;
53+
let matomoService: jasmine.SpyObj<MatomoService>;
4654

4755
const mocklink = {
4856
href: 'http://test.org',
@@ -60,6 +68,7 @@ describe('BitstreamDownloadPageComponent', () => {
6068
authService = jasmine.createSpyObj('authService', {
6169
isAuthenticated: observableOf(true),
6270
setRedirectUrl: {},
71+
getShortlivedToken: observableOf('token'),
6372
});
6473
authorizationService = jasmine.createSpyObj('authorizationSerivice', {
6574
isAuthorized: observableOf(true),
@@ -69,9 +78,18 @@ describe('BitstreamDownloadPageComponent', () => {
6978
retrieveFileDownloadLink: observableOf('content-url-with-headers'),
7079
});
7180

72-
hardRedirectService = jasmine.createSpyObj('fileService', {
81+
hardRedirectService = jasmine.createSpyObj('hardRedirectService', {
7382
redirect: {},
7483
});
84+
85+
location = jasmine.createSpyObj('location', {
86+
back: {},
87+
});
88+
89+
dsoNameService = jasmine.createSpyObj('dsoNameService', {
90+
getName: 'Test Bitstream',
91+
});
92+
7593
bitstream = Object.assign(new Bitstream(), {
7694
uuid: 'bitstreamUuid',
7795
_links: {
@@ -100,6 +118,8 @@ describe('BitstreamDownloadPageComponent', () => {
100118
signpostingDataService = jasmine.createSpyObj('SignpostingDataService', {
101119
getLinks: observableOf([mocklink, mocklink2]),
102120
});
121+
matomoService = jasmine.createSpyObj('MatomoService', ['appendVisitorId']);
122+
matomoService.appendVisitorId.and.callFake((link) => observableOf(link));
103123
}
104124

105125
function initTestbed() {
@@ -114,7 +134,10 @@ describe('BitstreamDownloadPageComponent', () => {
114134
{ provide: HardRedirectService, useValue: hardRedirectService },
115135
{ provide: ServerResponseService, useValue: serverResponseService },
116136
{ provide: SignpostingDataService, useValue: signpostingDataService },
137+
{ provide: MatomoService, useValue: matomoService },
117138
{ provide: PLATFORM_ID, useValue: 'server' },
139+
{ provide: Location, useValue: location },
140+
{ provide: DSONameService, useValue: dsoNameService },
118141
],
119142
})
120143
.compileComponents();
@@ -148,9 +171,11 @@ describe('BitstreamDownloadPageComponent', () => {
148171
component = fixture.componentInstance;
149172
fixture.detectChanges();
150173
});
151-
it('should redirect to the content link', () => {
152-
expect(hardRedirectService.redirect).toHaveBeenCalledWith('bitstream-content-link');
153-
});
174+
it('should redirect to the content link', waitForAsync(() => {
175+
fixture.whenStable().then(() => {
176+
expect(hardRedirectService.redirect).toHaveBeenCalledWith('bitstream-content-link');
177+
});
178+
}));
154179
it('should add the signposting links', () => {
155180
expect(serverResponseService.setHeader).toHaveBeenCalled();
156181
});
@@ -165,9 +190,11 @@ describe('BitstreamDownloadPageComponent', () => {
165190
component = fixture.componentInstance;
166191
fixture.detectChanges();
167192
});
168-
it('should redirect to an updated content link', () => {
169-
expect(hardRedirectService.redirect).toHaveBeenCalledWith('content-url-with-headers');
170-
});
193+
it('should redirect to an updated content link', waitForAsync(() => {
194+
fixture.whenStable().then(() => {
195+
expect(hardRedirectService.redirect).toHaveBeenCalledWith('content-url-with-headers');
196+
});
197+
}));
171198
});
172199
describe('when the user is not authorized and logged in', () => {
173200
beforeEach(waitForAsync(() => {
@@ -181,11 +208,13 @@ describe('BitstreamDownloadPageComponent', () => {
181208
fixture.detectChanges();
182209
});
183210
// UMD Customization
184-
it('should navigate to the restricted access route', () => {
185-
expect(router.navigateByUrl).toHaveBeenCalledWith(
186-
`${RESTRICTED_ACCESS_MODULE_PATH}/bitstreamUuid`, { replaceUrl: true },
187-
);
188-
});
211+
it('should navigate to the restricted access route', waitForAsync(() => {
212+
fixture.whenStable().then(() => {
213+
expect(router.navigateByUrl).toHaveBeenCalledWith(
214+
`${RESTRICTED_ACCESS_MODULE_PATH}/bitstreamUuid`, { replaceUrl: true },
215+
);
216+
});
217+
}));
189218
// End UMD Customization
190219
});
191220
describe('when the user is not authorized and not logged in', () => {
@@ -201,11 +230,13 @@ describe('BitstreamDownloadPageComponent', () => {
201230
fixture.detectChanges();
202231
});
203232
// UMD Customization
204-
it('should navigate to the restricted access route', () => {
205-
expect(router.navigateByUrl).toHaveBeenCalledWith(
206-
`${RESTRICTED_ACCESS_MODULE_PATH}/bitstreamUuid`, { replaceUrl: true },
207-
);
208-
});
233+
it('should navigate to the restricted access route', waitForAsync(() => {
234+
fixture.whenStable().then(() => {
235+
expect(router.navigateByUrl).toHaveBeenCalledWith(
236+
`${RESTRICTED_ACCESS_MODULE_PATH}/bitstreamUuid`, { replaceUrl: true },
237+
);
238+
});
239+
}));
209240
// End UMD Customization
210241
});
211242
});

src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
hasValue,
5151
isNotEmpty,
5252
} from '../../shared/empty.util';
53+
import { MatomoService } from '../../statistics/matomo.service';
5354

5455
@Component({
5556
selector: 'ds-bitstream-download-page',
@@ -79,6 +80,7 @@ export class BitstreamDownloadPageComponent implements OnInit {
7980
public dsoNameService: DSONameService,
8081
private signpostingDataService: SignpostingDataService,
8182
private responseService: ServerResponseService,
83+
private matomoService: MatomoService,
8284
@Inject(PLATFORM_ID) protected platformId: string,
8385
) {
8486
this.initPageLinks();
@@ -115,14 +117,20 @@ export class BitstreamDownloadPageComponent implements OnInit {
115117
return [isAuthorized, isLoggedIn, bitstream, fileLink];
116118
}));
117119
} else {
118-
return [[isAuthorized, isLoggedIn, bitstream, '']];
120+
return [[isAuthorized, isLoggedIn, bitstream, bitstream._links.content.href]];
119121
}
120122
}),
123+
switchMap(([isAuthorized, isLoggedIn, bitstream, fileLink]: [boolean, boolean, Bitstream, string]) =>
124+
this.matomoService.appendVisitorId(fileLink)
125+
.pipe(
126+
map((fileLinkWithVisitorId) => [isAuthorized, isLoggedIn, bitstream, fileLinkWithVisitorId]),
127+
),
128+
),
121129
).subscribe(([isAuthorized, isLoggedIn, bitstream, fileLink]: [boolean, boolean, Bitstream, string]) => {
122130
if (isAuthorized && isLoggedIn && isNotEmpty(fileLink)) {
123131
this.hardRedirectService.redirect(fileLink);
124132
} else if (isAuthorized && !isLoggedIn) {
125-
this.hardRedirectService.redirect(bitstream._links.content.href);
133+
this.hardRedirectService.redirect(fileLink);
126134
// UMD Customization
127135
} else if (!isAuthorized && isLoggedIn) {
128136
// This can happen due to a "Campus" group IP restrictions

src/app/core/shared/search/search.service.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { SearchService } from './search.service';
3737
import { SearchConfigurationService } from './search-configuration.service';
3838
import anything = jasmine.anything;
3939

40+
4041
@Component({
4142
template: '',
4243
standalone: true,

src/app/core/shared/search/search.service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ export class SearchService {
367367
const appliedFilter = appliedFilters[i];
368368
filters.push(appliedFilter);
369369
}
370-
this.angulartics2.eventTrack.next({
370+
const searchTrackObject = {
371371
action: 'search',
372372
properties: {
373373
searchOptions: config,
@@ -384,7 +384,9 @@ export class SearchService {
384384
filters: filters,
385385
clickedObject,
386386
},
387-
});
387+
};
388+
389+
this.angulartics2.eventTrack.next(searchTrackObject);
388390
}
389391

390392
/**

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ANONYMOUS_STORAGE_NAME_KLARO } from './klaro-configuration';
2828
describe('BrowserKlaroService', () => {
2929
const trackingIdProp = 'google.analytics.key';
3030
const trackingIdTestValue = 'mock-tracking-id';
31+
const matomoTrackingId = 'matomo-tracking-id';
3132
const googleAnalytics = 'google-analytics';
3233
const recaptchaProp = 'registration.verification.enabled';
3334
const recaptchaValue = 'true';
@@ -309,9 +310,11 @@ describe('BrowserKlaroService', () => {
309310
describe('initialize google analytics configuration', () => {
310311
let GOOGLE_ANALYTICS_KEY;
311312
let REGISTRATION_VERIFICATION_ENABLED_KEY;
313+
let MATOMO_ENABLED;
312314
beforeEach(() => {
313315
GOOGLE_ANALYTICS_KEY = clone((service as any).GOOGLE_ANALYTICS_KEY);
314316
REGISTRATION_VERIFICATION_ENABLED_KEY = clone((service as any).REGISTRATION_VERIFICATION_ENABLED_KEY);
317+
MATOMO_ENABLED = clone((service as any).MATOMO_ENABLED);
315318
spyOn((service as any), 'getUser$').and.returnValue(observableOf(user));
316319
translateService.get.and.returnValue(observableOf('loading...'));
317320
spyOn(service, 'addAppMessages');
@@ -360,6 +363,15 @@ describe('BrowserKlaroService', () => {
360363
name: trackingIdTestValue,
361364
values: ['false'],
362365
}),
366+
)
367+
.withArgs(MATOMO_ENABLED)
368+
.and
369+
.returnValue(
370+
createSuccessfulRemoteDataObject$({
371+
... new ConfigurationProperty(),
372+
name: matomoTrackingId,
373+
values: ['false'],
374+
}),
363375
);
364376

365377
service.initialize();
@@ -379,6 +391,15 @@ describe('BrowserKlaroService', () => {
379391
name: trackingIdTestValue,
380392
values: ['false'],
381393
}),
394+
)
395+
.withArgs(MATOMO_ENABLED)
396+
.and
397+
.returnValue(
398+
createSuccessfulRemoteDataObject$({
399+
... new ConfigurationProperty(),
400+
name: matomoTrackingId,
401+
values: ['false'],
402+
}),
382403
);
383404
service.initialize();
384405
expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics }));
@@ -397,6 +418,15 @@ describe('BrowserKlaroService', () => {
397418
name: trackingIdTestValue,
398419
values: ['false'],
399420
}),
421+
)
422+
.withArgs(MATOMO_ENABLED)
423+
.and
424+
.returnValue(
425+
createSuccessfulRemoteDataObject$({
426+
... new ConfigurationProperty(),
427+
name: matomoTrackingId,
428+
values: ['false'],
429+
}),
400430
);
401431
service.initialize();
402432
expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics }));

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { KlaroService } from './klaro.service';
3535
import {
3636
ANONYMOUS_STORAGE_NAME_KLARO,
3737
klaroConfiguration,
38+
MATOMO_KLARO_KEY,
3839
} from './klaro-configuration';
3940

4041
/**
@@ -85,6 +86,8 @@ export class BrowserKlaroService extends KlaroService {
8586

8687
private readonly GOOGLE_ANALYTICS_SERVICE_NAME = 'google-analytics';
8788

89+
private readonly MATOMO_ENABLED = 'matomo.enabled';
90+
8891
/**
8992
* Initial Klaro configuration
9093
*/
@@ -126,15 +129,26 @@ export class BrowserKlaroService extends KlaroService {
126129
),
127130
);
128131

129-
const servicesToHide$: Observable<string[]> = observableCombineLatest([hideGoogleAnalytics$, hideRegistrationVerification$]).pipe(
130-
map(([hideGoogleAnalytics, hideRegistrationVerification]) => {
132+
const hideMatomo$ =
133+
this.configService.findByPropertyName(this.MATOMO_ENABLED).pipe(
134+
getFirstCompletedRemoteData(),
135+
map((remoteData) =>
136+
!remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values) || remoteData.payload.values[0].toLowerCase() !== 'true',
137+
),
138+
);
139+
140+
const servicesToHide$: Observable<string[]> = observableCombineLatest([hideGoogleAnalytics$, hideRegistrationVerification$, hideMatomo$]).pipe(
141+
map(([hideGoogleAnalytics, hideRegistrationVerification, hideMatomo]) => {
131142
const servicesToHideArray: string[] = [];
132143
if (hideGoogleAnalytics) {
133144
servicesToHideArray.push(this.GOOGLE_ANALYTICS_SERVICE_NAME);
134145
}
135146
if (hideRegistrationVerification) {
136147
servicesToHideArray.push(CAPTCHA_NAME);
137148
}
149+
if (hideMatomo) {
150+
servicesToHideArray.push(MATOMO_KLARO_KEY);
151+
}
138152
return servicesToHideArray;
139153
}),
140154
);

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export const ANONYMOUS_STORAGE_NAME_KLARO = 'klaro-anonymous';
2121

2222
export const GOOGLE_ANALYTICS_KLARO_KEY = 'google-analytics';
2323

24+
export const MATOMO_KLARO_KEY = 'matomo';
25+
26+
export const MATOMO_COOKIE = 'dsMatomo';
27+
2428
/**
2529
* Klaro configuration
2630
* For more information see https://klaro.org/docs/integration/annotated-configuration
@@ -150,6 +154,16 @@ export const klaroConfiguration: any = {
150154
HAS_AGREED_END_USER,
151155
],
152156
},
157+
{
158+
name: MATOMO_KLARO_KEY,
159+
purposes: ['statistical'],
160+
required: false,
161+
cookies: [
162+
MATOMO_COOKIE,
163+
],
164+
onAccept: `window?.changeMatomoConsent(true)`,
165+
onDecline: `window?.changeMatomoConsent(false)`,
166+
},
153167
{
154168
name: GOOGLE_ANALYTICS_KLARO_KEY,
155169
purposes: ['statistical'],

0 commit comments

Comments
 (0)