Merge pull request #8830 from MiroslavDionisiev/UEPR-51

[UERP-51] Google Analytics events for onboarding journeys
This commit is contained in:
Miroslav Dionisiev 2024-10-23 11:03:53 +03:00 committed by GitHub
commit 7c58c33d12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 119 additions and 11 deletions

View file

@ -6,6 +6,7 @@ const DriverJourney = require('../driver-journey/driver-journey.jsx');
const {defineMessages, useIntl} = require('react-intl');
const {useMemo, useState, useCallback} = require('react');
const PropTypes = require('prop-types');
const {triggerAnalyticsEvent} = require('../../../lib/onboarding.js');
require('./editor-journey.scss');
@ -92,8 +93,21 @@ const messages = defineMessages({
}
});
const STEP_NAMES = [
'pick-genre-step',
'game-step',
'animation-step',
'music-step',
'clicker-game-step',
'pong-game-step',
'animate-character-step',
'make-fly-animation-step',
'record-sound-step',
'make-music-step'
];
const projectIds = {
clicker: '10000252',
clicker: '10128368',
pong: '10128515',
animateCharacter: '10128067',
makeItFly: '114019829',
@ -138,6 +152,14 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
));
const intl = useIntl();
const pickStep = useCallback((stepNumber, editorJourneyStep) => {
triggerAnalyticsEvent({
event: 'editor-journey-step',
editorJourneyStep: editorJourneyStep
});
driverObj.moveTo(stepNumber);
}, driverObj);
const createStep = useCallback((projectId, tutorialId) => ({
title: intl.formatMessage(messages.createStepTitle),
showButtons: ['close'],
@ -149,6 +171,10 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
imgSrc: '/images/onboarding-journeys/Tutorials-Icon.svg',
text: intl.formatMessage(messages.tutorialButtonText),
handleOnClick: () => {
triggerAnalyticsEvent({
event: 'editor-journey-step',
editorJourneyStep: `${tutorialId}-Open-Tutorial`
});
onActivateDeck(tutorialId);
setShowJourney(false);
driverObj.destroy();
@ -167,6 +193,10 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
imgSrc: '/images/onboarding-journeys/On-Own-Icon.svg',
text: intl.formatMessage(messages.onMyOwnButtonText),
handleOnClick: () => {
triggerAnalyticsEvent({
event: 'editor-journey-step',
editorJourneyStep: `${tutorialId}-On-My-Own`
});
setCanViewTutorialsHighlight(true);
setShowJourney(false);
driverObj.destroy();
@ -181,6 +211,14 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
() => ({
popoverClass: 'gui-journey',
overlayOpacity: 0,
onDestroyStarted: () => {
const stepName = STEP_NAMES[driverObj.getActiveIndex()] || '';
triggerAnalyticsEvent({
event: 'editor-journey-step',
editorJourneyStep: `${stepName}-closed`
});
driverObj.destroy();
},
onDestroyed: () => {
setShowJourney(false);
},
@ -195,17 +233,17 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
{
imgSrc: '/images/onboarding-journeys/Games-Icon.svg',
text: intl.formatMessage(messages.gameButtonText),
handleOnClick: () => driverObj.moveTo(1)
handleOnClick: () => pickStep(1, 'Games')
},
{
imgSrc: '/images/onboarding-journeys/Animation-Icon.svg',
text: intl.formatMessage(messages.animiationButtonText),
handleOnClick: () => driverObj.moveTo(2)
handleOnClick: () => pickStep(2, 'Animation')
},
{
imgSrc: '/images/onboarding-journeys/Music-Icon.svg',
text: intl.formatMessage(messages.musicButtonText),
handleOnClick: () => driverObj.moveTo(3)
handleOnClick: () => pickStep(3, 'Music')
}
]}
/>
@ -223,12 +261,12 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
{
imgSrc: '/images/onboarding-journeys/Clicker-Game.jpg',
text: intl.formatMessage(messages.clickerGameButtonText),
handleOnClick: () => driverObj.moveTo(4)
handleOnClick: () => pickStep(4, 'Clicker-Game')
},
{
imgSrc: '/images/onboarding-journeys/Pong-Game.jpg',
text: intl.formatMessage(messages.pongGameButtonText),
handleOnClick: () => driverObj.moveTo(5)
handleOnClick: () => pickStep(5, 'Pong-Game')
}
]}
/>
@ -246,12 +284,12 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
{
imgSrc: '/images/onboarding-journeys/Character-Animation.jpg',
text: intl.formatMessage(messages.characterAnimationButtonText),
handleOnClick: () => driverObj.moveTo(6)
handleOnClick: () => pickStep(6, 'Character-Animation')
},
{
imgSrc: '/images/onboarding-journeys/Fly-Animation.jpg',
text: intl.formatMessage(messages.flyAnimationButtonText),
handleOnClick: () => driverObj.moveTo(7)
handleOnClick: () => pickStep(7, 'Fly-Animation')
}
]}
/>
@ -269,12 +307,12 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
{
imgSrc: '/images/onboarding-journeys/Record-Music.jpg',
text: intl.formatMessage(messages.recordSoundButtonText),
handleOnClick: () => driverObj.moveTo(8)
handleOnClick: () => pickStep(8, 'Record-Music')
},
{
imgSrc: '/images/onboarding-journeys/Make-Music.jpg',
text: intl.formatMessage(messages.makeMusicButtonText),
handleOnClick: () => driverObj.moveTo(9)
handleOnClick: () => pickStep(9, 'Make-Music')
}
]}
/>

