Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: change user engagement event and engagement time calculate rule #13

Merged
merged 2 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ ClickstreamAnalytics.configure({
sendMode: EventMode.Batch,
sendEventsInterval: 5000,
isTrackPageViewEvents: true,
isTrackUserEngagementEvents: true,
isTrackClickEvents: true,
isTrackSearchEvents: true,
isTrackScrollEvents: true,
Expand All @@ -132,6 +133,7 @@ Here is an explanation of each property:
- **sendMode**: EventMode.Immediate, EventMode.Batch, default is Immediate mode.
- **sendEventsInterval**: event sending interval millisecond, works only bath send mode, the default value is `5000`
- **isTrackPageViewEvents**: whether auto record page view events in browser, default is `true`
- **isTrackUserEngagementEvents**: whether auto record user engagement events in browser, default is `true`
- **isTrackClickEvents**: whether auto record link click events in browser, default is `true`
- **isTrackSearchEvents**: whether auto record search result page events in browser, default is `true`
- **isTrackScrollEvents**: whether auto record page scroll events in browser, default is `true`
Expand All @@ -152,6 +154,7 @@ ClickstreamAnalytics.updateConfigure({
isLogEvents: true,
authCookie: 'your auth cookie',
isTrackPageViewEvents: false,
isTrackUserEngagementEvents: false,
isTrackClickEvents: false,
isTrackScrollEvents: false,
isTrackSearchEvents: false,
Expand Down
12 changes: 3 additions & 9 deletions src/provider/AnalyticsEventBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,17 @@ import {
Item,
UserAttribute,
} from '../types';
import { HashUtil } from '../util/HashUtil';
import { StorageUtil } from '../util/StorageUtil';

const sdkVersion = config.sdkVersion;

export class AnalyticsEventBuilder {
static async createEvent(
static createEvent(
context: ClickstreamContext,
event: ClickstreamEvent,
userAttributes: UserAttribute,
session?: Session
): Promise<AnalyticsEvent> {
): AnalyticsEvent {
const { browserInfo, configuration } = context;
const attributes = this.getEventAttributesWithCheck(event.attributes);
if (session !== undefined) {
Expand All @@ -56,8 +55,7 @@ export class AnalyticsEventBuilder {
browserInfo.latestReferrerHost;

const items = this.getEventItemsWithCheck(event.items, attributes);

const analyticEvent = {
return {
hashCode: '',
event_type: event.name,
event_id: uuidV4(),
Expand All @@ -80,10 +78,6 @@ export class AnalyticsEventBuilder {
user: userAttributes ?? {},
attributes: attributes,
};
analyticEvent.hashCode = await HashUtil.getHashCode(
JSON.stringify(analyticEvent)
);
return analyticEvent;
}

static getEventAttributesWithCheck(
Expand Down
23 changes: 18 additions & 5 deletions src/provider/ClickstreamProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { PageViewTracker, SessionTracker } from '../tracker';
import { ClickTracker } from '../tracker/ClickTracker';
import { ScrollTracker } from '../tracker/ScrollTracker';
import {
AnalyticsEvent,
AnalyticsProvider,
ClickstreamAttribute,
ClickstreamConfiguration,
Expand All @@ -31,6 +32,7 @@ import {
SendMode,
UserAttribute,
} from '../types';
import { HashUtil } from '../util/HashUtil';
import { StorageUtil } from '../util/StorageUtil';

const logger = new Logger('ClickstreamProvider');
Expand All @@ -52,6 +54,7 @@ export class ClickstreamProvider implements AnalyticsProvider {
sendMode: SendMode.Immediate,
sendEventsInterval: 5000,
isTrackPageViewEvents: true,
isTrackUserEngagementEvents: true,
isTrackClickEvents: true,
isTrackSearchEvents: true,
isTrackScrollEvents: true,
Expand Down Expand Up @@ -118,17 +121,27 @@ export class ClickstreamProvider implements AnalyticsProvider {
logger.error(result.error_message);
return;
}
AnalyticsEventBuilder.createEvent(
const resultEvent = this.createEvent(event);
this.recordEvent(resultEvent, event.isImmediate);
}

createEvent(event: ClickstreamEvent) {
return AnalyticsEventBuilder.createEvent(
this.context,
event,
this.userAttribute,
this.sessionTracker.session
)
.then(resultEvent => {
this.eventRecorder.record(resultEvent, event.isImmediate);
);
}

recordEvent(event: AnalyticsEvent, isImmediate = false) {
HashUtil.getHashCode(JSON.stringify(event))
.then(hashCode => {
event.hashCode = hashCode;
this.eventRecorder.record(event, isImmediate);
})
.catch(error => {
logger.error(`Create event fail with ${error}`);
logger.error(`Create hash code failed with ${error}`);
});
}

Expand Down
1 change: 1 addition & 0 deletions src/provider/Event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class Event {
PAGE_REFERRER_TITLE: '_page_referrer_title',
LATEST_REFERRER: '_latest_referrer',
LATEST_REFERRER_HOST: '_latest_referrer_host',
PREVIOUS_TIMESTAMP: '_previous_timestamp',
ENTRANCES: '_entrances',
SESSION_ID: '_session_id',
SESSION_DURATION: '_session_duration',
Expand Down
114 changes: 80 additions & 34 deletions src/tracker/PageViewTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,28 @@ export class PageViewTracker extends BaseTracker {
context: ClickstreamContext;
isEntrances = false;
searchKeywords = Event.Constants.KEYWORDS;
lastEngageTime = 0;
lastScreenStartTimestamp = 0;

init() {
const configuredSearchKeywords = this.provider.configuration.searchKeyWords;
Object.assign(this.searchKeywords, configuredSearchKeywords);
this.trackPageView = this.trackPageView.bind(this);
this.onPageChange = this.onPageChange.bind(this);
if (this.context.configuration.pageType === PageType.SPA) {
this.trackPageViewForSPA();
} else {
this.trackPageView();
this.onPageChange();
}
}

trackPageViewForSPA() {
MethodEmbed.add(history, 'pushState', this.trackPageView);
MethodEmbed.add(history, 'replaceState', this.trackPageView);
window.addEventListener('popstate', this.trackPageView);
this.trackPageView();
MethodEmbed.add(history, 'pushState', this.onPageChange);
MethodEmbed.add(history, 'replaceState', this.onPageChange);
window.addEventListener('popstate', this.onPageChange);
this.onPageChange();
}

trackPageView() {
onPageChange() {
if (!window.sessionStorage) {
logger.warn('unsupported web environment for sessionStorage');
return;
Expand All @@ -55,41 +57,81 @@ export class PageViewTracker extends BaseTracker {
const previousPageTitle = StorageUtil.getPreviousPageTitle();
const currentPageUrl = BrowserInfo.getCurrentPageUrl();
const currentPageTitle = BrowserInfo.getCurrentPageTitle();
const previousPageStartTime = StorageUtil.getPreviousPageStartTime();
let engagementTime = 0;
this.isEntrances =
this.provider.sessionTracker.session.isNewSession() &&
previousPageUrl === '';
const currentPageStartTime = new Date().getTime();
if (previousPageStartTime > 0) {
engagementTime = currentPageStartTime - previousPageStartTime;
}
if (previousPageUrl !== currentPageUrl) {
if (
previousPageUrl !== currentPageUrl ||
previousPageTitle !== currentPageTitle
) {
this.provider.scrollTracker?.enterNewPage();
const eventAttributes = {
[Event.ReservedAttribute.PAGE_REFERRER]: previousPageUrl,
[Event.ReservedAttribute.PAGE_REFERRER_TITLE]: previousPageTitle,
[Event.ReservedAttribute.ENTRANCES]: this.isEntrances ? 1 : 0,
};
if (!this.isEntrances) {
eventAttributes[Event.ReservedAttribute.ENGAGEMENT_TIMESTAMP] =
engagementTime;
}
this.provider.record({
name: Event.PresetEvent.PAGE_VIEW,
attributes: eventAttributes,
});
this.recordUserEngagement();
this.trackPageView(previousPageUrl, previousPageTitle);
this.trackSearchEvents();

StorageUtil.savePreviousPageUrl(currentPageUrl);
StorageUtil.savePreviousPageTitle(currentPageTitle);
StorageUtil.savePreviousPageStartTime(currentPageStartTime);
if (this.context.configuration.isTrackSearchEvents) {
this.trackSearchEvents();
}
}
}
}

trackPageView(previousPageUrl: string, previousPageTitle: string) {
const previousPageStartTime = StorageUtil.getPreviousPageStartTime();
const analyticsEvent = this.provider.createEvent({
name: Event.PresetEvent.PAGE_VIEW,
});
const currentPageStartTime = analyticsEvent.timestamp;

const eventAttributes = {
[Event.ReservedAttribute.PAGE_REFERRER]: previousPageUrl,
[Event.ReservedAttribute.PAGE_REFERRER_TITLE]: previousPageTitle,
[Event.ReservedAttribute.ENTRANCES]: this.isEntrances ? 1 : 0,
};
if (previousPageStartTime > 0) {
eventAttributes[Event.ReservedAttribute.PREVIOUS_TIMESTAMP] =
previousPageStartTime;
}
if (this.lastEngageTime > 0) {
eventAttributes[Event.ReservedAttribute.ENGAGEMENT_TIMESTAMP] =
this.lastEngageTime;
}
Object.assign(analyticsEvent.attributes, eventAttributes);
this.provider.recordEvent(analyticsEvent);

this.isEntrances = false;

StorageUtil.savePreviousPageStartTime(currentPageStartTime);
this.lastScreenStartTimestamp = currentPageStartTime;
}

setIsEntrances() {
this.isEntrances = true;
}

updateLastScreenStartTimestamp() {
this.lastScreenStartTimestamp = new Date().getTime();
}

recordUserEngagement(isImmediate = false) {
if (this.lastScreenStartTimestamp === 0) return;
this.lastEngageTime = this.getLastEngageTime();
if (
this.provider.configuration.isTrackUserEngagementEvents &&
this.lastEngageTime > Constants.minEngagementTime
) {
this.provider.record({
name: Event.PresetEvent.USER_ENGAGEMENT,
attributes: {
[Event.ReservedAttribute.ENGAGEMENT_TIMESTAMP]: this.lastEngageTime,
},
isImmediate: isImmediate,
});
}
}

getLastEngageTime() {
return new Date().getTime() - this.lastScreenStartTimestamp;
}

trackSearchEvents() {
if (!this.context.configuration.isTrackSearchEvents) return;
const searchStr = window.location.search;
if (!searchStr || searchStr.length === 0) return;
const urlParams = new URLSearchParams(searchStr);
Expand All @@ -108,3 +150,7 @@ export class PageViewTracker extends BaseTracker {
}
}
}

enum Constants {
minEngagementTime = 1000,
}
24 changes: 4 additions & 20 deletions src/tracker/SessionTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export class SessionTracker extends BaseTracker {
hiddenStr: string;
visibilityChange: string;
session: Session;
startEngageTimestamp: number;
isWindowClosing = false;

init() {
Expand Down Expand Up @@ -63,9 +62,11 @@ export class SessionTracker extends BaseTracker {

onPageAppear(isFirstTime = false) {
logger.debug('page appear');
this.updateEngageTimestamp();
const pageViewTracker = this.provider.pageViewTracker;
pageViewTracker.updateLastScreenStartTimestamp();
this.session = Session.getCurrentSession(this.context);
if (this.session.isNewSession()) {
pageViewTracker.setIsEntrances();
this.provider.record({ name: Event.PresetEvent.SESSION_START });
}
this.provider.record({
Expand All @@ -86,16 +87,7 @@ export class SessionTracker extends BaseTracker {
}

recordUserEngagement(isImmediate = false) {
const engagementTime = new Date().getTime() - this.startEngageTimestamp;
if (engagementTime > Constants.minEngagementTime) {
this.provider.record({
name: Event.PresetEvent.USER_ENGAGEMENT,
attributes: {
[Event.ReservedAttribute.ENGAGEMENT_TIMESTAMP]: engagementTime,
},
isImmediate: isImmediate,
});
}
this.provider.pageViewTracker.recordUserEngagement(isImmediate);
}

onBeforeUnload() {
Expand All @@ -108,10 +100,6 @@ export class SessionTracker extends BaseTracker {
StorageUtil.saveSession(this.session);
}

updateEngageTimestamp() {
this.startEngageTimestamp = new Date().getTime();
}

checkEnv(): boolean {
if (!document || !document.addEventListener) {
logger.debug('not in the supported web environment');
Expand All @@ -133,7 +121,3 @@ export class SessionTracker extends BaseTracker {
return true;
}
}

enum Constants {
minEngagementTime = 1000,
}
1 change: 1 addition & 0 deletions src/types/Analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface Configuration {
isLogEvents?: boolean;
authCookie?: string;
isTrackPageViewEvents?: boolean;
isTrackUserEngagementEvents?: boolean;
isTrackClickEvents?: boolean;
isTrackScrollEvents?: boolean;
isTrackSearchEvents?: boolean;
Expand Down
4 changes: 2 additions & 2 deletions src/util/StorageUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export class StorageUtil {
}

static getPreviousPageStartTime(): number {
const startTime = sessionStorage.getItem(
const startTime = localStorage.getItem(
StorageUtil.previousPageStartTimeKey
);
if (startTime === null) {
Expand All @@ -270,7 +270,7 @@ export class StorageUtil {
}

static savePreviousPageStartTime(timestamp: number) {
sessionStorage.setItem(
localStorage.setItem(
StorageUtil.previousPageStartTimeKey,
timestamp.toString()
);
Expand Down
Loading
Loading