Skip to content

Commit cb0a8c3

Browse files
authored
Merge pull request #91 from dsteelma-umd/feature/LIBDRUM-973
LIBDRUM-973. Updated Matomo with 4Science pull requests https://umd-dit.atlassian.net/browse/LIBDRUM-973
2 parents 6adbfb7 + f1a55b8 commit cb0a8c3

7 files changed

Lines changed: 198 additions & 52 deletions

File tree

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

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { RESTRICTED_ACCESS_MODULE_PATH } from '../../app-routing-paths';
2424
// End UMD Customization
2525
import { AuthService } from '../../core/auth/auth.service';
2626
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
27+
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
2728
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
2829
import { SignpostingDataService } from '../../core/data/signposting-data.service';
2930
import { HardRedirectService } from '../../core/services/hard-redirect.service';
@@ -97,16 +98,16 @@ describe('BitstreamDownloadPageComponent', () => {
9798
self: { href: 'bitstream-self-link' },
9899
},
99100
});
100-
101101
activatedRoute = {
102102
data: observableOf({
103-
bitstream: createSuccessfulRemoteDataObject(
104-
bitstream,
105-
),
103+
bitstream: createSuccessfulRemoteDataObject(bitstream),
106104
}),
107105
params: observableOf({
108106
id: 'testid',
109107
}),
108+
queryParams: observableOf({
109+
accessToken: undefined,
110+
}),
110111
};
111112

