Skip to content

Commit

Permalink
PIDP-898 Add Snowplow web tracker (#519)
Browse files Browse the repository at this point in the history
* add snowplow script

* add snowplow to allowed CSP scripts

* move to scripts

* mv

* add script

* Revert "add script"

This reverts commit 0b9c3e3.

* move to script src

* set CSP_connect

* add script-src-elem directive for the other script

* maybe now

* mv

* fix

* try again

* s

* fix2

* hope it works

* add window ref service

* snowplow collector as a service

* run snowplow

* track link clicking on login page

* track link clicking on help page

* rearrange lifecycle methods to have ngOninit last

* dmft

* hcim

* imms

* prescription

* add custom event tracking method

* track PAS link clicks

* saeforms

* unused
  • Loading branch information
Paahn authored Nov 6, 2024
1 parent 2db0600 commit 0b88fc4
Show file tree
Hide file tree
Showing 17 changed files with 236 additions and 31 deletions.
5 changes: 2 additions & 3 deletions charts/pidp/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,8 @@ nginx:
set $CSP_style "style-src 'self' 'unsafe-inline' *.googleapis.com *.gstatic.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css";
set $CSP_font "font-src 'self' data: *.googleapis.com *.gstatic.com";
set $CSP_frame "frame-ancestors 'self' *.oidc.gov.bc.ca oidc.gov.bc.ca";
set $CSP_SCRIPT "script-src 'self' 'unsafe-inline' https://code.jquery.com/jquery-3.6.0.min.js";
set $CSP "default-src 'self' 'unsafe-inline' *.hlth.gov.bc.ca ; ${CSP_style} ; ${CSP_font} ; ${CSP_SCRIPT} ; ${CSP_frame}";
set $CSP_SCRIPT "script-src 'self' 'unsafe-inline' https://code.jquery.com/jquery-3.6.0.min.js https://www2.gov.bc.ca/StaticWebResources/static/sp/sp-2-14-0.js";
set $CSP "default-src 'self' 'unsafe-inline' *.hlth.gov.bc.ca https://spm.apps.gov.bc.ca ; ${CSP_style} ; ${CSP_font} ; ${CSP_SCRIPT} ; ${CSP_frame}";
add_header Content-Security-Policy $CSP;
add_header X-Frame-Options "ALLOW-FROM dev.oidc.gov.bc.ca oidc.gov.bc.ca" always;
add_header X-XSS-Protection "1; mode=block" always;
Expand Down
13 changes: 12 additions & 1 deletion workspace/apps/pidp/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
ActivatedRoute,
Data,
Event,
NavigationEnd,
Router,
RouterOutlet,
Scroll,
} from '@angular/router';
Expand All @@ -13,6 +15,7 @@ import { Observable, delay, map, mergeMap } from 'rxjs';
import { contentContainerSelector } from '@bcgov/shared/ui';

import { RouteStateService } from '@core/services/route-state.service';
import { SnowplowService } from '@core/services/snowplow.service';
import { UtilsService } from '@core/services/utils.service';

