-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #192 from bcgov/DEVX1881-CreateEvent
Create SelfDescribingEvent for Wizard usage
- Loading branch information
Showing
2 changed files
with
149 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
253 changes: 145 additions & 108 deletions
253
plugins/analytics-module-snowplow/src/apis/implementations/AnalyticsApi/SnowplowAnalytics.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,127 +1,164 @@ | ||
import { Config } from '@backstage/config'; | ||
import { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api'; | ||
import { | ||
AnalyticsApi, | ||
AnalyticsEvent, | ||
} from '@backstage/core-plugin-api'; | ||
import { newTracker, trackPageView, enableActivityTracking } from '@snowplow/browser-tracker'; | ||
import { LinkClickTrackingPlugin, trackLinkClick } from '@snowplow/browser-plugin-link-click-tracking'; | ||
import { SiteTrackingPlugin, trackSiteSearch } from '@snowplow/browser-plugin-site-tracking'; | ||
newTracker, | ||
trackPageView, | ||
enableActivityTracking, | ||
trackSelfDescribingEvent, | ||
} from '@snowplow/browser-tracker'; | ||
import { | ||
LinkClickTrackingPlugin as linkClickTrackingPlugin, | ||
trackLinkClick, | ||
} from '@snowplow/browser-plugin-link-click-tracking'; | ||
import { | ||
SiteTrackingPlugin as siteTrackingPlugin, | ||
trackSiteSearch, | ||
} from '@snowplow/browser-plugin-site-tracking'; | ||
|
||
export class SnowplowAnalytics implements AnalyticsApi { | ||
private readonly enabled: boolean; | ||
private readonly baseUrl: string; | ||
private readonly debounceTime: number; | ||
private cancelProc: NodeJS.Timeout | null; | ||
private readonly enabled: boolean; | ||
private readonly baseUrl: string; | ||
private readonly debounceTime: number; | ||
private cancelProc: NodeJS.Timeout | null; | ||
|
||
private constructor(options: { | ||
enabled: boolean, | ||
baseUrl: string, | ||
trackerId: string, | ||
endpoint: string, | ||
appId: string, | ||
cookieLifetime: number, | ||
debounceTime: number | ||
}) { | ||
const { | ||
enabled, | ||
baseUrl, | ||
trackerId, | ||
endpoint, | ||
appId, | ||
cookieLifetime, | ||
debounceTime | ||
} = options; | ||
private constructor(options: { | ||
enabled: boolean; | ||
baseUrl: string; | ||
trackerId: string; | ||
endpoint: string; | ||
appId: string; | ||
cookieLifetime: number; | ||
debounceTime: number; | ||
}) { | ||
const { | ||
enabled, | ||
baseUrl, | ||
trackerId, | ||
endpoint, | ||
appId, | ||
cookieLifetime, | ||
debounceTime, | ||
} = options; | ||
|
||
this.enabled = enabled; | ||
this.baseUrl = baseUrl; | ||
this.cancelProc = null; | ||
this.debounceTime = debounceTime; | ||
|
||
// create the Snowplow tracker | ||
if (this.enabled) { | ||
newTracker(trackerId, endpoint, { | ||
appId: appId, | ||
cookieLifetime: cookieLifetime, | ||
platform: "web", | ||
contexts: { | ||
webPage: true | ||
}, | ||
plugins: [LinkClickTrackingPlugin(), SiteTrackingPlugin()] | ||
}); | ||
this.enabled = enabled; | ||
this.baseUrl = baseUrl; | ||
this.cancelProc = null; | ||
this.debounceTime = debounceTime; | ||
|
||
enableActivityTracking({ | ||
minimumVisitLength: 30, | ||
heartbeatDelay: 30 | ||
}); | ||
} | ||
} | ||
|
||
static fromConfig(config: Config): SnowplowAnalytics { | ||
const enabled = config.getBoolean('app.analytics.snowplow.enabled'); | ||
const baseUrl = config.getString('app.baseUrl'); | ||
const endpoint = config.getString('app.analytics.snowplow.collectorUrl'); | ||
const appId = config.getOptionalString('app.analytics.snowplow.appId') || 'Snowplow_standalone_OCIO' ; | ||
const trackerId = config.getOptionalString('app.analytics.snowplow.trackerId') || 'rt'; | ||
const cookieLifetime = config.getOptionalNumber('app.analytics.snowplow.cookieLifetime') ?? 86400 * 548; | ||
const debounceTime = config.getOptionalNumber('app.analytics.snowplow.debounceTime') ?? 3000; | ||
// create the Snowplow tracker | ||
if (this.enabled) { | ||
newTracker(trackerId, endpoint, { | ||
appId: appId, | ||
cookieLifetime: cookieLifetime, | ||
platform: 'web', | ||
contexts: { | ||
webPage: true, | ||
}, | ||
plugins: [linkClickTrackingPlugin(), siteTrackingPlugin()], | ||
}); | ||
|
||
return new SnowplowAnalytics({ | ||
enabled, | ||
baseUrl, | ||
trackerId, | ||
endpoint, | ||
appId, | ||
cookieLifetime, | ||
debounceTime | ||
}); | ||
} | ||
|
||
captureEvent(event: AnalyticsEvent): void { | ||
if (this.enabled) { | ||
switch (event.action) { | ||
case "search": | ||
this.captureSearch(event); | ||
break; | ||
case "navigate": | ||
trackPageView(); | ||
break; | ||
case "click": | ||
case "discover": | ||
this.trackClick(event); | ||
break; | ||
} | ||
} | ||
enableActivityTracking({ | ||
minimumVisitLength: 30, | ||
heartbeatDelay: 30, | ||
}); | ||
} | ||
} | ||
|
||
private trackClick(event: AnalyticsEvent): void { | ||
let to: string = event.attributes?.to as string ?? ''; | ||
const hasDomain = new RegExp(/[A-Za-z0-9-]{1,63}\.[A-Za-z]{2,6}/).test(to); | ||
const isMailto = to.startsWith('mailto:'); | ||
static fromConfig(config: Config): SnowplowAnalytics { | ||
const enabled = config.getBoolean('app.analytics.snowplow.enabled'); | ||
const baseUrl = config.getString('app.baseUrl'); | ||
const endpoint = config.getString('app.analytics.snowplow.collectorUrl'); | ||
const appId = | ||
config.getOptionalString('app.analytics.snowplow.appId') || | ||
'Snowplow_standalone_OCIO'; | ||
const trackerId = | ||
config.getOptionalString('app.analytics.snowplow.trackerId') || 'rt'; | ||
const cookieLifetime = | ||
config.getOptionalNumber('app.analytics.snowplow.cookieLifetime') ?? | ||
86400 * 548; | ||
const debounceTime = | ||
config.getOptionalNumber('app.analytics.snowplow.debounceTime') ?? 3000; | ||
|
||
// add the baseUrl to relative path links (this is largely to remain consistent with previous analytics) | ||
if (!hasDomain && !isMailto) { | ||
to = (to.startsWith('/'))? this.baseUrl + to : this.baseUrl + '/' + to; | ||
} | ||
return new SnowplowAnalytics({ | ||
enabled, | ||
baseUrl, | ||
trackerId, | ||
endpoint, | ||
appId, | ||
cookieLifetime, | ||
debounceTime, | ||
}); | ||
} | ||
|
||
trackLinkClick({ targetUrl: to, elementContent: event.subject }); | ||
captureEvent(event: AnalyticsEvent): void { | ||
if (this.enabled) { | ||
switch (event.action) { | ||
case 'search': | ||
this.captureSearch(event); | ||
break; | ||
case 'navigate': | ||
trackPageView(); | ||
break; | ||
case 'click': | ||
case 'discover': | ||
this.trackClick(event); | ||
break; | ||
case 'create': | ||
this.trackCreate(event); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
} | ||
|
||
private trackSearch(event: AnalyticsEvent): void { | ||
// trim whitespace, split into non-empty terms | ||
trackSiteSearch({ terms: event.subject.trim().split(" ").filter( t => t ) }); | ||
private trackClick(event: AnalyticsEvent): void { | ||
let to: string = (event.attributes?.to as string) ?? ''; | ||
const hasDomain = new RegExp(/[A-Za-z0-9-]{1,63}\.[A-Za-z]{2,6}/).test(to); | ||
const isMailto = to.startsWith('mailto:'); | ||
|
||
// add the baseUrl to relative path links (this is largely to remain consistent with previous analytics) | ||
if (!hasDomain && !isMailto) { | ||
to = to.startsWith('/') ? this.baseUrl + to : `${this.baseUrl}/${to}`; | ||
} | ||
|
||
private captureSearch(event: AnalyticsEvent): void { | ||
// cancel any pending trackSearch call | ||
if (this.cancelProc) { | ||
clearTimeout(this.cancelProc); | ||
} | ||
trackLinkClick({ targetUrl: to, elementContent: event.subject }); | ||
} | ||
|
||
private trackSearch(event: AnalyticsEvent): void { | ||
// trim whitespace, split into non-empty terms | ||
trackSiteSearch({ | ||
terms: event.subject | ||
.trim() | ||
.split(' ') | ||
.filter(t => t), | ||
}); | ||
} | ||
|
||
// track event once the debounceTime has elapsed | ||
this.cancelProc = setTimeout(() => { | ||
this.trackSearch(event); | ||
this.cancelProc = null; | ||
}, this.debounceTime); | ||
private captureSearch(event: AnalyticsEvent): void { | ||
// cancel any pending trackSearch call | ||
if (this.cancelProc) { | ||
clearTimeout(this.cancelProc); | ||
} | ||
|
||
} | ||
// track event once the debounceTime has elapsed | ||
this.cancelProc = setTimeout(() => { | ||
this.trackSearch(event); | ||
this.cancelProc = null; | ||
}, this.debounceTime); | ||
} | ||
|
||
private trackCreate(event: AnalyticsEvent): void { | ||
trackSelfDescribingEvent({ | ||
event: { | ||
schema: 'iglu:ca.bc.gov.devx/action/jsonschema/1-0-0', | ||
data: { | ||
action: 'wizard-complete', | ||
text: event.subject, | ||
ministry: '', | ||
time_saved: event.value ?? 0.0, | ||
}, | ||
}, | ||
}); | ||
} | ||
} |