112113
router = jasmine.createSpyObj('router', ['navigateByUrl']);
@@ -118,7 +119,10 @@ describe('BitstreamDownloadPageComponent', () => {
118119
signpostingDataService = jasmine.createSpyObj('SignpostingDataService', {
119120
getLinks: observableOf([mocklink, mocklink2]),
120121
});
121-
matomoService = jasmine.createSpyObj('MatomoService', ['appendVisitorId']);
122+
matomoService = jasmine.createSpyObj('MatomoService', {
123+
appendVisitorId: observableOf(''),
124+
isMatomoEnabled$: observableOf(true),
125+
});
122126
matomoService.appendVisitorId.and.callFake((link) => observableOf(link));
123127
}
124128

@@ -138,6 +142,7 @@ describe('BitstreamDownloadPageComponent', () => {
138142
{ provide: PLATFORM_ID, useValue: 'server' },
139143
{ provide: Location, useValue: location },
140144
{ provide: DSONameService, useValue: dsoNameService },
145+
{ provide: ConfigurationDataService, useValue: {} },
141146
],
142147
})
143148
.compileComponents();
@@ -240,4 +245,42 @@ describe('BitstreamDownloadPageComponent', () => {
240245
// End UMD Customization
241246
});
242247
});
248+
249+
describe('when Matomo is enabled', () => {
250+
beforeEach(waitForAsync(() => {
251+
init();
252+
(matomoService.appendVisitorId as jasmine.Spy).and.callFake((link) => observableOf(link + '?visitorId=12345'));
253+
initTestbed();
254+
}));
255+
beforeEach(() => {
256+
fixture = TestBed.createComponent(BitstreamDownloadPageComponent);
257+
component = fixture.componentInstance;
258+
fixture.detectChanges();
259+
});
260+
it('should append visitor ID to the file link', waitForAsync(() => {
261+
fixture.whenStable().then(() => {
262+
expect(matomoService.appendVisitorId).toHaveBeenCalledWith('content-url-with-headers');
263+
expect(hardRedirectService.redirect).toHaveBeenCalledWith('content-url-with-headers?visitorId=12345');
264+
});
265+
}));
266+
});
267+
268+
describe('when Matomo is not enabled', () => {
269+
beforeEach(waitForAsync(() => {
270+
init();
271+
(matomoService.isMatomoEnabled$ as jasmine.Spy).and.returnValue(observableOf(false));
272+
initTestbed();
273+
}));
274+
beforeEach(() => {
275+
fixture = TestBed.createComponent(BitstreamDownloadPageComponent);
276+
component = fixture.componentInstance;
277+
fixture.detectChanges();
278+
});
279+
it('should not append visitor ID to the file link', waitForAsync(() => {
280+
fixture.whenStable().then(() => {
281+
expect(matomoService.appendVisitorId).not.toHaveBeenCalled();
282+
expect(hardRedirectService.redirect).toHaveBeenCalledWith('content-url-with-headers');
283+
});
284+
}));
285+
});
243286
});

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

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import {
1010
import {
1111
Component,
1212
Inject,
13+
inject,
1314
OnInit,
1415
PLATFORM_ID,
1516
} from '@angular/core';
1617
import {
1718
ActivatedRoute,
19+
Params,
1820
Router,
1921
} from '@angular/router';
2022
import { TranslateModule } from '@ngx-translate/core';
@@ -35,6 +37,7 @@ import { RESTRICTED_ACCESS_MODULE_PATH } from '../../app-routing-paths';
3537
// End UMD Customization
3638
import { AuthService } from '../../core/auth/auth.service';
3739
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
40+
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
3841
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
3942
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
4043
import { RemoteData } from '../../core/data/remote-data';
@@ -69,6 +72,8 @@ export class BitstreamDownloadPageComponent implements OnInit {
6972
bitstream$: Observable<Bitstream>;
7073
bitstreamRD$: Observable<RemoteData<Bitstream>>;
7174

75+
configService = inject(ConfigurationDataService);
76+
7277
constructor(
7378
private route: ActivatedRoute,
7479
protected router: Router,
@@ -91,6 +96,10 @@ export class BitstreamDownloadPageComponent implements OnInit {
9196
}
9297

9398
ngOnInit(): void {
99+
const accessToken$: Observable<string> = this.route.queryParams.pipe(
100+
map((queryParams: Params) => queryParams?.accessToken || null),
101+
take(1),
102+
);
94103

95104
this.bitstreamRD$ = this.route.data.pipe(
96105
map((data) => data.bitstream));
@@ -104,45 +113,58 @@ export class BitstreamDownloadPageComponent implements OnInit {
104113
switchMap((bitstream: Bitstream) => {
105114
const isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(bitstream) ? bitstream.self : undefined);
106115
const isLoggedIn$ = this.auth.isAuthenticated();
107-
return observableCombineLatest([isAuthorized$, isLoggedIn$, observableOf(bitstream)]);
116+
const isMatomoEnabled$ = this.matomoService.isMatomoEnabled$();
117+
return observableCombineLatest([isAuthorized$, isLoggedIn$, isMatomoEnabled$, accessToken$, observableOf(bitstream)]);
108118
}),
109-
filter(([isAuthorized, isLoggedIn, bitstream]: [boolean, boolean, Bitstream]) => hasValue(isAuthorized) && hasValue(isLoggedIn)),
119+
filter(([isAuthorized, isLoggedIn, isMatomoEnabled, accessToken, bitstream]: [boolean, boolean, boolean, string, Bitstream]) => (hasValue(isAuthorized) && hasValue(isLoggedIn)) || hasValue(accessToken)),
110120
take(1),
111-
switchMap(([isAuthorized, isLoggedIn, bitstream]: [boolean, boolean, Bitstream]) => {
121+
switchMap(([isAuthorized, isLoggedIn, isMatomoEnabled, accessToken, bitstream]: [boolean, boolean, boolean, string, Bitstream]) => {
112122
if (isAuthorized && isLoggedIn) {
113123
return this.fileService.retrieveFileDownloadLink(bitstream._links.content.href).pipe(
114124
filter((fileLink) => hasValue(fileLink)),
115125
take(1),
116126
map((fileLink) => {
117-
return [isAuthorized, isLoggedIn, bitstream, fileLink];
127+
return [isAuthorized, isLoggedIn, isMatomoEnabled, bitstream, fileLink];
118128
}));
129+
} else if (hasValue(accessToken)) {
130+
return [[isAuthorized, !isLoggedIn, isMatomoEnabled, bitstream, '', accessToken]];
119131
} else {
120-
return [[isAuthorized, isLoggedIn, bitstream, bitstream._links.content.href]];
132+
return [[isAuthorized, isLoggedIn, isMatomoEnabled, bitstream, bitstream._links.content.href]];
121133
}
122134
}),
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-
),
129-
).subscribe(([isAuthorized, isLoggedIn, bitstream, fileLink]: [boolean, boolean, Bitstream, string]) => {
135+
switchMap(([isAuthorized, isLoggedIn, isMatomoEnabled, bitstream, fileLink, accessToken]: [boolean, boolean, boolean, Bitstream, string, string]) => {
136+
if (isMatomoEnabled) {
137+
return this.matomoService.appendVisitorId(fileLink).pipe(
138+
map((fileLinkWithVisitorId) => [isAuthorized, isLoggedIn, bitstream, fileLinkWithVisitorId, accessToken]),
139+
);
140+
}
141+
return observableOf([isAuthorized, isLoggedIn, bitstream, fileLink, accessToken]);
142+
}),
143+
).subscribe(([isAuthorized, isLoggedIn, bitstream, fileLink, accessToken]: [boolean, boolean, Bitstream, string, string]) => {
130144
if (isAuthorized && isLoggedIn && isNotEmpty(fileLink)) {
131145
this.hardRedirectService.redirect(fileLink);
132-
} else if (isAuthorized && !isLoggedIn) {
146+
} else if (isAuthorized && !isLoggedIn && !hasValue(accessToken)) {
133147
this.hardRedirectService.redirect(fileLink);
134-
// UMD Customization
135-
} else if (!isAuthorized && isLoggedIn) {
136-
// This can happen due to a "Campus" group IP restrictions
137-
void this.router.navigateByUrl(
138-
`${RESTRICTED_ACCESS_MODULE_PATH}/${bitstream.uuid}`,
139-
{ replaceUrl: true },
140-
);
141-
} else if (!isAuthorized && !isLoggedIn) {
142-
void this.router.navigateByUrl(`${RESTRICTED_ACCESS_MODULE_PATH}/${bitstream.uuid}`,
143-
{ replaceUrl: true },
144-
);
145-
// End UMD Customization
148+
} else if (!isAuthorized) {
149+
// Either we have an access token, or we are logged in, or we are not logged in.
150+
// For now, the access token does not care if we are logged in or not.
151+
if (hasValue(accessToken)) {
152+
this.hardRedirectService.redirect(bitstream._links.content.href + '?accessToken=' + accessToken);
153+
} else if (isLoggedIn) {
154+
// UMD Customization
155+
// This can happen due to a "Campus" group IP restrictions
156+
void this.router.navigateByUrl(
157+
`${RESTRICTED_ACCESS_MODULE_PATH}/${bitstream.uuid}`,
158+
{ replaceUrl: true },
159+
);
160+
// End UMD Customization
161+
} else if (!isLoggedIn) {
162+
// UMD Customization
163+
void this.router.navigateByUrl(`${RESTRICTED_ACCESS_MODULE_PATH}/${bitstream.uuid}`,
164+
{ replaceUrl: true },
165+
);
166+
// End UMD Customization
167+
}
146168
}
147169
});
148170
}

src/app/footer/footer.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class FooterComponent implements OnInit {
6161
}
6262

6363
showCookieSettings() {
64-
if (hasValue(this.cookies)) {
64+
if (hasValue(this.cookies) && this.cookies.showSettings instanceof Function) {
6565
this.cookies.showSettings();
6666
}
6767
return false;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { EPerson } from '../../core/eperson/models/eperson.model';
2626
import { CAPTCHA_NAME } from '../../core/google-recaptcha/google-recaptcha.service';
2727
import { CookieService } from '../../core/services/cookie.service';
2828
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
29+
import { MATOMO_ENABLED } from '../../statistics/matomo.service';
2930
import {
3031
hasValue,
3132
isEmpty,
@@ -86,7 +87,7 @@ export class BrowserKlaroService extends KlaroService {
8687

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

89-
private readonly MATOMO_ENABLED = 'matomo.enabled';
90+
private readonly MATOMO_ENABLED = MATOMO_ENABLED;
9091

9192
/**
9293
* Initial Klaro configuration

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Observable } from 'rxjs';
44
/**
55
* Abstract class representing a service for handling Klaro consent preferences and UI
66
*/
7-
@Injectable()
7+
@Injectable({ providedIn: 'root' })
88
export abstract class KlaroService {
99
/**
1010
* Initializes the service

src/app/statistics/matomo.service.spec.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
Injector,
3+
runInInjectionContext,
4+
} from '@angular/core';
15
import {
26
fakeAsync,
37
TestBed,
@@ -23,6 +27,7 @@ import {
2327
createSuccessfulRemoteDataObject$,
2428
} from '../shared/remote-data.utils';
2529
import {
30+
MATOMO_ENABLED,
2631
MATOMO_SITE_ID,
2732
MATOMO_TRACKER_URL,
2833
MatomoService,
@@ -52,6 +57,7 @@ describe('MatomoService', () => {
5257
{ provide: KlaroService, useValue: klaroService },
5358
{ provide: NativeWindowService, useValue: nativeWindowService },
5459
{ provide: ConfigurationDataService, useValue: configService },
60+
{ provide: Injector, useValue: TestBed },
5561
],
5662
});
5763

@@ -69,11 +75,13 @@ describe('MatomoService', () => {
6975
});
7076

7177
it('should call setConsentGiven when consent is true', () => {
78+
service.matomoTracker = matomoTracker;
7279
service.changeMatomoConsent(true);
7380
expect(matomoTracker.setConsentGiven).toHaveBeenCalled();
7481
});
7582

7683
it('should call forgetConsentGiven when consent is false', () => {
84+
service.matomoTracker = matomoTracker;
7785
service.changeMatomoConsent(false);
7886
expect(matomoTracker.forgetConsentGiven).toHaveBeenCalled();
7987
});
@@ -84,10 +92,16 @@ describe('MatomoService', () => {
8492
configService.findByPropertyName.withArgs(MATOMO_TRACKER_URL).and.returnValue(
8593
createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['http://matomo'] })),
8694
);
95+
configService.findByPropertyName.withArgs(MATOMO_ENABLED).and.returnValue(
96+
createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['true'] })),
97+
);
8798
configService.findByPropertyName.withArgs(MATOMO_SITE_ID).and.returnValue(
8899
createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { values: ['1'] })));
89100
klaroService.getSavedPreferences.and.returnValue(of({ matomo: true }));
90-
service.init();
101+
102+
runInInjectionContext(TestBed, () => {
103+
service.init();
104+
});
91105

92106
expect(matomoTracker.setConsentGiven).toHaveBeenCalled();
93107
expect(matomoInitializer.initializeTracker).toHaveBeenCalledWith({
@@ -96,36 +110,66 @@ describe('MatomoService', () => {
96110
});
97111
});
98112

99-
it('should initialize tracker with REST configuration correct parameters in production', () => {
113+
it('should initialize tracker with REST configuration correct parameters in production', fakeAsync(() => {
100114
environment.production = true;
101115
environment.matomo = { trackerUrl: '' };
102116
configService.findByPropertyName.withArgs(MATOMO_TRACKER_URL).and.returnValue(
103117
createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['http://matomo'] })),
104118
);
119+
configService.findByPropertyName.withArgs(MATOMO_ENABLED).and.returnValue(
120+
createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['true'] })),
121+
);
105122
configService.findByPropertyName.withArgs(MATOMO_SITE_ID).and.returnValue(
106123
createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { values: ['1'] })));
107124
klaroService.getSavedPreferences.and.returnValue(of({ matomo: true }));
108125

109-
service.init();
126+
runInInjectionContext(TestBed, () => {
127+
service.init();
128+
});
129+
130+
tick();
110131

111132
expect(matomoTracker.setConsentGiven).toHaveBeenCalled();
112133
expect(matomoInitializer.initializeTracker).toHaveBeenCalledWith({
113134
siteId: '1',
114135
trackerUrl: 'http://matomo',
115136
});
116-
});
137+
}));
117138

118139
it('should not initialize tracker if not in production', () => {
119140
environment.production = false;
120141

121-
service.init();
142+
runInInjectionContext(TestBed, () => {
143+
service.init();
144+
});
145+
146+
expect(matomoInitializer.initializeTracker).not.toHaveBeenCalled();
147+
});
148+
149+
it('should not initialize tracker if matomo is disabled', () => {
150+
environment.production = true;
151+
environment.matomo = { trackerUrl: '' };
152+
configService.findByPropertyName.withArgs(MATOMO_TRACKER_URL).and.returnValue(
153+
createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['http://example.com'] })),
154+
);
155+
configService.findByPropertyName.withArgs(MATOMO_ENABLED).and.returnValue(
156+
createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['false'] })),
157+
);
158+
configService.findByPropertyName.withArgs(MATOMO_SITE_ID).and.returnValue(
159+
createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { values: ['1'] })));
160+
klaroService.getSavedPreferences.and.returnValue(of({ matomo: true }));
161+
162+
runInInjectionContext(TestBed, () => {
163+
service.init();
164+
});
122165

123166
expect(matomoInitializer.initializeTracker).not.toHaveBeenCalled();
124167
});
125168

126169
describe('with visitorId set', () => {
127170
beforeEach(() => {
128171
matomoTracker.getVisitorId.and.returnValue(Promise.resolve('12345'));
172+
service.matomoTracker = matomoTracker;
129173
});
130174

131175
it('should add trackerId parameter', fakeAsync(() => {

0 commit comments

Comments
 (0)