From 241365d099848723ae7c6560f04c7c04dde7e06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Z=C3=A9fling?= Date: Thu, 7 Oct 2021 18:36:30 +0200 Subject: [PATCH] feat: Rename Piwik to Matomo and some updates (#413) --- README.md | 2 +- build.ts | 2 +- src/app/providers/providers.component.ts | 4 +- src/assets/svg/matomo.svg | 11 + src/assets/svg/piwik.svg | 7 - src/lib/core/package.json | 2 +- src/lib/providers/{piwik => matomo}/README.md | 40 +- .../piwik.spec.ts => matomo/matomo.spec.ts} | 82 ++-- src/lib/providers/matomo/matomo.ts | 432 ++++++++++++++++++ .../providers/{piwik => matomo}/package.json | 8 +- src/lib/providers/piwik/piwik.ts | 256 ----------- 11 files changed, 514 insertions(+), 332 deletions(-) create mode 100644 src/assets/svg/matomo.svg delete mode 100644 src/assets/svg/piwik.svg rename src/lib/providers/{piwik => matomo}/README.md (62%) rename src/lib/providers/{piwik/piwik.spec.ts => matomo/matomo.spec.ts} (62%) create mode 100644 src/lib/providers/matomo/matomo.ts rename src/lib/providers/{piwik => matomo}/package.json (71%) delete mode 100644 src/lib/providers/piwik/piwik.ts diff --git a/README.md b/README.md index c1df0f74..96071ff1 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ System.config({ * [Google Global Site Tag](/src/lib/providers/gst) (`gtag.js`) * [Kissmetrics](/src/lib/providers/kissmetrics) * [Mixpanel](/src/lib/providers/mixpanel) -* [Piwik](/src/lib/providers/piwik) +* [Matomo](/src/lib/providers/matomo) * [Segment](/src/lib/providers/segment) * [Baidu Analytics](/src/lib/providers/baidu) * [Facebook Pixel](/src/lib/providers/facebook) diff --git a/build.ts b/build.ts index e4d801a7..bdf5a682 100644 --- a/build.ts +++ b/build.ts @@ -20,7 +20,7 @@ const MODULE_NAMES = [ 'launch', 'mixpanel', 'pyze', - 'piwik', + 'matomo', 'segment', 'intercom', 'woopra', diff --git a/src/app/providers/providers.component.ts b/src/app/providers/providers.component.ts index b645159d..d463261b 100644 --- a/src/app/providers/providers.component.ts +++ b/src/app/providers/providers.component.ts @@ -85,8 +85,8 @@ export class ProvidersComponent { type: 'Analytics', }, { - name: 'piwik', - display: 'Piwik', + name: 'matomo', + display: 'Matomo', type: 'Analytics', }, { diff --git a/src/assets/svg/matomo.svg b/src/assets/svg/matomo.svg new file mode 100644 index 00000000..e8ffe4fb --- /dev/null +++ b/src/assets/svg/matomo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svg/piwik.svg b/src/assets/svg/piwik.svg deleted file mode 100644 index cbaa1586..00000000 --- a/src/assets/svg/piwik.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/lib/core/package.json b/src/lib/core/package.json index b63d598d..5476b2fc 100644 --- a/src/lib/core/package.json +++ b/src/lib/core/package.json @@ -22,7 +22,7 @@ "gtag", "woopra", "splunk", - "piwik", + "matomo", "inspectlet", "hubspot", "interecom", diff --git a/src/lib/providers/piwik/README.md b/src/lib/providers/matomo/README.md similarity index 62% rename from src/lib/providers/piwik/README.md rename to src/lib/providers/matomo/README.md index 41d116e7..d31c80ea 100644 --- a/src/lib/providers/piwik/README.md +++ b/src/lib/providers/matomo/README.md @@ -1,41 +1,41 @@ Piwik logo -# Piwik -__homepage__: [piwik.org](https://piwik.org) -__docs__: [developer.piwik.org](https://developer.piwik.org) -__import__: `import { Angulartics2Piwik } from 'angulartics2/piwik';` +# Matomo +__homepage__: [matomo.org](https://matomo.org) +__docs__: [developer.matomo.org](https://developer.matomo.org) +__import__: `import { Angulartics2Matomo } from 'angulartics2/matomo';` ## Setup -Add the standard piwik track code inside your index.html head tag: +Add the standard matomo track code inside your index.html head tag: ```html - + - + ``` Make sure "trackPageView" line is commented or deleted. It is not needed as page tracking will be trigger by the angular module on route change. -Replace YOUR-DOMAIN with your piwik domain (`//DOMAIN.innocraft.cloud` if you are using the innocraft cloud service). +Replace YOUR-DOMAIN with your matomo domain (`//DOMAIN.innocraft.cloud` if you are using the innocraft cloud service). -Pass the Piwik provider to angulartics in app.module: +Pass the Matomo provider to angulartics in app.module: ```ts import { Angulartics2Module } from 'angulartics2'; -import { Angulartics2Piwik } from 'angulartics2/piwik'; +import { Angulartics2Matomo } from 'angulartics2/matomo'; @NgModule({ imports: [ Angulartics2Module.forRoot(), @@ -44,11 +44,11 @@ import { Angulartics2Piwik } from 'angulartics2/piwik'; Inject angulartics into your root component (usually appComponent) ```ts -import { Angulartics2Piwik } from 'angulartics2/piwik'; +import { Angulartics2Matomo } from 'angulartics2/matomo'; export class AppComponent { - // inject Angulartics2Piwik in root component and initialize it - constructor(private angulartics2Piwik: Angulartics2Piwik) { - angulartics2Piwik.startTracking(); + // inject Angulartics2Matomo in root component and initialize it + constructor(private angulartics2Matomo: Angulartics2Matomo) { + angulartics2Matomo.startTracking(); } } ``` @@ -69,6 +69,6 @@ angulartics2.setUserProperties.next({ ``` Note: Custom Variables and Custom Dimensions cannot be tracked in the same call, and requires separate setUserProperties calls -To track full URLs if there is a hash (#), make sure to enable `settings=>websites=>settings=>page url fragments tracking` in the Piwik dashboard. +To track full URLs if there is a hash (#), make sure to enable `settings=>websites=>settings=>page url fragments tracking` in the Matomo dashboard. Once set up, Angulartics [usage](https://github.com/angulartics/angulartics2#usage) is the same regardless of provider diff --git a/src/lib/providers/piwik/piwik.spec.ts b/src/lib/providers/matomo/matomo.spec.ts similarity index 62% rename from src/lib/providers/piwik/piwik.spec.ts rename to src/lib/providers/matomo/matomo.spec.ts index 6246dfaa..2b3b5a3d 100644 --- a/src/lib/providers/piwik/piwik.spec.ts +++ b/src/lib/providers/matomo/matomo.spec.ts @@ -1,32 +1,34 @@ -import { fakeAsync, inject, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, inject, TestBed } from '@angular/core/testing'; import { Angulartics2 } from 'angulartics2'; + import { advance, createRoot, RootCmp, TestModule } from '../../test.mocks'; -import { Angulartics2Piwik } from './piwik'; + +import { Angulartics2Matomo } from './matomo'; jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; declare var window: any; -describe('Angulartics2Piwik', () => { +describe('Angulartics2Matomo', () => { let _paq: Array; let fixture: ComponentFixture; beforeEach(() => { TestBed.configureTestingModule({ imports: [TestModule], - providers: [Angulartics2Piwik], + providers: [Angulartics2Matomo], }); window._paq = _paq = []; - const provider: Angulartics2Piwik = TestBed.inject(Angulartics2Piwik); + const provider: Angulartics2Matomo = TestBed.inject(Angulartics2Matomo); provider.startTracking(); }); it('should track pages', - fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); - angulartics2.pageTrack.next({path: '/abc' }); + angulartics2.pageTrack.next({ path: '/abc' }); advance(fixture); expect(_paq).toContain(['setCustomUrl', window.location.origin + '/abc']); }, @@ -47,15 +49,15 @@ describe('Angulartics2Piwik', () => { }); it('should track set ecommerce view events', - fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); - // Set up ecommerce view description to inform Piwik that product details are shown + // Set up ecommerce view description to inform Matomo that product details are shown const ecommerceViewDescription = product; ecommerceViewDescription.categoryName = 'category name'; - angulartics2.eventTrack.next({action: 'setEcommerceView', properties: ecommerceViewDescription}); + angulartics2.eventTrack.next({ action: 'setEcommerceView', properties: ecommerceViewDescription }); advance(fixture); expect(_paq).toContain(['setEcommerceView', @@ -70,33 +72,33 @@ describe('Angulartics2Piwik', () => { it('should track goals', - fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); - const piwikGoal = { + const matomoGoal = { goalId: 1, value: 35, }; - angulartics2.eventTrack.next({action: 'trackGoal', properties: piwikGoal}); + angulartics2.eventTrack.next({ action: 'trackGoal', properties: matomoGoal }); advance(fixture); expect(_paq).toContain([ 'trackGoal', - piwikGoal.goalId, - piwikGoal.value, + matomoGoal.goalId, + matomoGoal.value, ]); } )), ); it('should track add ecommerce item events', - fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); - angulartics2.eventTrack.next({action: 'addEcommerceItem', properties: product}); + angulartics2.eventTrack.next({ action: 'addEcommerceItem', properties: product }); advance(fixture); expect(_paq).toContain(['addEcommerceItem', @@ -111,11 +113,11 @@ describe('Angulartics2Piwik', () => { ); it('should track ecommerce cart update events', - fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); - angulartics2.eventTrack.next({action: 'trackEcommerceCartUpdate', properties: {grandTotal: 15.5}}); + angulartics2.eventTrack.next({ action: 'trackEcommerceCartUpdate', properties: { grandTotal: 15.5 } }); advance(fixture); expect(_paq).toContain(['trackEcommerceCartUpdate', 15.5]); @@ -124,8 +126,8 @@ describe('Angulartics2Piwik', () => { ); it('should track ecommerce order events', - fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); const ecommerceOrder = { @@ -137,7 +139,7 @@ describe('Angulartics2Piwik', () => { discount: false, }; - angulartics2.eventTrack.next({action: 'trackEcommerceOrder', properties: ecommerceOrder}); + angulartics2.eventTrack.next({ action: 'trackEcommerceOrder', properties: ecommerceOrder }); advance(fixture); expect(_paq).toContain(['trackEcommerceOrder', @@ -153,8 +155,8 @@ describe('Angulartics2Piwik', () => { ); it('should track search', - fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); const search = { @@ -163,7 +165,7 @@ describe('Angulartics2Piwik', () => { searchCount: 42, }; - angulartics2.eventTrack.next({action: 'trackSiteSearch', properties: search}); + angulartics2.eventTrack.next({ action: 'trackSiteSearch', properties: search }); advance(fixture); expect(_paq).toContain(['trackSiteSearch', @@ -175,10 +177,10 @@ describe('Angulartics2Piwik', () => { )), ); - it('should track events', fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + it('should track events', fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); - angulartics2.eventTrack.next({action: 'do', properties: {category: 'cat'}}); + angulartics2.eventTrack.next({ action: 'do', properties: { category: 'cat' } }); advance(fixture); expect(_paq).toContain(['trackEvent', 'cat', 'do', undefined, undefined]); } @@ -186,8 +188,8 @@ describe('Angulartics2Piwik', () => { }); - it('should set username', fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + it('should set username', fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); angulartics2.setUsername.next('testUser'); advance(fixture); @@ -196,10 +198,10 @@ describe('Angulartics2Piwik', () => { ))); it('should set user properties as custom variable', - fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); - angulartics2.setUserProperties.next({index: 1, name: 'John', value: 123, scope: 'visit'}); + angulartics2.setUserProperties.next({ index: 1, name: 'John', value: 123, scope: 'visit' }); advance(fixture); expect(_paq).toContain(['setCustomVariable', 1, 'John', 123, 'visit']); } @@ -207,8 +209,8 @@ describe('Angulartics2Piwik', () => { ); it('should set user properties as custom dimension', - fakeAsync(inject([Angulartics2, Angulartics2Piwik], - (angulartics2: Angulartics2, angulartics2Piwik: Angulartics2Piwik) => { + fakeAsync(inject([Angulartics2, Angulartics2Matomo], + (angulartics2: Angulartics2, angulartics2Matomo: Angulartics2Matomo) => { fixture = createRoot(RootCmp); angulartics2.setUserProperties.next({ dimension1: 'v1.2.3', diff --git a/src/lib/providers/matomo/matomo.ts b/src/lib/providers/matomo/matomo.ts new file mode 100644 index 00000000..51a2d8c9 --- /dev/null +++ b/src/lib/providers/matomo/matomo.ts @@ -0,0 +1,432 @@ +import { Injectable } from '@angular/core'; + +import { Angulartics2 } from 'angulartics2'; + +declare var _paq: any; + +export type EventTrackAction = 'setEcommerceView' | 'addEcommerceItem' | 'trackEcommerceCartUpdate' + | 'trackEcommerceOrder' | 'trackLink' | 'trackGoal' | 'trackSiteSearch' | string; + +export type ScopeMatomo = 'visit' | 'page'; + + +export interface DimensionsMatomoProperties { + dimension0?: string; + dimension1?: string; + dimension2?: string; + dimension3?: string; + dimension4?: string; + dimension5?: string; + dimension6?: string; + dimension7?: string; + dimension8?: string; + dimension9?: string; +} +export interface SetEcommerceViewMatomoProperties { + /** @class SetEcommerceViewMatomoProperties */ + productSKU: string; + /** @class SetEcommerceViewMatomoProperties */ + productName: string; + /** @class SetEcommerceViewMatomoProperties */ + categoryName: string; + /** @class SetEcommerceViewMatomoProperties */ + price: string; +} + +export interface AddEcommerceItemProperties { + /** @class AddEcommerceItemProperties */ + productSKU: string; + /** @class AddEcommerceItemProperties */ + productName: string; + /** @class AddEcommerceItemProperties */ + productCategory: string; + /** @class AddEcommerceItemProperties */ + price: string; + /** @class AddEcommerceItemProperties */ + quantity: string; +} + +export interface TrackEcommerceCartUpdateMatomoProperties { + /** @class TrackEcommerceCartUpdateMatomoProperties */ + grandTotal: string; +} + +export interface TrackEcommerceOrderMatomoProperties { + /** @class TrackEcommerceOrderMatomoProperties */ + orderId: string; + /** @class TrackEcommerceOrderMatomoProperties */ + grandTotal: string; + /** @class TrackEcommerceOrderMatomoProperties */ + subTotal: string; + /** @class TrackEcommerceOrderMatomoProperties */ + tax: string; + /** @class TrackEcommerceOrderMatomoProperties */ + shipping: string; + /** @class TrackEcommerceOrderMatomoProperties */ + discount: string; +} + +export interface TrackLinkMatomoProperties { + /** @class TrackLinkMatomoProperties */ + url: string; + /** @class TrackLinkMatomoProperties */ + linkType: string; +} + +export interface TrackGoalMatomoProperties { + /** @class TrackGoalMatomoProperties */ + goalId: string; + /** @class TrackGoalMatomoProperties */ + value: string; +} + +export interface TrackSiteSearchMatomoProperties { + /** @class TrackSiteSearchMatomoProperties */ + keyword: string; + /** @class TrackSiteSearchMatomoProperties */ + category: string; + /** @class TrackSiteSearchMatomoProperties */ + searchCount: string; +} + +export interface TrackEventMatomoProperties { + /** @class TrackEventMatomoProperties */ + category: string; + /** @class TrackEventMatomoProperties */ + name?: string; + /** @class TrackEventMatomoProperties */ + label?: string; + /** @class TrackEventMatomoProperties */ + value: number | string; +} + +export interface SetCustomVariableMatomoProperties extends DimensionsMatomoProperties { + /** @class SetCustomVariableMatomoProperties */ + index: number; + /** @class SetCustomVariableMatomoProperties */ + name: string; + /** @class SetCustomVariableMatomoProperties */ + value: string; + /** @class SetCustomVariableMatomoProperties */ + scope: ScopeMatomo; +} + +export interface DeleteCustomVariableMatomoProperties { + /** @class DeleteCustomVariableMatomoProperties */ + index: number; + /** @class DeleteCustomVariableMatomoProperties */ + scope: ScopeMatomo; +} + +export type EventTrackactionProperties = SetEcommerceViewMatomoProperties + | AddEcommerceItemProperties + | TrackEcommerceCartUpdateMatomoProperties + | TrackEcommerceOrderMatomoProperties + | TrackLinkMatomoProperties + | TrackGoalMatomoProperties + | TrackSiteSearchMatomoProperties + | TrackEventMatomoProperties; + +@Injectable({ providedIn: 'root' }) +export class Angulartics2Matomo { + + constructor(private angulartics2: Angulartics2) { + if (typeof (_paq) === 'undefined') { + console.warn('Matomo not found'); + } + this.angulartics2.setUsername + .subscribe((x: string) => this.setUsername(x)); + this.angulartics2.setUserProperties + .subscribe((x: SetCustomVariableMatomoProperties) => this.setUserProperties(x)); + } + + startTracking(): void { + this.angulartics2.pageTrack + .pipe(this.angulartics2.filterDeveloperMode()) + .subscribe((x) => this.pageTrack(x.path)); + this.angulartics2.eventTrack + .pipe(this.angulartics2.filterDeveloperMode()) + .subscribe((x) => this.eventTrack(x.action, x.properties)); + } + + pageTrack(path: string, title?: string) { + try { + if (!window.location.origin) { + (window.location as any).origin = window.location.protocol + '//' + + window.location.hostname + + (window.location.port ? ':' + window.location.port : ''); + } + _paq.push(['setDocumentTitle', title || window.document.title]); + _paq.push(['setCustomUrl', window.location.origin + path]); + _paq.push(['trackPageView']); + } catch (e) { + if (!(e instanceof ReferenceError)) { + throw e; + } + } + } + + resetUser() { + try { + _paq.push(['appendToTrackingUrl', 'new_visit=1']); // (1) forces a new visit + _paq.push(['deleteCookies']); // (2) deletes existing tracking cookies to start the new visit + } catch (e) { + if (!(e instanceof ReferenceError)) { + throw e; + } + } + } + + eventTrack(action: 'setEcommerceView', properties: SetEcommerceViewMatomoProperties): void; + eventTrack(action: 'addEcommerceItem', properties: AddEcommerceItemProperties): void; + eventTrack(action: 'trackEcommerceCartUpdate', properties: TrackEcommerceCartUpdateMatomoProperties): void; + eventTrack(action: 'trackEcommerceOrder', properties: TrackEcommerceOrderMatomoProperties): void; + eventTrack(action: 'trackLink', properties: TrackLinkMatomoProperties): void; + eventTrack(action: 'trackGoal', properties: TrackGoalMatomoProperties): void; + eventTrack(action: 'trackSiteSearch', properties: TrackSiteSearchMatomoProperties): void; + eventTrack(action: string, properties: TrackEventMatomoProperties): void; + + /** + * Track a basic event in Matomo, or send an ecommerce event. + * + * @param action A string corresponding to the type of event that needs to be tracked. + * @param properties The properties that need to be logged with the event. + */ + eventTrack(action: EventTrackAction, properties?: EventTrackactionProperties) { + let params = []; + switch (action) { + /** + * @description Sets the current page view as a product or category page view. When you call + * setEcommerceView it must be followed by a call to trackPageView to record the product or + * category page view. + * + * @link https://matomo.org/docs/ecommerce-analytics/#tracking-product-page-views-category-page-views-optional + * @link https://developer.matomo.org/api-reference/tracking-javascript#ecommerce + * + * @property productSKU (required) SKU: Product unique identifier + * @property productName (optional) Product name + * @property categoryName (optional) Product category, or array of up to 5 categories + * @property price (optional) Product Price as displayed on the page + */ + case 'setEcommerceView': + params = ['setEcommerceView', + (properties as SetEcommerceViewMatomoProperties).productSKU, + (properties as SetEcommerceViewMatomoProperties).productName, + (properties as SetEcommerceViewMatomoProperties).categoryName, + (properties as SetEcommerceViewMatomoProperties).price, + ]; + break; + + /** + * @description Adds a product into the ecommerce order. Must be called for each product in + * the order. + * + * @link https://matomo.org/docs/ecommerce-analytics/#tracking-ecommerce-orders-items-purchased-required + * @link https://developer.matomo.org/api-reference/tracking-javascript#ecommerce + * + * @property productSKU (required) SKU: Product unique identifier + * @property productName (optional) Product name + * @property categoryName (optional) Product category, or array of up to 5 categories + * @property price (recommended) Product price + * @property quantity (optional, default to 1) Product quantity + */ + case 'addEcommerceItem': + params = [ + 'addEcommerceItem', + (properties as AddEcommerceItemProperties).productSKU, + (properties as AddEcommerceItemProperties).productName, + (properties as AddEcommerceItemProperties).productCategory, + (properties as AddEcommerceItemProperties).price, + (properties as AddEcommerceItemProperties).quantity, + ]; + break; + + /** + * @description Tracks a shopping cart. Call this javascript function every time a user is + * adding, updating or deleting a product from the cart. + * + * @link https://matomo.org/docs/ecommerce-analytics/#tracking-add-to-cart-items-added-to-the-cart-optional + * @link https://developer.matomo.org/api-reference/tracking-javascript#ecommerce + * + * @property grandTotal (required) Cart amount + */ + case 'trackEcommerceCartUpdate': + params = ['trackEcommerceCartUpdate', (properties as TrackEcommerceCartUpdateMatomoProperties).grandTotal]; + break; + + /** + * @description Tracks an Ecommerce order, including any ecommerce item previously added to + * the order. orderId and grandTotal (ie. revenue) are required parameters. + * + * @link https://matomo.org/docs/ecommerce-analytics/#tracking-ecommerce-orders-items-purchased-required + * @link https://developer.matomo.org/api-reference/tracking-javascript#ecommerce + * + * @property orderId (required) Unique Order ID + * @property grandTotal (required) Order Revenue grand total (includes tax, shipping, and subtracted discount) + * @property subTotal (optional) Order sub total (excludes shipping) + * @property tax (optional) Tax amount + * @property shipping (optional) Shipping amount + * @property discount (optional) Discount offered (set to false for unspecified parameter) + */ + case 'trackEcommerceOrder': + params = [ + 'trackEcommerceOrder', + (properties as TrackEcommerceOrderMatomoProperties).orderId, + (properties as TrackEcommerceOrderMatomoProperties).grandTotal, + (properties as TrackEcommerceOrderMatomoProperties).subTotal, + (properties as TrackEcommerceOrderMatomoProperties).tax, + (properties as TrackEcommerceOrderMatomoProperties).shipping, + (properties as TrackEcommerceOrderMatomoProperties).discount, + ]; + break; + + /** + * @description To manually trigger an outlink + * + * @link https://matomo.org/docs/tracking-goals-web-analytics/ + * @link https://developer.matomo.org/guides/tracking-javascript-guide#tracking-a-click-as-an-outlink-via-css-or-javascript + * + * @property url (required) link url + * @property linkType (optional) type of link + */ + case 'trackLink': + params = [ + 'trackLink', + (properties as TrackLinkMatomoProperties).url, + (properties as TrackLinkMatomoProperties).linkType + ]; + break; + + /** + * @description Tracks an Ecommerce goal + * + * @link https://matomo.org/docs/tracking-goals-web-analytics/ + * @link https://developer.matomo.org/guides/tracking-javascript-guide#manually-trigger-goal-conversions + * + * @property goalId (required) Unique Goal ID + * @property value (optional) passed to goal tracking + */ + case 'trackGoal': + params = [ + 'trackGoal', + (properties as TrackGoalMatomoProperties).goalId, + (properties as TrackGoalMatomoProperties).value, + ]; + break; + + /** + * @description Tracks a site search + * + * @link https://matomo.org/docs/site-search/ + * @link https://developer.matomo.org/guides/tracking-javascript-guide#internal-search-tracking + * + * @property keyword (required) Keyword searched for + * @property category (optional) Search category + * @property searchCount (optional) Number of results + */ + case 'trackSiteSearch': + params = [ + 'trackSiteSearch', + (properties as TrackSiteSearchMatomoProperties).keyword, + (properties as TrackSiteSearchMatomoProperties).category, + (properties as TrackSiteSearchMatomoProperties).searchCount, + ]; + break; + + /** + * @description Logs an event with an event category (Videos, Music, Games...), an event + * action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...), and an optional + * event name and optional numeric value. + * + * @link https://matomo.org/docs/event-tracking/ + * @link https://developer.matomo.org/api-reference/tracking-javascript#using-the-tracker-object + * + * @property category + * @property action + * @property name (optional, recommended) + * @property value (optional) + */ + default: + // PAQ requires that eventValue be an integer, see: http://matomo.org/docs/event-tracking + if ((properties as TrackEventMatomoProperties).value) { + const parsed = parseInt((properties as TrackEventMatomoProperties).value as any, 10); + (properties as TrackEventMatomoProperties).value = isNaN(parsed) ? 0 : parsed; + } + + params = [ + 'trackEvent', + (properties as TrackEventMatomoProperties).category, + action, + (properties as TrackEventMatomoProperties).name || (properties as TrackEventMatomoProperties).label, // Changed in favour of Matomo documentation. Added fallback so it's backwards compatible. + (properties as TrackEventMatomoProperties).value, + ]; + } + try { + _paq.push(params); + } catch (e) { + if (!(e instanceof ReferenceError)) { + throw e; + } + } + } + + setUsername(userId: string | boolean) { + try { + _paq.push(['setUserId', userId]); + } catch (e) { + if (!(e instanceof ReferenceError)) { + throw e; + } + } + } + + /** + * Sets custom dimensions if at least one property has the key "dimension", + * e.g. dimension10. If there are custom dimensions, any other property is ignored. + * + * If there are no custom dimensions in the given properties object, the properties + * object is saved as a custom variable. + * + * If in doubt, prefer custom dimensions. + * @link https://matomo.org/docs/custom-variables/ + */ + setUserProperties(properties: SetCustomVariableMatomoProperties) { + const dimensions = this.setCustomDimensions(properties); + try { + if (dimensions.length === 0) { + _paq.push(['setCustomVariable', properties.index, properties.name, properties.value, properties.scope]); + } + } catch (e) { + if (!(e instanceof ReferenceError)) { + throw e; + } + } + } + + /** + * If you created a custom variable and then decide to remove this variable from + * a visit or page view, you can use deleteCustomVariable. + * + * @link https://developer.matomo.org/guides/tracking-javascript-guide#deleting-a-custom-variable + */ + deletedUserProperties(properties: DeleteCustomVariableMatomoProperties) { + try { + _paq.push(['deleteCustomVariable', properties.index, properties.scope]); + } catch (e) { + if (!(e instanceof ReferenceError)) { + throw e; + } + } + } + + private setCustomDimensions(properties: SetCustomVariableMatomoProperties): string[] { + const dimensionRegex: RegExp = /dimension[1-9]\d*/; + const dimensions = Object.keys(properties) + .filter(key => dimensionRegex.exec(key)); + dimensions.forEach(dimension => { + const number = Number(dimension.substr(9)); + _paq.push(['setCustomDimension', number, properties[dimension]]); + }); + return dimensions; + } +} diff --git a/src/lib/providers/piwik/package.json b/src/lib/providers/matomo/package.json similarity index 71% rename from src/lib/providers/piwik/package.json rename to src/lib/providers/matomo/package.json index 07c04211..8de8724d 100644 --- a/src/lib/providers/piwik/package.json +++ b/src/lib/providers/matomo/package.json @@ -1,18 +1,18 @@ { "$schema": "../../../../node_modules/ng-packagr/package.schema.json", - "name": "angulartics2/piwik", - "description": "The piwik module", + "name": "angulartics2/matomo", + "description": "The Matomo module", "homepage": "https://angulartics.github.io/angulartics2/", "author": "João Ribeiro (http://github.com/JonnyBGod)", "repository": "angulartics/angulartics2", "license": "MIT", "ngPackage": { "lib": { - "entryFile": "piwik.ts", + "entryFile": "matomo.ts", "umdModuleIds": { "angulartics2": "angulartics2" } }, - "dest": "../../../../dist/packages-dist/piwik" + "dest": "../../../../dist/packages-dist/matomo" } } diff --git a/src/lib/providers/piwik/piwik.ts b/src/lib/providers/piwik/piwik.ts deleted file mode 100644 index 1c97f521..00000000 --- a/src/lib/providers/piwik/piwik.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { Injectable } from '@angular/core'; - -import { Angulartics2 } from 'angulartics2'; - -declare var _paq: any; - -@Injectable({ providedIn: 'root' }) -export class Angulartics2Piwik { - - constructor(private angulartics2: Angulartics2) { - if (typeof (_paq) === 'undefined') { - console.warn('Piwik not found'); - } - this.angulartics2.setUsername - .subscribe((x: string) => this.setUsername(x)); - this.angulartics2.setUserProperties - .subscribe((x) => this.setUserProperties(x)); - } - - startTracking(): void { - this.angulartics2.pageTrack - .pipe(this.angulartics2.filterDeveloperMode()) - .subscribe((x) => this.pageTrack(x.path)); - this.angulartics2.eventTrack - .pipe(this.angulartics2.filterDeveloperMode()) - .subscribe((x) => this.eventTrack(x.action, x.properties)); - } - - pageTrack(path: string, location?: any) { - try { - if (!window.location.origin) { - (window.location as any).origin = window.location.protocol + '//' - + window.location.hostname - + (window.location.port ? ':' + window.location.port : ''); - } - _paq.push(['setDocumentTitle', window.document.title]); - _paq.push(['setCustomUrl', window.location.origin + path]); - _paq.push(['trackPageView']); - } catch (e) { - if (!(e instanceof ReferenceError)) { - throw e; - } - } - } - - /** - * Track a basic event in Piwik, or send an ecommerce event. - * - * @param action A string corresponding to the type of event that needs to be tracked. - * @param properties The properties that need to be logged with the event. - */ - eventTrack(action: string, properties: any = {}) { - let params = []; - switch (action) { - /** - * @description Sets the current page view as a product or category page view. When you call - * setEcommerceView it must be followed by a call to trackPageView to record the product or - * category page view. - * - * @link https://piwik.org/docs/ecommerce-analytics/#tracking-product-page-views-category-page-views-optional - * @link https://developer.piwik.org/api-reference/tracking-javascript#ecommerce - * - * @property productSKU (required) SKU: Product unique identifier - * @property productName (optional) Product name - * @property categoryName (optional) Product category, or array of up to 5 categories - * @property price (optional) Product Price as displayed on the page - */ - case 'setEcommerceView': - params = ['setEcommerceView', - properties.productSKU, - properties.productName, - properties.categoryName, - properties.price, - ]; - break; - - /** - * @description Adds a product into the ecommerce order. Must be called for each product in - * the order. - * - * @link https://piwik.org/docs/ecommerce-analytics/#tracking-ecommerce-orders-items-purchased-required - * @link https://developer.piwik.org/api-reference/tracking-javascript#ecommerce - * - * @property productSKU (required) SKU: Product unique identifier - * @property productName (optional) Product name - * @property categoryName (optional) Product category, or array of up to 5 categories - * @property price (recommended) Product price - * @property quantity (optional, default to 1) Product quantity - */ - case 'addEcommerceItem': - params = [ - 'addEcommerceItem', - properties.productSKU, - properties.productName, - properties.productCategory, - properties.price, - properties.quantity, - ]; - break; - - /** - * @description Tracks a shopping cart. Call this javascript function every time a user is - * adding, updating or deleting a product from the cart. - * - * @link https://piwik.org/docs/ecommerce-analytics/#tracking-add-to-cart-items-added-to-the-cart-optional - * @link https://developer.piwik.org/api-reference/tracking-javascript#ecommerce - * - * @property grandTotal (required) Cart amount - */ - case 'trackEcommerceCartUpdate': - params = ['trackEcommerceCartUpdate', properties.grandTotal]; - break; - - /** - * @description Tracks an Ecommerce order, including any ecommerce item previously added to - * the order. orderId and grandTotal (ie. revenue) are required parameters. - * - * @link https://piwik.org/docs/ecommerce-analytics/#tracking-ecommerce-orders-items-purchased-required - * @link https://developer.piwik.org/api-reference/tracking-javascript#ecommerce - * - * @property orderId (required) Unique Order ID - * @property grandTotal (required) Order Revenue grand total (includes tax, shipping, and subtracted discount) - * @property subTotal (optional) Order sub total (excludes shipping) - * @property tax (optional) Tax amount - * @property shipping (optional) Shipping amount - * @property discount (optional) Discount offered (set to false for unspecified parameter) - */ - case 'trackEcommerceOrder': - params = [ - 'trackEcommerceOrder', - properties.orderId, - properties.grandTotal, - properties.subTotal, - properties.tax, - properties.shipping, - properties.discount, - ]; - break; - - /** - * @description Tracks an Ecommerce goal - * - * @link https://piwik.org/docs/tracking-goals-web-analytics/ - * @link https://developer.piwik.org/guides/tracking-javascript-guide#manually-trigger-goal-conversions - * - * @property goalId (required) Unique Goal ID - * @property value (optional) passed to goal tracking - */ - case 'trackGoal': - params = [ - 'trackGoal', - properties.goalId, - properties.value, - ]; - break; - - /** - * @description Tracks a site search - * - * @link https://piwik.org/docs/site-search/ - * @link https://developer.piwik.org/guides/tracking-javascript-guide#internal-search-tracking - * - * @property keyword (required) Keyword searched for - * @property category (optional) Search category - * @property searchCount (optional) Number of results - */ - case 'trackSiteSearch': - params = [ - 'trackSiteSearch', - properties.keyword, - properties.category, - properties.searchCount, - ]; - break; - - /** - * @description Logs an event with an event category (Videos, Music, Games...), an event - * action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...), and an optional - * event name and optional numeric value. - * - * @link https://piwik.org/docs/event-tracking/ - * @link https://developer.piwik.org/api-reference/tracking-javascript#using-the-tracker-object - * - * @property category - * @property action - * @property name (optional, recommended) - * @property value (optional) - */ - default: - // PAQ requires that eventValue be an integer, see: http://piwik.org/docs/event-tracking - if (properties.value) { - const parsed = parseInt(properties.value, 10); - properties.value = isNaN(parsed) ? 0 : parsed; - } - - params = [ - 'trackEvent', - properties.category, - action, - properties.name || properties.label, // Changed in favour of Piwik documentation. Added fallback so it's backwards compatible. - properties.value, - ]; - } - try { - _paq.push(params); - } catch (e) { - if (!(e instanceof ReferenceError)) { - throw e; - } - } - } - - setUsername(userId: string | boolean) { - try { - _paq.push(['setUserId', userId]); - } catch (e) { - if (!(e instanceof ReferenceError)) { - throw e; - } - } - } - - /** - * Sets custom dimensions if at least one property has the key "dimension", - * e.g. dimension10. If there are custom dimensions, any other property is ignored. - * - * If there are no custom dimensions in the given properties object, the properties - * object is saved as a custom variable. - * - * If in doubt, prefer custom dimensions. - * @link https://piwik.org/docs/custom-variables/ - */ - setUserProperties(properties: any) { - const dimensions = this.setCustomDimensions(properties); - try { - if (dimensions.length === 0) { - _paq.push(['setCustomVariable', properties.index, properties.name, properties.value, properties.scope]); - } - } catch (e) { - if (!(e instanceof ReferenceError)) { - throw e; - } - } - } - - private setCustomDimensions(properties: any): string[] { - const dimensionRegex: RegExp = /dimension[1-9]\d*/; - const dimensions = Object.keys(properties) - .filter(key => dimensionRegex.exec(key)); - dimensions.forEach(dimension => { - const number = Number(dimension.substr(9)); - _paq.push(['setCustomDimension', number, properties[dimension]]); - }); - return dimensions; - } -}