mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 07:38:07 -05:00
Merge pull request #8830 from MiroslavDionisiev/UEPR-51
[UERP-51] Google Analytics events for onboarding journeys
This commit is contained in:
commit
7c58c33d12
4 changed files with 119 additions and 11 deletions
|
@ -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
38
src/lib/onboarding.js
Normal 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
|
||||
});
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue