[CST-18964] add matomo integration

This commit is contained in:
Andrea Barbasso
2025-02-05 11:37:00 +01:00
parent 0649342658
commit 2225c2e428
16 changed files with 165 additions and 4 deletions

13
package-lock.json generated
View File

@@ -66,6 +66,7 @@
"ng2-file-upload": "5.0.0", "ng2-file-upload": "5.0.0",
"ng2-nouislider": "^2.0.0", "ng2-nouislider": "^2.0.0",
"ngx-infinite-scroll": "^16.0.0", "ngx-infinite-scroll": "^16.0.0",
"ngx-matomo-client": "^6.4.1",
"ngx-pagination": "6.0.3", "ngx-pagination": "6.0.3",
"ngx-skeleton-loader": "^9.0.0", "ngx-skeleton-loader": "^9.0.0",
"ngx-ui-switch": "^14.1.0", "ngx-ui-switch": "^14.1.0",
@@ -16935,6 +16936,18 @@
"@angular/forms": ">=10.0.0" "@angular/forms": ">=10.0.0"
} }
}, },
"node_modules/ngx-matomo-client": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/ngx-matomo-client/-/ngx-matomo-client-6.4.1.tgz",
"integrity": "sha512-GRriCGW0ULCg9oSZw3ule+o9esELVVJTJ0Z99/zYKGjlyrrHLn5a1e0GSdgICubo59gP1cg9NwsOC0BH7oio9A==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": "^17.0.0 || ^18.0.0",
"@angular/core": "^17.0.0 || ^18.0.0"
}
},
"node_modules/ngx-pagination": { "node_modules/ngx-pagination": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/ngx-pagination/-/ngx-pagination-6.0.3.tgz", "resolved": "https://registry.npmjs.org/ngx-pagination/-/ngx-pagination-6.0.3.tgz",

View File

@@ -153,6 +153,7 @@
"ng2-file-upload": "5.0.0", "ng2-file-upload": "5.0.0",
"ng2-nouislider": "^2.0.0", "ng2-nouislider": "^2.0.0",
"ngx-infinite-scroll": "^16.0.0", "ngx-infinite-scroll": "^16.0.0",
"ngx-matomo-client": "^6.4.1",
"ngx-pagination": "6.0.3", "ngx-pagination": "6.0.3",
"ngx-skeleton-loader": "^9.0.0", "ngx-skeleton-loader": "^9.0.0",
"ngx-ui-switch": "^14.1.0", "ngx-ui-switch": "^14.1.0",

View File

@@ -1,6 +1,7 @@
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Angulartics2 } from 'angulartics2'; import { Angulartics2 } from 'angulartics2';
import { MatomoTracker } from 'ngx-matomo-client';
import { import {
BehaviorSubject, BehaviorSubject,
combineLatest as observableCombineLatest, combineLatest as observableCombineLatest,
@@ -112,6 +113,7 @@ export class SearchService {
private paginationService: PaginationService, private paginationService: PaginationService,
private searchConfigurationService: SearchConfigurationService, private searchConfigurationService: SearchConfigurationService,
private angulartics2: Angulartics2, private angulartics2: Angulartics2,
private matomoTracker: MatomoTracker,
) { ) {
this.searchDataService = new SearchDataService(); this.searchDataService = new SearchDataService();
} }
@@ -367,7 +369,7 @@ export class SearchService {
const appliedFilter = appliedFilters[i]; const appliedFilter = appliedFilters[i];
filters.push(appliedFilter); filters.push(appliedFilter);
} }
this.angulartics2.eventTrack.next({ const searchTrackObject = {
action: 'search', action: 'search',
properties: { properties: {
searchOptions: config, searchOptions: config,
@@ -384,7 +386,10 @@ export class SearchService {
filters: filters, filters: filters,
clickedObject, clickedObject,
}, },
}); };
this.matomoTracker.trackSiteSearch(config.query, config.scope, searchQueryResponse.pageInfo.totalElements, searchTrackObject);
this.angulartics2.eventTrack.next(searchTrackObject);
} }
/** /**

View File

@@ -39,6 +39,7 @@ import { OrejimeService } from './orejime.service';
import { import {
ANONYMOUS_STORAGE_NAME_OREJIME, ANONYMOUS_STORAGE_NAME_OREJIME,
getOrejimeConfiguration, getOrejimeConfiguration,
MATOMO_OREJIME_KEY,
} from './orejime-configuration'; } from './orejime-configuration';
/** /**
@@ -133,8 +134,10 @@ export class BrowserOrejimeService extends OrejimeService {
), ),
); );
const appsToHide$: Observable<string[]> = observableCombineLatest([hideGoogleAnalytics$, hideRegistrationVerification$]).pipe( const hideMatomo$ = observableOf(!(environment.matomo?.trackerUrl && environment.matomo?.siteId));
map(([hideGoogleAnalytics, hideRegistrationVerification]) => {
const appsToHide$: Observable<string[]> = observableCombineLatest([hideGoogleAnalytics$, hideRegistrationVerification$, hideMatomo$]).pipe(
map(([hideGoogleAnalytics, hideRegistrationVerification, hideMatomo]) => {
const appsToHideArray: string[] = []; const appsToHideArray: string[] = [];
if (hideGoogleAnalytics) { if (hideGoogleAnalytics) {
appsToHideArray.push(this.GOOGLE_ANALYTICS_SERVICE_NAME); appsToHideArray.push(this.GOOGLE_ANALYTICS_SERVICE_NAME);
@@ -142,6 +145,9 @@ export class BrowserOrejimeService extends OrejimeService {
if (hideRegistrationVerification) { if (hideRegistrationVerification) {
appsToHideArray.push(CAPTCHA_NAME); appsToHideArray.push(CAPTCHA_NAME);
} }
if (hideMatomo) {
appsToHideArray.push(MATOMO_OREJIME_KEY);
}
return appsToHideArray; return appsToHideArray;
}), }),
); );

View File

@@ -19,6 +19,10 @@ export const ANONYMOUS_STORAGE_NAME_OREJIME = 'orejime-anonymous';
export const GOOGLE_ANALYTICS_OREJIME_KEY = 'google-analytics'; export const GOOGLE_ANALYTICS_OREJIME_KEY = 'google-analytics';
export const MATOMO_OREJIME_KEY = 'matomo';
export const MATOMO_COOKIE = 'dsMatomo';
/** /**
* Orejime configuration * Orejime configuration
* For more information see https://github.com/empreinte-digitale/orejime * For more information see https://github.com/empreinte-digitale/orejime
@@ -134,6 +138,17 @@ export function getOrejimeConfiguration(_window: NativeWindowRef): any {
HAS_AGREED_END_USER, HAS_AGREED_END_USER,
], ],
}, },
{
name: MATOMO_OREJIME_KEY,
purposes: ['statistical'],
required: false,
cookies: [
MATOMO_COOKIE,
],
callback: (consent: boolean) => {
_window?.nativeWindow.changeMatomoConsent(consent);
},
},
{ {
name: GOOGLE_ANALYTICS_OREJIME_KEY, name: GOOGLE_ANALYTICS_OREJIME_KEY,
purposes: ['statistical'], purposes: ['statistical'],

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { MatomoService } from './matomo.service';
describe('MatomoService', () => {
let service: MatomoService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(MatomoService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,52 @@
import {
inject,
Injectable,
} from '@angular/core';
import {
MatomoInitializerService,
MatomoTracker,
} from 'ngx-matomo-client';
import { environment } from '../../environments/environment';
import { NativeWindowService } from '../core/services/window.service';
import { OrejimeService } from '../shared/cookies/orejime.service';
@Injectable({
providedIn: 'root',
})
export class MatomoService {
matomoInitializer = inject(MatomoInitializerService);
matomoTracker = inject(MatomoTracker);
orejimeService = inject(OrejimeService);
_window = inject(NativeWindowService);
init() {
if (this._window.nativeWindow) {
this._window.nativeWindow.changeMatomoConsent = this.changeMatomoConsent;
}
if (environment.production) {
const preferences$ = this.orejimeService.getSavedPreferences();
preferences$.subscribe(preferences => {
this.changeMatomoConsent(preferences.matomo);
if (environment.matomo?.siteId && environment.matomo?.trackerUrl) {
this.matomoInitializer.initializeTracker({
siteId: environment.matomo.siteId,
trackerUrl: environment.matomo.trackerUrl,
});
}
});
}
}
changeMatomoConsent = (consent: boolean) => {
if (consent) {
this.matomoTracker.setConsentGiven();
} else {
this.matomoTracker.forgetConsentGiven();
}
};
}

View File

@@ -0,0 +1,3 @@
export class MockMatomoTracker {
trackSiteSearch = () => {};
}

View File

@@ -1630,6 +1630,10 @@
"cookies.consent.app.description.google-recaptcha": "We use google reCAPTCHA service during registration and password recovery", "cookies.consent.app.description.google-recaptcha": "We use google reCAPTCHA service during registration and password recovery",
"cookies.consent.app.title.matomo": "Matomo",
"cookies.consent.app.description.matomo": "Allows us to track statistical data",
"cookies.consent.purpose.functional": "Functional", "cookies.consent.purpose.functional": "Functional",
"cookies.consent.purpose.statistical": "Statistical", "cookies.consent.purpose.statistical": "Statistical",

View File

@@ -2029,6 +2029,13 @@
"cookies.consent.app.description.google-recaptcha": "Utilizziamo il servizio Google reCAPTCHA nelle fasi di registrazione e recupero password", "cookies.consent.app.description.google-recaptcha": "Utilizziamo il servizio Google reCAPTCHA nelle fasi di registrazione e recupero password",
// "cookies.consent.app.title.matomo": "Matomo",
"cookies.consent.app.title.matomo": "Matomo",
// "cookies.consent.app.description.matomo": "Allows us to track statistical data",
"cookies.consent.app.description.matomo": "Ci permette di tracciare i dati statistici",
// "cookies.consent.purpose.functional": "Functional", // "cookies.consent.purpose.functional": "Functional",
"cookies.consent.purpose.functional": "Funzionale", "cookies.consent.purpose.functional": "Funzionale",

View File

@@ -24,6 +24,7 @@ import { InfoConfig } from './info-config.interface';
import { ItemConfig } from './item-config.interface'; import { ItemConfig } from './item-config.interface';
import { LangConfig } from './lang-config.interface'; import { LangConfig } from './lang-config.interface';
import { MarkdownConfig } from './markdown-config.interface'; import { MarkdownConfig } from './markdown-config.interface';
import { MatomoConfig } from './matomo-config.interface';
import { MediaViewerConfig } from './media-viewer-config.interface'; import { MediaViewerConfig } from './media-viewer-config.interface';
import { INotificationBoardOptions } from './notifications-config.interfaces'; import { INotificationBoardOptions } from './notifications-config.interfaces';
import { QualityAssuranceConfig } from './quality-assurance.config'; import { QualityAssuranceConfig } from './quality-assurance.config';
@@ -66,6 +67,7 @@ interface AppConfig extends Config {
search: SearchConfig; search: SearchConfig;
notifyMetrics: AdminNotifyMetricsRow[]; notifyMetrics: AdminNotifyMetricsRow[];
liveRegion: LiveRegionConfig; liveRegion: LiveRegionConfig;
matomo?: MatomoConfig;
} }
/** /**

View File

@@ -19,6 +19,7 @@ import { InfoConfig } from './info-config.interface';
import { ItemConfig } from './item-config.interface'; import { ItemConfig } from './item-config.interface';
import { LangConfig } from './lang-config.interface'; import { LangConfig } from './lang-config.interface';
import { MarkdownConfig } from './markdown-config.interface'; import { MarkdownConfig } from './markdown-config.interface';
import { MatomoConfig } from './matomo-config.interface';
import { MediaViewerConfig } from './media-viewer-config.interface'; import { MediaViewerConfig } from './media-viewer-config.interface';
import { INotificationBoardOptions } from './notifications-config.interfaces'; import { INotificationBoardOptions } from './notifications-config.interfaces';
import { QualityAssuranceConfig } from './quality-assurance.config'; import { QualityAssuranceConfig } from './quality-assurance.config';
@@ -599,4 +600,6 @@ export class DefaultAppConfig implements AppConfig {
messageTimeOutDurationMs: 30000, messageTimeOutDurationMs: 30000,
isVisible: false, isVisible: false,
}; };
matomo: MatomoConfig = {};
} }

View File

@@ -0,0 +1,9 @@
import { Config } from './config.interface';
/**
* Configuration interface for Matomo tracking
*/
export interface MatomoConfig extends Config {
trackerUrl?: string;
siteId?: string;
}

View File

@@ -29,6 +29,11 @@ import {
Angulartics2GoogleTagManager, Angulartics2GoogleTagManager,
Angulartics2RouterlessModule, Angulartics2RouterlessModule,
} from 'angulartics2'; } from 'angulartics2';
import {
provideMatomo,
withRouteData,
withRouter,
} from 'ngx-matomo-client';
import { commonAppConfig } from '../../app/app.config'; import { commonAppConfig } from '../../app/app.config';
import { storeModuleConfig } from '../../app/app.reducer'; import { storeModuleConfig } from '../../app/app.reducer';
@@ -157,5 +162,12 @@ export const browserAppConfig: ApplicationConfig = mergeApplicationConfig({
provide: MathService, provide: MathService,
useClass: ClientMathService, useClass: ClientMathService,
}, },
provideMatomo(
{
mode: 'deferred',
},
withRouter(),
withRouteData(),
),
], ],
}, commonAppConfig); }, commonAppConfig);

View File

@@ -45,6 +45,7 @@ import { MenuService } from '../../app/shared/menu/menu.service';
import { ThemeService } from '../../app/shared/theme-support/theme.service'; import { ThemeService } from '../../app/shared/theme-support/theme.service';
import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider'; import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service'; import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service';
import { MatomoService } from '../../app/statistics/matomo.service';
import { import {
StoreAction, StoreAction,
StoreActionTypes, StoreActionTypes,
@@ -85,6 +86,7 @@ export class BrowserInitService extends InitService {
protected router: Router, protected router: Router,
private requestService: RequestService, private requestService: RequestService,
private halService: HALEndpointService, private halService: HALEndpointService,
private matomoService: MatomoService,
) { ) {
super( super(
@@ -124,6 +126,7 @@ export class BrowserInitService extends InitService {
this.initI18n(); this.initI18n();
this.initAngulartics(); this.initAngulartics();
this.initGoogleAnalytics(); this.initGoogleAnalytics();
this.initMatomo();
this.initRouteListeners(); this.initRouteListeners();
this.themeService.listenForThemeChanges(true); this.themeService.listenForThemeChanges(true);
this.trackAuthTokenExpiration(); this.trackAuthTokenExpiration();
@@ -173,6 +176,10 @@ export class BrowserInitService extends InitService {
this.googleAnalyticsService.addTrackingIdToPage(); this.googleAnalyticsService.addTrackingIdToPage();
} }
protected initMatomo(): void {
this.matomoService.init();
}
/** /**
* During an external authentication flow invalidate the * During an external authentication flow invalidate the
* data in the cache. This allows the app to fetch fresh content. * data in the cache. This allows the app to fetch fresh content.

View File

@@ -28,6 +28,7 @@ import {
Angulartics2GoogleAnalytics, Angulartics2GoogleAnalytics,
Angulartics2GoogleGlobalSiteTag, Angulartics2GoogleGlobalSiteTag,
} from 'angulartics2'; } from 'angulartics2';
import { MatomoTracker } from 'ngx-matomo-client';
import { commonAppConfig } from '../../app/app.config'; import { commonAppConfig } from '../../app/app.config';
import { storeModuleConfig } from '../../app/app.reducer'; import { storeModuleConfig } from '../../app/app.reducer';
@@ -55,6 +56,7 @@ import { XSRFService } from '../../app/core/xsrf/xsrf.service';
import { AngularticsProviderMock } from '../../app/shared/mocks/angulartics-provider.service.mock'; import { AngularticsProviderMock } from '../../app/shared/mocks/angulartics-provider.service.mock';
import { Angulartics2Mock } from '../../app/shared/mocks/angulartics2.service.mock'; import { Angulartics2Mock } from '../../app/shared/mocks/angulartics2.service.mock';
import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider'; import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
import { MockMatomoTracker } from '../../app/statistics/mock-matomo-tracker';
import { ServerSubmissionService } from '../../app/submission/server-submission.service'; import { ServerSubmissionService } from '../../app/submission/server-submission.service';
import { SubmissionService } from '../../app/submission/submission.service'; import { SubmissionService } from '../../app/submission/submission.service';
import { TranslateServerLoader } from '../../ngx-translate-loaders/translate-server.loader'; import { TranslateServerLoader } from '../../ngx-translate-loaders/translate-server.loader';
@@ -144,5 +146,9 @@ export const serverAppConfig: ApplicationConfig = mergeApplicationConfig({
provide: MathService, provide: MathService,
useClass: ServerMathService, useClass: ServerMathService,
}, },
{
provide: MatomoTracker,
useClass: MockMatomoTracker,
},
], ],
}, commonAppConfig); }, commonAppConfig);