@Component({
Expand All @@ -28,7 +31,15 @@ export class AppComponent implements OnInit {
private titleService: Title,
private routeStateService: RouteStateService,
private utilsService: UtilsService,
) {}
private router: Router,
private snowplowService: SnowplowService,
) {
this.router.events.subscribe((evt) => {
if (evt instanceof NavigationEnd) {
this.snowplowService.trackPageView();
}
});
}

public ngOnInit(): void {
this.setPageTitle(this.routeStateService.onNavigationEnd());
Expand Down
16 changes: 16 additions & 0 deletions workspace/apps/pidp/src/app/core/services/snowplow.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { SnowplowService } from './snowplow.service';

describe('SnowplowService', () => {
let service: SnowplowService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SnowplowService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
53 changes: 53 additions & 0 deletions workspace/apps/pidp/src/app/core/services/snowplow.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Injectable } from '@angular/core';

import {
ISnowplowWindow,
WindowRefService,
} from '@core/services/window-ref.service';

@Injectable({
providedIn: 'root',
})
export class SnowplowService {
private _window: ISnowplowWindow;

public constructor(window: WindowRefService) {
this._window = window.nativeWindow;
if (this._window.snowplow) {
const collector = 'spm.apps.gov.bc.ca';
this._window.snowplow('newTracker', 'rt', collector, {
appId: 'Snowplow_standalone',
cookieLifetime: 86400 * 548,
platform: 'web',
post: true,
forceSecureTracker: true,
contexts: {
webPage: true,
performanceTiming: true,
},
});
this._window.snowplow('enableActivityTracking', 30, 30); // Ping every 30 seconds after 30 seconds
this._window.snowplow('enableLinkClickTracking');
}
}

public trackPageView(): void {
if (this._window.snowplow) {
this._window.snowplow('trackPageView');
}
}

// Add Snowplow click listeners to all links which do not already have them
public refreshLinkClickTracking(): void {
if (this._window.snowplow) {
this._window.snowplow('refreshLinkClickTracking');
}
}

// Add Snowplow click listeners to a custom click event
public trackLinkClick(url: string): void {
if (this._window.snowplow) {
this._window.snowplow('trackLinkClick', url);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { WindowRefService } from './window-ref.service';

describe('WindowRefService', () => {
let service: WindowRefService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(WindowRefService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
23 changes: 23 additions & 0 deletions workspace/apps/pidp/src/app/core/services/window-ref.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Injectable } from '@angular/core';

export interface ISnowplowWindow extends Window {
snowplow: (...args: any) => void;
}

function getWindow(): any {
return window;
}

@Injectable({
providedIn: 'root',
})
export class WindowRefService {
/**
* @description
* Get a reference to the native window object.
*/
public get nativeWindow(): ISnowplowWindow {
return getWindow();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NgIf } from '@angular/common';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { AfterViewInit, Component, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';

Expand All @@ -19,6 +19,7 @@ import {

import { PartyService } from '@app/core/party/party.service';
import { LoggerService } from '@app/core/services/logger.service';
import { SnowplowService } from '@app/core/services/snowplow.service';
import { StatusCode } from '@app/features/portal/enums/status-code.enum';
import { BreadcrumbComponent } from '@app/shared/components/breadcrumb/breadcrumb.component';

Expand Down Expand Up @@ -46,7 +47,7 @@ import {
RouterLink,
],
})
export class DriverFitnessPage implements OnInit {
export class DriverFitnessPage implements OnInit, AfterViewInit {
public completed: boolean | null;
public accessRequestFailed: boolean;
public driverFitnessSupportEmail: string;
Expand All @@ -69,6 +70,7 @@ export class DriverFitnessPage implements OnInit {
private partyService: PartyService,
private resource: DriverFitnessResource,
private logger: LoggerService,
private snowplowService: SnowplowService,
) {
const routeData = this.route.snapshot.data;
this.completed = routeData.driverFitnessStatusCode === StatusCode.COMPLETED;
Expand All @@ -92,6 +94,10 @@ export class DriverFitnessPage implements OnInit {
}
}

public ngAfterViewInit(): void {
this.snowplowService.refreshLinkClickTracking();
}

public onRequestAccess(): void {
this.loadingOverlayService.open(LOADING_OVERLAY_DEFAULT_MESSAGE);
this.resource
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { AfterViewInit, Component, OnInit } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
Expand Down Expand Up @@ -33,6 +33,7 @@ import {
} from '@app/core/classes/abstract-form-page.class';
import { PartyService } from '@app/core/party/party.service';
import { LoggerService } from '@app/core/services/logger.service';
import { SnowplowService } from '@app/core/services/snowplow.service';
import { StatusCode } from '@app/features/portal/enums/status-code.enum';
import { BreadcrumbComponent } from '@app/shared/components/breadcrumb/breadcrumb.component';

Expand Down Expand Up @@ -84,7 +85,7 @@ import {
})
export class HcimAccountTransferPage
extends AbstractFormPage<HcimAccountTransferFormState>
implements OnInit
implements OnInit, AfterViewInit
{
public title: string;
public formState: HcimAccountTransferFormState;
Expand Down Expand Up @@ -121,6 +122,7 @@ export class HcimAccountTransferPage
private resource: HcimAccountTransferResource,
private logger: LoggerService,
fb: FormBuilder,
private snowplowService: SnowplowService,
) {
super(dependenciesService);

Expand Down Expand Up @@ -153,6 +155,10 @@ export class HcimAccountTransferPage
}
}

public ngAfterViewInit(): void {
this.snowplowService.refreshLinkClickTracking();
}

protected performSubmission(): Observable<HcimAccountTransferResponse> {
const partyId = this.partyService.partyId;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NgIf } from '@angular/common';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { AfterViewInit, Component, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { ActivatedRoute, Router } from '@angular/router';

Expand All @@ -24,6 +24,7 @@ import {
import { PartyService } from '@app/core/party/party.service';
import { DocumentService } from '@app/core/services/document.service';
import { LoggerService } from '@app/core/services/logger.service';
import { SnowplowService } from '@app/core/services/snowplow.service';
import { StatusCode } from '@app/features/portal/enums/status-code.enum';
import { BreadcrumbComponent } from '@app/shared/components/breadcrumb/breadcrumb.component';

Expand Down Expand Up @@ -59,7 +60,7 @@ import {
SafePipe,
],
})
export class ImmsBCEformsPage implements OnInit {
export class ImmsBCEformsPage implements OnInit, AfterViewInit {
public title: string;
public collectionNotice: string;
public completed: boolean | null;
Expand All @@ -84,6 +85,7 @@ export class ImmsBCEformsPage implements OnInit {
private resource: ImmsBCEformsResource,
private logger: LoggerService,
documentService: DocumentService,
private snowplowService: SnowplowService,
) {
const routeData = this.route.snapshot.data;
this.title = routeData.title;
Expand Down Expand Up @@ -130,6 +132,10 @@ export class ImmsBCEformsPage implements OnInit {
}
}

public ngAfterViewInit(): void {
this.snowplowService.refreshLinkClickTracking();
}

private navigateToRoot(): void {
this.router.navigate([this.route.snapshot.data.routes.root]);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NgIf } from '@angular/common';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { AfterViewInit, Component, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { ActivatedRoute, Router } from '@angular/router';

Expand All @@ -24,6 +24,7 @@ import {
import { PartyService } from '@app/core/party/party.service';
import { DocumentService } from '@app/core/services/document.service';
import { LoggerService } from '@app/core/services/logger.service';
import { SnowplowService } from '@app/core/services/snowplow.service';
import { StatusCode } from '@app/features/portal/enums/status-code.enum';
import { BreadcrumbComponent } from '@app/shared/components/breadcrumb/breadcrumb.component';

Expand Down Expand Up @@ -59,7 +60,7 @@ import {
SafePipe,
],
})
export class PrescriptionRefillEformsPage implements OnInit {
export class PrescriptionRefillEformsPage implements OnInit, AfterViewInit {
public title: string;
public collectionNotice: string;
public completed: boolean | null;
Expand All @@ -84,6 +85,7 @@ export class PrescriptionRefillEformsPage implements OnInit {
private resource: PrescriptionRefillEformsResource,
private logger: LoggerService,
private documentService: DocumentService,
private snowplowService: SnowplowService,
) {
const routeData = this.route.snapshot.data;
this.title = routeData.title;
Expand Down Expand Up @@ -133,6 +135,10 @@ export class PrescriptionRefillEformsPage implements OnInit {
}
}

public ngAfterViewInit(): void {
this.snowplowService.refreshLinkClickTracking();
}

private navigateToRoot(): void {
this.router.navigate([this.route.snapshot.data.routes.root]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ import { BehaviorSubject, Observable, of, switchMap, tap } from 'rxjs';

import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';

import {
InjectViewportCssClassDirective,
} from '@bcgov/shared/ui';
import { InjectViewportCssClassDirective } from '@bcgov/shared/ui';

import { APP_CONFIG, AppConfig } from '@app/app.config';
import {
Destination,
DiscoveryResource,
} from '@app/core/party/discovery-resource.service';
import { PartyService } from '@app/core/party/party.service';
import { SnowplowService } from '@app/core/services/snowplow.service';
import { ToastService } from '@app/core/services/toast.service';
import { AuthService } from '@app/features/auth/services/auth.service';
import { StatusCode } from '@app/features/portal/enums/status-code.enum';
import { ProfileStatus } from '@app/features/portal/models/profile-status.model';
import { PortalResource } from '@app/features/portal/portal-resource.service';
import { BreadcrumbComponent } from '@app/shared/components/breadcrumb/breadcrumb.component';

import { AccessRoutes } from '../../access.routes';
import { BcProviderEditResource } from '../bc-provider-edit/bc-provider-edit-resource.service';
Expand All @@ -35,7 +35,7 @@ import {
bcProviderTutorialLink,
provincialAttachmentSystemWebsite,
} from './provincial-attachment-system.constants';
import { BreadcrumbComponent } from '@app/shared/components/breadcrumb/breadcrumb.component';

@Component({
selector: 'app-provincial-attachment-system',
standalone: true,
Expand Down Expand Up @@ -80,9 +80,12 @@ export class ProvincialAttachmentSystemPage implements OnInit {
public StatusCode = StatusCode;
public AccessRoutes = AccessRoutes;
public breadcrumbsData: Array<{ title: string; path: string }> = [
{title: 'Home', path: ''},
{title: 'Access', path: AccessRoutes.routePath(AccessRoutes.ACCESS_REQUESTS)},
{title: 'PAS', path: ''},
{ title: 'Home', path: '' },
{
title: 'Access',
path: AccessRoutes.routePath(AccessRoutes.ACCESS_REQUESTS),
},
{ title: 'PAS', path: '' },
];
private readonly provincialAttachmentSystemWebsite: string;

Expand All @@ -95,6 +98,7 @@ export class ProvincialAttachmentSystemPage implements OnInit {
private partyService: PartyService,
private router: Router,
private toastService: ToastService,
private snowplowService: SnowplowService,
) {
this.selectedIndex = -1;
this.provincialAttachmentSystemWebsite = provincialAttachmentSystemWebsite;
Expand Down Expand Up @@ -171,6 +175,7 @@ export class ProvincialAttachmentSystemPage implements OnInit {
}

private navigateToExternalUrl(url: string): void {
this.snowplowService.trackLinkClick(this.provincialAttachmentSystemWebsite);
window.open(url, '_blank');
this.router.navigateByUrl('/');
}
Expand Down
Loading

0 comments on commit 0b88fc4

Please sign in to comment.