38
src/lib/onboarding.js Normal file
View file

@ -0,0 +1,38 @@
const calculateAgeGroup = (birthYear, birthMonth) => {
const today = new Date();
let age = today.getFullYear() - parseInt(birthYear, 10);
const monthDiff = (today.getMonth() + 1) - birthMonth;
if (monthDiff < 0) {
age--;
}
if (age <= 10) {
return '[00-10]';
} else if (age <= 16) {
return '[11-16]';
}
return '[17+]';
};
// add logic when implementing the eligibility check in [UEPR-71]
const onboardingTestGroup = () => 'Displayed Onboarding Journeys';
export const triggerAnalyticsEvent = eventVaribles => {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
...eventVaribles
});
};
export const sendUserProperties = user => {
window.dataLayer = window.dataLayer || [];
const {gender, birthYear, birthMonth} = user;
window.dataLayer.push({
testGroup: onboardingTestGroup(),
ageGroup: calculateAgeGroup(birthYear, birthMonth),
gender: gender
});
};

View file

@ -41,8 +41,9 @@ const projectShape = require('./projectshape.jsx').projectShape;
require('./preview.scss');
const frameless = require('../../lib/frameless');
const {useState, useCallback} = require('react');
const {useState, useCallback, useEffect} = require('react');
const ProjectJourney = require('../../components/journeys/project-journey/project-journey.jsx');
const {triggerAnalyticsEvent} = require('../../lib/onboarding.js');
// disable enter key submission on formsy input fields; otherwise formsy thinks
// we meant to trigger the "See inside" button. Instead, treat these keypresses
@ -235,6 +236,15 @@ const PreviewPresentation = ({
}
}, [hasSubmittedComment, user]);
useEffect(() => {
if (canViewProjectJourney && projectInfo.title) {
triggerAnalyticsEvent({
event: 'editor-journey-step',
editorJourneyStep: `${projectInfo.title}-Starter-Project`
});
}
}, [canViewProjectJourney, projectInfo.title]);
return (
<div className="preview">
{showEmailConfirmationModal && <EmailConfirmationModal

View file

@ -47,6 +47,7 @@ const {useEffect, useState} = require('react');
const EditorJourney = require('../../components/journeys/editor-journey/editor-journey.jsx');
const {usePrevious} = require('react-use');
const TutorialsHighlight = require('../../components/journeys/tutorials-highlight/tutorials-highlight.jsx');
const {triggerAnalyticsEvent, sendUserProperties} = require('../../lib/onboarding.js');
const IntlGUIWithProjectHandler = ({...props}) => {
const [showJourney, setShowJourney] = useState(false);
@ -255,6 +256,10 @@ class Preview extends React.Component {
false // Do not show cloud/username alerts again
);
}
if (!prevProps.user.id && this.props.user.id) {
sendUserProperties(this.props.user);
}
}
componentWillUnmount () {
this.removeEventListeners();
@ -540,6 +545,15 @@ class Preview extends React.Component {
if (!this.state.greenFlagRecorded) {
this.props.logProjectView(this.props.projectInfo.id, this.props.authorUsername, this.props.user.token);
}
const showJourney = queryString.parse(location.search, {parseBooleans: true}).showJourney;
if (showJourney) {
triggerAnalyticsEvent({
event: 'tutorial-played',
playedProject: this.props.projectInfo.title
});
}
this.setState({
showUsernameBlockAlert: false,
showCloudDataAlert: false,
@ -651,6 +665,14 @@ class Preview extends React.Component {
}
}
handleRemix () {
const showJourney = queryString.parse(location.search, {parseBooleans: true}).showJourney;
if (showJourney) {
triggerAnalyticsEvent({
event: 'tutorial-remixed',
remixedProject: this.props.projectInfo.title
});
}
// Update the state first before starting the remix to show spinner
this.setState({isRemixing: true}, () => {
this.props.remixProject();