diff --git a/charts/pidp/values.yaml b/charts/pidp/values.yaml index 27aa379a0..c99e1669b 100644 --- a/charts/pidp/values.yaml +++ b/charts/pidp/values.yaml @@ -108,9 +108,8 @@ nginx: set $CSP_style "style-src 'self' 'unsafe-inline' *.googleapis.com *.gstatic.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/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; diff --git a/workspace/apps/pidp/src/app/app.component.ts b/workspace/apps/pidp/src/app/app.component.ts index e9d4da428..5183db8c3 100644 --- a/workspace/apps/pidp/src/app/app.component.ts +++ b/workspace/apps/pidp/src/app/app.component.ts @@ -4,6 +4,8 @@ import { ActivatedRoute, Data, Event, + NavigationEnd, + Router, RouterOutlet, Scroll, } from '@angular/router'; @@ -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({ @@ -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()); diff --git a/workspace/apps/pidp/src/app/core/services/snowplow.service.spec.ts b/workspace/apps/pidp/src/app/core/services/snowplow.service.spec.ts new file mode 100644 index 000000000..7ec296fa5 --- /dev/null +++ b/workspace/apps/pidp/src/app/core/services/snowplow.service.spec.ts @@ -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(); + }); +}); diff --git a/workspace/apps/pidp/src/app/core/services/snowplow.service.ts b/workspace/apps/pidp/src/app/core/services/snowplow.service.ts new file mode 100644 index 000000000..2d688fa00 --- /dev/null +++ b/workspace/apps/pidp/src/app/core/services/snowplow.service.ts @@ -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); + } + } +} diff --git a/workspace/apps/pidp/src/app/core/services/window-ref.service.spec.ts b/workspace/apps/pidp/src/app/core/services/window-ref.service.spec.ts new file mode 100644 index 000000000..ba01bcca4 --- /dev/null +++ b/workspace/apps/pidp/src/app/core/services/window-ref.service.spec.ts @@ -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(); + }); +}); diff --git a/workspace/apps/pidp/src/app/core/services/window-ref.service.ts b/workspace/apps/pidp/src/app/core/services/window-ref.service.ts new file mode 100644 index 000000000..327ae81bd --- /dev/null +++ b/workspace/apps/pidp/src/app/core/services/window-ref.service.ts @@ -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(); + } +} diff --git a/workspace/apps/pidp/src/app/features/access/pages/driver-fitness/driver-fitness.page.ts b/workspace/apps/pidp/src/app/features/access/pages/driver-fitness/driver-fitness.page.ts index 8a34529ab..4c449c037 100644 --- a/workspace/apps/pidp/src/app/features/access/pages/driver-fitness/driver-fitness.page.ts +++ b/workspace/apps/pidp/src/app/features/access/pages/driver-fitness/driver-fitness.page.ts @@ -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'; @@ -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'; @@ -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; @@ -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; @@ -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 diff --git a/workspace/apps/pidp/src/app/features/access/pages/hcim-account-transfer/hcim-account-transfer.page.ts b/workspace/apps/pidp/src/app/features/access/pages/hcim-account-transfer/hcim-account-transfer.page.ts index 1415a5930..53019180f 100644 --- a/workspace/apps/pidp/src/app/features/access/pages/hcim-account-transfer/hcim-account-transfer.page.ts +++ b/workspace/apps/pidp/src/app/features/access/pages/hcim-account-transfer/hcim-account-transfer.page.ts @@ -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'; @@ -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'; @@ -84,7 +85,7 @@ import { }) export class HcimAccountTransferPage extends AbstractFormPage - implements OnInit + implements OnInit, AfterViewInit { public title: string; public formState: HcimAccountTransferFormState; @@ -121,6 +122,7 @@ export class HcimAccountTransferPage private resource: HcimAccountTransferResource, private logger: LoggerService, fb: FormBuilder, + private snowplowService: SnowplowService, ) { super(dependenciesService); @@ -153,6 +155,10 @@ export class HcimAccountTransferPage } } + public ngAfterViewInit(): void { + this.snowplowService.refreshLinkClickTracking(); + } + protected performSubmission(): Observable { const partyId = this.partyService.partyId; diff --git a/workspace/apps/pidp/src/app/features/access/pages/immsbc-eforms/immsbc-eforms.page.ts b/workspace/apps/pidp/src/app/features/access/pages/immsbc-eforms/immsbc-eforms.page.ts index 7ac2d12f1..ec2ed7915 100644 --- a/workspace/apps/pidp/src/app/features/access/pages/immsbc-eforms/immsbc-eforms.page.ts +++ b/workspace/apps/pidp/src/app/features/access/pages/immsbc-eforms/immsbc-eforms.page.ts @@ -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'; @@ -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'; @@ -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; @@ -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; @@ -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]); } diff --git a/workspace/apps/pidp/src/app/features/access/pages/prescription-refill-eforms/prescription-refill-eforms.page.ts b/workspace/apps/pidp/src/app/features/access/pages/prescription-refill-eforms/prescription-refill-eforms.page.ts index 6b040c189..b7c2ad66c 100644 --- a/workspace/apps/pidp/src/app/features/access/pages/prescription-refill-eforms/prescription-refill-eforms.page.ts +++ b/workspace/apps/pidp/src/app/features/access/pages/prescription-refill-eforms/prescription-refill-eforms.page.ts @@ -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'; @@ -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'; @@ -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; @@ -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; @@ -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]); } diff --git a/workspace/apps/pidp/src/app/features/access/pages/provincial-attachment-system/provincial-attachment-system.page.ts b/workspace/apps/pidp/src/app/features/access/pages/provincial-attachment-system/provincial-attachment-system.page.ts index fd87596f7..d7e438d9a 100644 --- a/workspace/apps/pidp/src/app/features/access/pages/provincial-attachment-system/provincial-attachment-system.page.ts +++ b/workspace/apps/pidp/src/app/features/access/pages/provincial-attachment-system/provincial-attachment-system.page.ts @@ -12,9 +12,7 @@ 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 { @@ -22,11 +20,13 @@ import { 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'; @@ -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, @@ -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; @@ -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; @@ -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('/'); } diff --git a/workspace/apps/pidp/src/app/features/access/pages/sa-eforms/sa-eforms.page.ts b/workspace/apps/pidp/src/app/features/access/pages/sa-eforms/sa-eforms.page.ts index bc715b715..12e3292a7 100644 --- a/workspace/apps/pidp/src/app/features/access/pages/sa-eforms/sa-eforms.page.ts +++ b/workspace/apps/pidp/src/app/features/access/pages/sa-eforms/sa-eforms.page.ts @@ -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'; @@ -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'; @@ -59,7 +60,7 @@ import { SafePipe, ], }) -export class SaEformsPage implements OnInit { +export class SaEformsPage implements OnInit, AfterViewInit { public title: string; public collectionNotice: string; public completed: boolean | null; @@ -85,6 +86,7 @@ export class SaEformsPage implements OnInit { private resource: SaEformsResource, private logger: LoggerService, documentService: DocumentService, + private snowplowService: SnowplowService, ) { const routeData = this.route.snapshot.data; this.title = routeData.title; @@ -130,6 +132,11 @@ export class SaEformsPage implements OnInit { return this.navigateToRoot(); } } + + public ngAfterViewInit(): void { + this.snowplowService.refreshLinkClickTracking(); + } + private navigateToRoot(): void { this.router.navigate([this.route.snapshot.data.routes.root]); } diff --git a/workspace/apps/pidp/src/app/features/auth/pages/login/login.page.ts b/workspace/apps/pidp/src/app/features/auth/pages/login/login.page.ts index 1f216b36f..1d2a19cfb 100644 --- a/workspace/apps/pidp/src/app/features/auth/pages/login/login.page.ts +++ b/workspace/apps/pidp/src/app/features/auth/pages/login/login.page.ts @@ -4,8 +4,9 @@ import { NgTemplateOutlet, UpperCasePipe, } from '@angular/common'; + +import { AfterViewInit, Component, Inject, OnInit } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Inject, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute } from '@angular/router'; @@ -30,6 +31,7 @@ import { MicrosoftLogLevel, } from '@app/core/services/client-logs.service'; import { DocumentService } from '@app/core/services/document.service'; +import { SnowplowService } from '@app/core/services/snowplow.service'; import { LoggerService } from '@app/core/services/logger.service'; import { AdminRoutes } from '@app/features/admin/admin.routes'; import { BannerComponent } from '@app/shared/components/banner/banner.component'; @@ -64,7 +66,7 @@ export interface LoginPageRouteData { BannerComponent, ], }) -export class LoginPage implements OnInit { +export class LoginPage implements OnInit, AfterViewInit { public viewportOptions = PidpViewport; public bcscMobileSetupUrl: string; @@ -91,6 +93,7 @@ export class LoginPage implements OnInit { private dialog: MatDialog, private documentService: DocumentService, private viewportService: ViewportService, + private snowplowService: SnowplowService, private loginResource: LoginResource, private logger: LoggerService, ) { @@ -135,6 +138,11 @@ export class LoginPage implements OnInit { ); } + public ngAfterViewInit(): void { + // refresh link urls now that we set the links + this.snowplowService.refreshLinkClickTracking(); + } + private onViewportChange(viewport: PidpViewport): void { this.viewport = viewport; diff --git a/workspace/apps/pidp/src/app/features/faq/pages/help/help.page.ts b/workspace/apps/pidp/src/app/features/faq/pages/help/help.page.ts index 7470b8f15..19626ec9c 100644 --- a/workspace/apps/pidp/src/app/features/faq/pages/help/help.page.ts +++ b/workspace/apps/pidp/src/app/features/faq/pages/help/help.page.ts @@ -1,5 +1,12 @@ import { NgIf } from '@angular/common'; -import { Component, HostListener, Inject, OnInit, signal } from '@angular/core'; +import { + AfterViewInit, + Component, + HostListener, + Inject, + OnInit, + signal, +} from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatExpansionModule } from '@angular/material/expansion'; import { Router } from '@angular/router'; @@ -19,6 +26,7 @@ import { } from '@bcgov/shared/ui'; import { APP_CONFIG, AppConfig } from '@app/app.config'; +import { SnowplowService } from '@app/core/services/snowplow.service'; import { UtilsService } from '@app/core/services/utils.service'; import { BreadcrumbComponent } from '@app/shared/components/breadcrumb/breadcrumb.component'; import { Constants } from '@app/shared/constants'; @@ -46,7 +54,7 @@ import { FaqRoutes } from '../../faq.routes'; InjectViewportCssClassDirective, ], }) -export class HelpPage implements OnInit { +export class HelpPage implements OnInit, AfterViewInit { public providerIdentitySupport: string; public readonly panelOpenState = signal(false); public showBackToTopButton: boolean = false; @@ -61,6 +69,7 @@ export class HelpPage implements OnInit { @Inject(APP_CONFIG) private config: AppConfig, private router: Router, private utilsService: UtilsService, + private snowplowService: SnowplowService, ) { this.providerIdentitySupport = this.config.emails.providerIdentitySupport; this.applicationUrl = this.config.applicationUrl; @@ -80,10 +89,6 @@ export class HelpPage implements OnInit { window.scrollTo({ top: 0, behavior: 'smooth' }); } - public ngOnInit(): void { - this.utilsService.scrollTop(); - } - public onClickSendEmail(address: string): void { window.location.assign(address); } @@ -91,4 +96,12 @@ export class HelpPage implements OnInit { public navigateToMfaPage(): void { this.router.navigateByUrl(FaqRoutes.routePath(FaqRoutes.MFA_SETUP)); } + + public ngOnInit(): void { + this.utilsService.scrollTop(); + } + + public ngAfterViewInit(): void { + this.snowplowService.refreshLinkClickTracking(); + } } diff --git a/workspace/apps/pidp/src/app/features/portal/portal.page.ts b/workspace/apps/pidp/src/app/features/portal/portal.page.ts index de2d37eec..06259ce57 100644 --- a/workspace/apps/pidp/src/app/features/portal/portal.page.ts +++ b/workspace/apps/pidp/src/app/features/portal/portal.page.ts @@ -1,6 +1,6 @@ import { STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper'; import { AsyncPipe, NgIf, NgOptimizedImage } from '@angular/common'; -import { Component, HostListener, OnInit } from '@angular/core'; +import { AfterViewInit, Component, HostListener, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Observable, map } from 'rxjs'; @@ -12,6 +12,7 @@ import { faArrowUp, faBookmark } from '@fortawesome/free-solid-svg-icons'; import { InjectViewportCssClassDirective } from '@bcgov/shared/ui'; import { PartyService } from '@app/core/party/party.service'; +import { SnowplowService } from '@app/core/services/snowplow.service'; import { AccessRoutes } from '../access/access.routes'; import { OrganizationInfoRoutes } from '../organization-info/organization-info.routes'; @@ -38,7 +39,7 @@ import { PortalResource } from './portal-resource.service'; AsyncPipe, ], }) -export class PortalPage implements OnInit { +export class PortalPage implements OnInit, AfterViewInit { public faBookmark = faBookmark; public faArrowUp = faArrowUp; public showBackToTopButton: boolean = false; @@ -53,6 +54,7 @@ export class PortalPage implements OnInit { private partyService: PartyService, private resource: PortalResource, private router: Router, + private snowplowService: SnowplowService, ) {} @HostListener('window:scroll', []) @@ -79,4 +81,9 @@ export class PortalPage implements OnInit { .getProfileStatus(this.partyService.partyId) .pipe(map((profileStatus) => profileStatus?.alerts ?? [])); } + + public ngAfterViewInit(): void { + // refresh link urls now that we set the links + this.snowplowService.refreshLinkClickTracking(); + } } diff --git a/workspace/apps/pidp/src/assets/snowplow.js b/workspace/apps/pidp/src/assets/snowplow.js new file mode 100644 index 000000000..28f52b977 --- /dev/null +++ b/workspace/apps/pidp/src/assets/snowplow.js @@ -0,0 +1,22 @@ +// +(function (p, l, o, w, i, n, g) { + if (!p[i]) { + p.GlobalSnowplowNamespace = p.GlobalSnowplowNamespace || []; + p.GlobalSnowplowNamespace.push(i); + p[i] = function () { + (p[i].q = p[i].q || []).push(arguments); + }; + p[i].q = p[i].q || []; + n = l.createElement(o); + g = l.getElementsByTagName(o)[0]; + n.async = 1; + n.src = w; + g.parentNode.insertBefore(n, g); + } +})( + window, + document, + 'script', + 'https://www2.gov.bc.ca/StaticWebResources/static/sp/sp-2-14-0.js', + 'snowplow', +); diff --git a/workspace/apps/pidp/src/index.html b/workspace/apps/pidp/src/index.html index ab3b8a326..76b344ab6 100644 --- a/workspace/apps/pidp/src/index.html +++ b/workspace/apps/pidp/src/index.html @@ -15,6 +15,7 @@ href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet" /> +