diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index 248811b86a..fe5a42bb85 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -24,8 +24,11 @@ void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) { logAction("toggled_away", { { "is_away", isAway } }); } -void UserActivityLoggerScriptingInterface::tutorialProgress(QString stepName, int stepNumber, float secondsToComplete, float tutorialElapsedTime) { +void UserActivityLoggerScriptingInterface::tutorialProgress( QString stepName, int stepNumber, float secondsToComplete, + float tutorialElapsedTime, QString tutorialRunID, int tutorialVersion) { logAction("tutorial_progress", { + { "tutorial_run_id", tutorialRunID }, + { "tutorial_version", tutorialVersion }, { "step", stepName }, { "step_number", stepNumber }, { "seconds_to_complete", secondsToComplete }, diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h index bf3e20a2d7..52101e3e53 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.h +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -23,7 +23,8 @@ public: Q_INVOKABLE void enabledEdit(); Q_INVOKABLE void openedMarketplace(); Q_INVOKABLE void toggledAway(bool isAway); - Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete, float tutorialElapsedTime); + Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete, + float tutorialElapsedTime, QString tutorialRunID = "", int tutorialVersion = 0); private: void logAction(QString action, QJsonObject details = {}); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 9fc3de1c9e..01088660ff 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -146,6 +146,8 @@ public: Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } + Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); } + bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget diff --git a/tutorial/tutorial.js b/tutorial/tutorial.js index 0fb86d7247..4908465779 100644 --- a/tutorial/tutorial.js +++ b/tutorial/tutorial.js @@ -391,61 +391,6 @@ stepOrient.prototype = { } }; -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -// // -// STEP: Raise hands above head // -// // -/////////////////////////////////////////////////////////////////////////////// -var stepRaiseAboveHead = function(name) { - this.tag = name; - this.tempTag = name + "-temporary"; -} -stepRaiseAboveHead.prototype = { - start: function(onFinish) { - var tag = this.tag; - - var STATE_START = 0; - var STATE_HANDS_DOWN = 1; - var STATE_HANDS_UP = 2; - this.state = STATE_START; - - editEntitiesWithTag(this.tag, { visible: true }); - - // Wait 2 seconds before starting to check for hands - this.checkIntervalID = null; - function checkForHandsAboveHead() { - debug("RaiseAboveHead | Checking hands"); - if (this.state == STATE_START) { - if (MyAvatar.getLeftPalmPosition().y < (MyAvatar.getHeadPosition().y - 0.1)) { - this.state = STATE_HANDS_DOWN; - } - } else if (this.state == STATE_HANDS_DOWN) { - if (MyAvatar.getLeftPalmPosition().y > (MyAvatar.getHeadPosition().y + 0.1)) { - this.state = STATE_HANDS_UP; - Script.clearInterval(this.checkIntervalID); - this.checkIntervalID = null; - playSuccessSound(); - onFinish(); - } - } - } - this.checkIntervalID = Script.setInterval(checkForHandsAboveHead.bind(this), 500); - }, - cleanup: function() { - debug("RaiseAboveHead | Cleanup"); - if (this.checkIntervalID) { - Script.clearInterval(this.checkIntervalID); - this.checkIntervalID = null - } - if (this.waitTimeoutID) { - Script.clearTimeout(this.waitTimeoutID); - this.waitTimeoutID = null; - } - editEntitiesWithTag(this.tag, { visible: false, collisionless: 1 }); - deleteEntitiesWithTag(this.tempTag); - } -}; /////////////////////////////////////////////////////////////////////////////// @@ -1027,16 +972,23 @@ TutorialManager = function() { var startedTutorialAt = 0; var startedLastStepAt = 0; + var wentToEntryStepNum; + var VERSION = 1; + var tutorialID; + var self = this; this.startTutorial = function() { currentStepNum = -1; currentStep = null; startedTutorialAt = Date.now(); + + // Old versions of interface do not have the Script.generateUUID function. + // If Script.generateUUID is not available, default to an empty string. + tutorialID = Script.generateUUID ? Script.generateUUID() : ""; STEPS = [ new stepStart("start"), new stepOrient("orient"), - //new stepRaiseAboveHead("raiseHands"), new stepNearGrab("nearGrab"), new stepFarGrab("farGrab"), new stepEquip("equip"), @@ -1045,6 +997,7 @@ TutorialManager = function() { new stepFinish("finish"), new stepEnableControllers("enableControllers"), ]; + wentToEntryStepNum = STEPS.length; for (var i = 0; i < STEPS.length; ++i) { STEPS[i].cleanup(); } @@ -1055,10 +1008,7 @@ TutorialManager = function() { this.onFinish = function() { debug("onFinish", currentStepNum); if (currentStep && currentStep.shouldLog !== false) { - var timeToFinishStep = (Date.now() - startedLastStepAt) / 1000; - var tutorialTimeElapsed = (Date.now() - startedTutorialAt) / 1000; - UserActivityLogger.tutorialProgress( - currentStep.tag, currentStepNum, timeToFinishStep, tutorialTimeElapsed); + self.trackStep(currentStep.tag, currentStepNum); } self.startNextStep(); @@ -1071,6 +1021,12 @@ TutorialManager = function() { ++currentStepNum; + // This always needs to be set because we use this value when + // tracking that the user has gone through the entry portal. When the + // tutorial finishes, there is a last "pseudo" step that the user + // finishes when stepping into the portal. + startedLastStepAt = Date.now(); + if (currentStepNum >= STEPS.length) { // Done info("DONE WITH TUTORIAL"); @@ -1080,7 +1036,6 @@ TutorialManager = function() { } else { info("Starting step", currentStepNum); currentStep = STEPS[currentStepNum]; - startedLastStepAt = Date.now(); currentStep.start(this.onFinish); return true; } @@ -1102,6 +1057,21 @@ TutorialManager = function() { currentStepNum = -1; currentStep = null; } + + this.trackStep = function(name, stepNum) { + var timeToFinishStep = (Date.now() - startedLastStepAt) / 1000; + var tutorialTimeElapsed = (Date.now() - startedTutorialAt) / 1000; + UserActivityLogger.tutorialProgress( + name, stepNum, timeToFinishStep, tutorialTimeElapsed, + tutorialID, VERSION); + } + + // This is a message sent from the "entry" portal in the courtyard, + // after the tutorial has finished. + this.enteredEntryPortal = function() { + info("Got enteredEntryPortal, tracking"); + this.trackStep("wentToEntry", wentToEntryStepNum); + } } // To run the tutorial: diff --git a/tutorial/tutorialZone.js b/tutorial/tutorialZone.js index 40028c9cd7..01e2aa4c52 100644 --- a/tutorial/tutorialZone.js +++ b/tutorial/tutorialZone.js @@ -113,6 +113,14 @@ if (!Function.prototype.bind) { } }, + onEnteredEntryPortal: function() { + print("TutorialZone | Got onEnteredEntryPortal"); + if (this.tutorialManager) { + print("TutorialZone | Calling enteredEntryPortal"); + this.tutorialManager.enteredEntryPortal(); + } + }, + enterEntity: function() { print("TutorialZone | ENTERED THE TUTORIAL AREA"); }, @@ -125,7 +133,7 @@ if (!Function.prototype.bind) { } if (this.tutorialManager) { this.tutorialManager.stopTutorial(); - this.tutorialManager = null; + //this.tutorialManager = null; } } };