Skip to content

Commit

Permalink
feat: implement matomo tracker
Browse files Browse the repository at this point in the history
Co-authored-by: Beatrice Guerra <[email protected]>
Co-authored-by: Rodley Orosa <[email protected]>
Refs: SHELL-198 (#388)
  • Loading branch information
3 people authored Mar 13, 2024
1 parent 9fc0b13 commit 8d0594b
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 1 deletion.
18 changes: 18 additions & 0 deletions src/boot/__mocks__/matomo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MatomoTracker } from '../matomo';

export function initMatomo(): void {
// do nothing
}

export function getMatomoTracker(): MatomoTracker {
return {
trackEvent: () => undefined,
trackPageView: () => undefined,
setCustomUrl: () => undefined
};
}
4 changes: 3 additions & 1 deletion src/boot/app/load-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import SettingsHeader from '../../settings/components/settings-header';
import { useAppStore } from '../../store/app';
import { AppLink } from '../../ui-extras/app-link';
import { Spinner } from '../../ui-extras/spinner';
import { Tracker } from '../tracker';

export const _scripts: { [pkgName: string]: HTMLScriptElement } = {};
let _scriptId = 0;
Expand All @@ -35,7 +36,8 @@ export function loadApp(appPkg: CarbonioModule): Promise<CarbonioModule> {
SettingsHeader,
...getAppSetters(appPkg),
...getAppFunctions(appPkg),
...CONSTANTS
...CONSTANTS,
Tracker
};
}

Expand Down
35 changes: 35 additions & 0 deletions src/boot/matomo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export interface MatomoTracker {
trackPageView(customTitle?: string): void;
trackEvent(category: string, action: string, name?: string, value?: number): void;
setCustomUrl(url: string): void;
}

declare global {
const Matomo: {
getTracker(trackerUrl: string, siteId: number): MatomoTracker;
};
}
export function initMatomo(url: string): void {
const matomoScriptId = 'matomo-script';

if (document.getElementById(matomoScriptId)) {
return;
}

const matomoScript = document.createElement('script');
const scriptElement = document.getElementsByTagName('script')[0];
matomoScript.type = 'text/javascript';
matomoScript.async = true;
matomoScript.src = `${url}matomo.js`;
matomoScript.id = matomoScriptId;
scriptElement.parentNode?.insertBefore(matomoScript, scriptElement);
}

export function getMatomoTracker(url: string, siteId: number): MatomoTracker {
return Matomo.getTracker(`${url}matomo.php`, siteId);
}
75 changes: 75 additions & 0 deletions src/boot/tracker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as matomo from './matomo';
import { Tracker } from './tracker';

beforeEach(() => {
Tracker.enableTracker(false);
});
describe('Tracker', () => {
it('should not call trackPageView when Tracker.isEnabled is false(default)', () => {
const trackPageView = jest.fn();
jest.spyOn(matomo, 'getMatomoTracker').mockReturnValue({
trackEvent: () => undefined,
trackPageView,
setCustomUrl: () => undefined
});
const tracker = new Tracker(1);
tracker.trackPageView('title');
expect(trackPageView).not.toHaveBeenCalled();
});

it('should call trackPageView when Tracker.isEnabled is true', () => {
const trackPageView = jest.fn();
const setCustomUrl = jest.fn();
jest.spyOn(matomo, 'getMatomoTracker').mockReturnValue({
trackEvent: () => undefined,
trackPageView,
setCustomUrl
});
Tracker.enableTracker(true);
const tracker = new Tracker(1);
tracker.trackPageView('title');
expect(trackPageView).toHaveBeenCalled();
expect(setCustomUrl).toHaveBeenCalled();
});

it('should not call trackEvent when Tracker.isEnabled is false(default)', () => {
const trackEvent = jest.fn();
jest.spyOn(matomo, 'getMatomoTracker').mockReturnValue({
trackEvent,
trackPageView: () => undefined,
setCustomUrl: () => undefined
});
const tracker = new Tracker(1);
tracker.trackEvent('category', 'action');
expect(trackEvent).not.toHaveBeenCalled();
});

it('should call trackEvent when Tracker.isEnabled is true', () => {
const trackEvent = jest.fn();
const setCustomUrl = jest.fn();
jest.spyOn(matomo, 'getMatomoTracker').mockReturnValue({
trackEvent,
trackPageView: () => undefined,
setCustomUrl
});
Tracker.enableTracker(true);
const tracker = new Tracker(1);
tracker.trackEvent('category', 'action');
expect(trackEvent).toHaveBeenCalled();
expect(setCustomUrl).toHaveBeenCalled();
});

it('should call init only when tracker is enabled', () => {
const initMatomoSpy = jest.spyOn(matomo, 'initMatomo');
// eslint-disable-next-line @typescript-eslint/no-unused-vars,unused-imports/no-unused-vars
const tracker = new Tracker(1);
expect(initMatomoSpy).not.toHaveBeenCalled();
Tracker.enableTracker(true);
expect(initMatomoSpy).toHaveBeenCalled();
});
});
59 changes: 59 additions & 0 deletions src/boot/tracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { getMatomoTracker, initMatomo, MatomoTracker } from './matomo';

export class Tracker {
private static readonly URL = 'https://analytics.zextras.tools/';

private matomoTracker: MatomoTracker | null;

private static isEnabled = false;

private readonly siteId: number;

constructor(siteId: number) {
this.siteId = siteId;
if (Tracker.isEnabled) {
this.matomoTracker = getMatomoTracker(Tracker.URL, siteId);
} else {
this.matomoTracker = null;
}
}

public static enableTracker(isEnabled: boolean): void {
if (isEnabled) {
initMatomo(Tracker.URL);
}
Tracker.isEnabled = isEnabled;
}

public trackPageView(customTitle?: string): void {
const tracker = this.getMatomoTrackerInstance();
tracker?.setCustomUrl(window.location.href);
tracker?.trackPageView(customTitle);
}

public trackEvent(category: string, action: string, name?: string, value?: number): void {
const tracker = this.getMatomoTrackerInstance();
tracker?.setCustomUrl(window.location.href);
tracker?.trackEvent(category, action, name, value);
}

private getMatomoTrackerInstance(): MatomoTracker | null {
if (!Tracker.isEnabled) {
return null;
}
if (this.matomoTracker === null) {
try {
this.matomoTracker = getMatomoTracker(Tracker.URL, this.siteId);
} catch (e) {
console.warn('Matomo is not initialized yet: ', e);
}
}
return this.matomoTracker;
}
}
2 changes: 2 additions & 0 deletions src/jest-env-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ afterEach(() => {
});
});

// https://jestjs.io/docs/manual-mocks#mocking-user-modules
jest.mock<typeof import('./workers')>('./workers');
jest.mock<typeof import('./reporting/functions')>('./reporting/functions');
jest.mock<typeof import('./reporting/store')>('./reporting/store');
jest.mock<typeof import('./boot/matomo')>('./boot/matomo');
8 changes: 8 additions & 0 deletions types/exports/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,11 @@ declare const reopenBoards: () => void;
declare const setCurrentBoard: (id: string) => void;
declare const useBoardHooks: () => BoardHooksContext;
declare const useBoard: <T>() => Board<T>;

declare class Tracker {
constructor(siteId: number);

public trackPageView(customTitle?: string): void;

public trackEvent(category: string, action: string, name?: string, value?: number): void;
}

0 comments on commit 8d0594b

Please sign in to comment.