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

Reduce the number of tutorial progress analytics events #5992

Merged
merged 2 commits into from
Dec 4, 2023
Merged
Changes from 1 commit
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
80 changes: 71 additions & 9 deletions newIDE/app/src/Utils/Analytics/EventSender.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ export const setCurrentlyRunningInAppTutorial = (tutorial: string | null) =>
* This function will retry to send the event if the analytics service is not ready.
*/
const recordEvent = (name: string, metadata?: { [string]: any }) => {
if (isDev) return;
if (isDev) {
// Uncomment to inspect analytics in development.
// console.log(`Should have sent analytics event "${name}"`, metadata);
return;
}

if (!posthogLoaded || !userIdentified) {
console.info(`App analytics not ready for an event - retrying in 2s.`);
Expand Down Expand Up @@ -433,16 +437,74 @@ export const sendEventsExtractedAsFunction = (metadata: {|
recordEvent('events-extracted-as-function', metadata);
};

export const sendInAppTutorialProgress = (metadata: {|
const inAppTutorialProgressLastFiredEvents: {
[string]: {
lastStep: number,
nextCheckTimeoutId: TimeoutID | null,
},
} = {};
const builtInTutorialIds = ['onboarding'];
4ian marked this conversation as resolved.
Show resolved Hide resolved

/**
* Register the progress of a tutorial.
*
* To avoid sending too many events, we only send tutorial progress analytics events
* when some steps are reached (step index == multiple of 5), when the tutorial is completed,
* or after some inactivity (more than 30 seconds).
*/
export const sendInAppTutorialProgress = ({
step,
tutorialId,
isCompleted,
}: {|
tutorialId: string,
step: number,
isCompleted: boolean,
|}) => {
const builtInTutorialIds = ['onboarding'];
recordEvent(
builtInTutorialIds.includes(metadata.tutorialId)
? 'in-app-tutorial-built-in'
: 'in-app-tutorial-external',
metadata
);
const immediatelyRecordEvent = (
spentMoreThan30SecondsSinceLastStep: ?boolean
) => {
// Remember the last step we sent an event for.
inAppTutorialProgressLastFiredEvents[tutorialId] = {
lastStep: step,
nextCheckTimeoutId: null,
};
recordEvent(
builtInTutorialIds.includes(tutorialId)
? 'in-app-tutorial-built-in'
: 'in-app-tutorial-external',
{
tutorialId,
step,
isCompleted,
spentMoreThan30SecondsSinceLastStep: !!spentMoreThan30SecondsSinceLastStep,
}
);
};

// We receive a new progress event, so we can clear the timeout used
// to send the last event in case there is no progress.
const lastFiredEvent = inAppTutorialProgressLastFiredEvents[tutorialId];
if (lastFiredEvent && lastFiredEvent.nextCheckTimeoutId !== null)
clearTimeout(lastFiredEvent.nextCheckTimeoutId);

// Immediately send the event if the tutorial is ended or it's the first progress.
if (isCompleted || !lastFiredEvent) {
immediatelyRecordEvent();
return;
}

// Then, send an event every 5 steps, or if we had more than 5 steps since the last event.
// This last point is important because some steps might be hidden/skipped.
if (step % 5 === 0 || step >= lastFiredEvent.lastStep + 5) {
immediatelyRecordEvent();
return;
}

// Otherwise, continue to remember the last step that was sent, and force to send it 30 seconds
// later if there was no more progress.
inAppTutorialProgressLastFiredEvents[tutorialId] = {
lastStep: lastFiredEvent.lastStep,
nextCheckTimeoutId: setTimeout(() => immediatelyRecordEvent(true), 30000),
};
};