From 6fb0e3491d721b1fa98a8d2cad7acd2ecf8aacd3 Mon Sep 17 00:00:00 2001
From: Aleksandar Shumakov <a.shumakov97@gmail.com>
Date: Thu, 22 Aug 2024 15:28:54 +0300
Subject: [PATCH] feat: simplify user guiding triggers

- Change exposed handler names to correspond to performed action
- Simplify method of picking random survey on interaction with Editor
- Move GUI useEffect hook to WWW in IntlGUIWithProjectHandler
- Remove onClick property from ComposeComment component
---
 package-lock.json                             |   7 +
 package.json                                  |   1 +
 src/lib/user-guiding.js                       | 170 +++++++-----------
 src/views/preview/comment/compose-comment.jsx |   2 -
 src/views/preview/presentation.jsx            |  30 ++--
 src/views/preview/project-view.jsx            |  45 ++---
 webpack.config.js                             |   2 +-
 7 files changed, 111 insertions(+), 146 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index de7ae7809..538ddba95 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -80,6 +80,7 @@
         "lodash.merge": "4.6.2",
         "lodash.mergewith": "4.6.2",
         "lodash.omit": "3.1.0",
+        "lodash.sample": "4.2.1",
         "lodash.uniqby": "4.7.0",
         "mini-css-extract-plugin": "1.6.2",
         "minilog": "2.1.0",
@@ -17081,6 +17082,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/lodash.sample": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/lodash.sample/-/lodash.sample-4.2.1.tgz",
+      "integrity": "sha512-odCZufa8jYDBZQ+JOSePWRs+iApPdvIp3qAiKc9F22RdSCMLuUu60Jvgs2M6qMisKAeBZoumAkqDiGr9HDym/Q==",
+      "dev": true
+    },
     "node_modules/lodash.throttle": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
diff --git a/package.json b/package.json
index e12d644e1..261a4503d 100644
--- a/package.json
+++ b/package.json
@@ -116,6 +116,7 @@
     "lodash.mergewith": "4.6.2",
     "lodash.omit": "3.1.0",
     "lodash.uniqby": "4.7.0",
+    "lodash.sample": "4.2.1",
     "mini-css-extract-plugin": "1.6.2",
     "minilog": "2.1.0",
     "pako": "0.2.8",
diff --git a/src/lib/user-guiding.js b/src/lib/user-guiding.js
index c899d86e0..0cea4d82b 100644
--- a/src/lib/user-guiding.js
+++ b/src/lib/user-guiding.js
@@ -1,7 +1,12 @@
 const api = require('./api');
+const sample = require('lodash.sample');
 
 const USER_GUIDING_ID = process.env.USER_GUIDING_ID;
-const SCRIPT_ID = 'UserGuiding';
+const AUTONOMY_SURVEY_ID = 3048;
+const RELATIONSHIP_SURVEY_ID = 3049;
+const JOY_SURVEY_ID = 3050;
+const COMPETENCE_SURVEY_ID = 3045;
+const EDITOR_INTERACTION_SURVEY_IDS = [COMPETENCE_SURVEY_ID, JOY_SURVEY_ID];
 
 const CONDITIONS = {condition_list: [
     'IsLoggedIn',
@@ -9,78 +14,58 @@ const CONDITIONS = {condition_list: [
     'NotMuted'
 ]};
 
-const getUserGuidingSnippet = () => (
-    `
-        (function(g, u, i, d, e, s) {
-            g[e] = g[e] || [];
-            var f = u.getElementsByTagName(i)[0];
-            var k = u.createElement(i);
-            k.async = true;
-            k.src = 'https://static.userguiding.com/media/user-guiding-' + s + '-embedded.js';
-            f.parentNode.insertBefore(k, f);
-            if (g[d]) return;
-            var ug = g[d] = {
-                q: []
+const USER_GUIDING_SNIPPET = `
+    (function(g, u, i, d, e, s) {
+        g[e] = g[e] || [];
+        var f = u.getElementsByTagName(i)[0];
+        var k = u.createElement(i);
+        k.async = true;
+        k.src = 'https://static.userguiding.com/media/user-guiding-' + s + '-embedded.js';
+        f.parentNode.insertBefore(k, f);
+        if (g[d]) return;
+        var ug = g[d] = {
+            q: []
+        };
+        ug.c = function(n) {
+            return function() {
+                ug.q.push([n, arguments])
             };
-            ug.c = function(n) {
-                return function() {
-                    ug.q.push([n, arguments])
-                };
-            };
-            var m = ['previewGuide', 'finishPreview', 'track', 'identify', 'hideChecklist', 'launchChecklist'];
-            for (var j = 0; j < m.length; j += 1) {
-                ug[m[j]] = ug.c(m[j]);
-            }
-        })(window, document, 'script', 'userGuiding', 'userGuidingLayer', '${USER_GUIDING_ID}');
-    `
-);
-
-const identifyUser = userId => {
-    window.userGuiding.identify(userId.toString());
-};
-
-const launchSurvey = surveyId => {
-    window.userGuiding.launchSurvey(surveyId);
-};
-
-const probabilityPicker = data => {
-    let generatedValue = Math.random();
-
-    for (const tmp of data) {
-        if (tmp.probability < generatedValue) {
-            generatedValue -= tmp.probability;
-        } else {
-            return tmp.prompt;
+        };
+        var m = ['previewGuide', 'finishPreview', 'track', 'identify', 'hideChecklist', 'launchChecklist'];
+        for (var j = 0; j < m.length; j += 1) {
+            ug[m[j]] = ug.c(m[j]);
         }
-    }
-};
+    })(window, document, 'script', 'userGuiding', 'userGuidingLayer', '${USER_GUIDING_ID}');
+`;
 
 const activateUserGuiding = (userId, callback) => {
-    if (USER_GUIDING_ID && !document.getElementById(SCRIPT_ID)) {
-        const userGuidingScript = document.createElement('script');
-        userGuidingScript.id = SCRIPT_ID;
-        userGuidingScript.innerHTML = getUserGuidingSnippet();
-        document.head.insertBefore(userGuidingScript, document.head.firstChild);
-
-        window.userGuidingSettings = {
-            disablePageViewAutoCapture: true
-        };
-
-        window.userGuidingLayer.push({
-            event: 'onload',
-            fn: () => identifyUser(userId)
-        });
-
-        window.userGuidingLayer.push({
-            event: 'onIdentificationComplete',
-            fn: callback
-        });
-    } else if (window.userGuiding) {
+    if (window.userGuiding) {
         callback();
+        return;
     }
+
+    const userGuidingScript = document.createElement('script');
+    userGuidingScript.innerHTML = USER_GUIDING_SNIPPET;
+    document.head.insertBefore(userGuidingScript, document.head.firstChild);
+
+    window.userGuidingSettings = {disablePageViewAutoCapture: true};
+
+    window.userGuidingLayer.push({
+        event: 'onload',
+        fn: () => window.userGuiding.identify(userId.toString())
+    });
+
+    window.userGuidingLayer.push({
+        event: 'onIdentificationComplete',
+        fn: callback
+    });
 };
 
-const displayUserGuiding = (userId, permissions, guideId, callback) => (
+const attemptDisplayUserGuidingSurvey = (userId, permissions, guideId, callback) => {
+    if (!USER_GUIDING_ID || !process.env.SORTING_HAT_HOST) {
+        return;
+    }
+
     api({
         uri: '/user_guiding',
         method: 'GET',
@@ -97,66 +82,43 @@ const displayUserGuiding = (userId, permissions, guideId, callback) => (
             return;
         }
 
-        if (body?.result === "true") {
+        if (body?.result === 'true') {
             activateUserGuiding(userId, callback);
         }
-    })
-);
-
-const loadCompetenceSurvey = (userId, permissions) => {
-    const COMPETENCE_SURVEY_ID = 3045;
-
-    displayUserGuiding(
-        userId,
-        permissions,
-        COMPETENCE_SURVEY_ID,
-        () => launchSurvey(COMPETENCE_SURVEY_ID)
-    );
+    });
 };
 
-const loadAutonomySurvey = (userId, permissions) => {
-    const AUTONOMY_SURVEY_ID = 3048;
-
-    displayUserGuiding(
+const onCommented = (userId, permissions) => {
+    attemptDisplayUserGuidingSurvey(
         userId,
         permissions,
         AUTONOMY_SURVEY_ID,
-        () => launchSurvey(AUTONOMY_SURVEY_ID)
+        () => window.userGuiding.launchSurvey(AUTONOMY_SURVEY_ID)
     );
 };
 
-const loadRelationshipsSurvey = (userId, permissions) => {
-    const RELATIONSHIP_SURVEY_ID = 3049;
-
-    displayUserGuiding(
+const onProjectShared = (userId, permissions) => {
+    attemptDisplayUserGuidingSurvey(
         userId,
         permissions,
         RELATIONSHIP_SURVEY_ID,
-        () => launchSurvey(RELATIONSHIP_SURVEY_ID)
+        () => window.userGuiding.launchSurvey(RELATIONSHIP_SURVEY_ID)
     );
 };
 
-const loadJoySurvey = (userId, permissions) => {
-    const JOY_SURVEY_ID = 3050;
+const onProjectLoaded = (userId, permissions) => {
+    const surveyId = sample(EDITOR_INTERACTION_SURVEY_IDS);
 
-    displayUserGuiding(
+    attemptDisplayUserGuidingSurvey(
         userId,
         permissions,
-        JOY_SURVEY_ID,
-        () => launchSurvey(JOY_SURVEY_ID)
+        surveyId,
+        () => window.userGuiding.launchSurvey(surveyId)
     );
 };
 
-const loadRandomPrompt = (userId, permissions, data) => {
-    const prompt = probabilityPicker(data);
-
-    prompt(userId, permissions);
-};
-
 module.exports = {
-    loadCompetenceSurvey,
-    loadAutonomySurvey,
-    loadRelationshipsSurvey,
-    loadJoySurvey,
-    loadRandomPrompt
+    onProjectLoaded,
+    onCommented,
+    onProjectShared
 };
diff --git a/src/views/preview/comment/compose-comment.jsx b/src/views/preview/comment/compose-comment.jsx
index adba435d6..cfa706dec 100644
--- a/src/views/preview/comment/compose-comment.jsx
+++ b/src/views/preview/comment/compose-comment.jsx
@@ -360,7 +360,6 @@ class ComposeComment extends React.Component {
                                     type="textarea"
                                     value={this.state.message}
                                     onInput={this.handleInput}
-                                    onClick={this.props.onClick}
                                     autoFocus={this.props.isReply}
                                 />
                                 <FlexRow className="compose-bottom-row">
@@ -438,7 +437,6 @@ ComposeComment.propTypes = {
     }),
     onAddComment: PropTypes.func,
     onCancel: PropTypes.func,
-    onClick: PropTypes.func,
     parentId: PropTypes.number,
     postURI: PropTypes.string,
     user: PropTypes.shape({
diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx
index ced00b5da..6b8e80c03 100644
--- a/src/views/preview/presentation.jsx
+++ b/src/views/preview/presentation.jsx
@@ -34,7 +34,7 @@ const thumbnailUrl = require('../../lib/user-thumbnail');
 const FormsyProjectUpdater = require('./formsy-project-updater.jsx');
 const EmailConfirmationModal = require('../../components/modal/email-confirmation/modal.jsx');
 const EmailConfirmationBanner = require('../../components/dropdown-banner/email-confirmation/banner.jsx');
-const {loadAutonomySurvey} = require('../../lib/user-guiding.js');
+const {onCommented} = require('../../lib/user-guiding.js');
 
 const projectShape = require('./projectshape.jsx').projectShape;
 require('./preview.scss');
@@ -147,7 +147,7 @@ const PreviewPresentation = ({
     userOwnsProject,
     visibilityInfo
 }) => {
-    const [interactionWithComment, setInteractionWithComment] = useState(false);
+    const [hasSubmittedComment, setHasSubmittedComment] = useState(false);
     const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : '';
     const revisedDate = ((projectInfo.history && projectInfo.history.modified)) ? projectInfo.history.modified : '';
     const showInstructions = editable || projectInfo.instructions ||
@@ -221,13 +221,14 @@ const PreviewPresentation = ({
         </FlexRow>
     );
 
-    const onCommentClick = useCallback(() => {
-        if (!interactionWithComment && user) {
-            setInteractionWithComment(!interactionWithComment);
-            loadAutonomySurvey(user.id, permissions);
+    const onAddCommentWrapper = useCallback(body => {
+        onAddComment(body);
+        if (!hasSubmittedComment && user) {
+            setHasSubmittedComment(true);
+            onCommented(user.id, permissions);
         }
-    }, [interactionWithComment, user]);
-
+    }, [hasSubmittedComment, user]);
+    
     return (
         <div className="preview">
             {showEmailConfirmationModal && <EmailConfirmationModal
@@ -623,8 +624,7 @@ const PreviewPresentation = ({
                                                         isLoggedIn ? (
                                                             isShared && <ComposeComment
                                                                 postURI={`/proxy/comments/project/${projectId}`}
-                                                                onAddComment={onAddComment}
-                                                                onClick={onCommentClick}
+                                                                onAddComment={onAddCommentWrapper}
                                                             />
                                                         ) : (
                                                         /* TODO add box for signing in to leave a comment */
@@ -816,15 +816,7 @@ PreviewPresentation.propTypes = {
     singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
     socialOpen: PropTypes.bool,
     user: PropTypes.shape({
-        id: PropTypes.number,
-        banned: PropTypes.bool,
-        vpn_required: PropTypes.bool,
-        username: PropTypes.string,
-        token: PropTypes.string,
-        thumbnailUrl: PropTypes.string,
-        dateJoined: PropTypes.string,
-        email: PropTypes.string,
-        classroomId: PropTypes.string
+        id: PropTypes.number
     }),
     userOwnsProject: PropTypes.bool,
     visibilityInfo: PropTypes.shape({
diff --git a/src/views/preview/project-view.jsx b/src/views/preview/project-view.jsx
index bd06654c6..b42fd67d0 100644
--- a/src/views/preview/project-view.jsx
+++ b/src/views/preview/project-view.jsx
@@ -26,10 +26,8 @@ const CanceledDeletionModal = require('../../components/login/canceled-deletion-
 const NotAvailable = require('../../components/not-available/not-available.jsx');
 const Meta = require('./meta.jsx');
 const {
-    loadRelationshipsSurvey,
-    loadCompetenceSurvey,
-    loadJoySurvey,
-    loadRandomPrompt
+    onProjectShared,
+    onProjectLoaded
 } = require('../../lib/user-guiding.js');
 
 const sessionActions = require('../../redux/session.js');
@@ -46,6 +44,25 @@ const IntlGUI = injectIntl(GUI.default);
 const localStorageAvailable = 'localStorage' in window && window.localStorage !== null;
 
 const xhr = require('xhr');
+const {useEffect} = require('react');
+
+const IntlGUIWithProjectHandler = ({user, permissions, ...props}) => {
+    useEffect(() => {
+        if (props.projectId && props.projectId !== '0') {
+            onProjectLoaded(user.id, permissions);
+        }
+    }, [props.projectId, user.id, permissions]);
+
+    return <IntlGUI {...props} />;
+};
+
+IntlGUIWithProjectHandler.propTypes = {
+    ...GUI.propTypes,
+    user: PropTypes.shape({
+        id: PropTypes.number
+    }),
+    permissions: PropTypes.object
+};
 
 class Preview extends React.Component {
     constructor (props) {
@@ -89,7 +106,6 @@ class Preview extends React.Component {
             'handleUpdateProjectId',
             'handleUpdateProjectTitle',
             'handleToggleComments',
-            'handleLoadRandomPrompt',
             'initCounts',
             'pushHistory',
             'renderLogin',
@@ -634,7 +650,7 @@ class Preview extends React.Component {
             justRemixed: false,
             justShared: true
         });
-        loadRelationshipsSurvey(this.props.user.id, this.props.permissions);
+        onProjectShared(this.props.user.id, this.props.permissions);
     }
     handleShareAttempt () {
         this.setState({
@@ -702,18 +718,6 @@ class Preview extends React.Component {
             this.props.user.token
         );
     }
-    handleLoadRandomPrompt () {
-        loadRandomPrompt(this.props.user.id, this.props.permissions, [
-            {
-                prompt: loadCompetenceSurvey,
-                probability: 0.5
-            },
-            {
-                prompt: loadJoySurvey,
-                probability: 0.5
-            }
-        ]);
-    }
     initCounts (favorites, loves) {
         this.setState({
             favoriteCount: favorites,
@@ -863,7 +867,7 @@ class Preview extends React.Component {
                     </Page> :
                     <React.Fragment>
                         {showGUI && (
-                            <IntlGUI
+                            <IntlGUIWithProjectHandler
                                 assetHost={this.props.assetHost}
                                 authorId={this.props.authorId}
                                 authorThumbnailUrl={this.props.authorThumbnailUrl}
@@ -901,7 +905,8 @@ class Preview extends React.Component {
                                 onUpdateProjectId={this.handleUpdateProjectId}
                                 onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail}
                                 onUpdateProjectTitle={this.handleUpdateProjectTitle}
-                                onLoadRandomPrompt={this.handleLoadRandomPrompt}
+                                user={this.props.user}
+                                permissions={this.props.permissions}
                             />
                         )}
                         {this.props.registrationOpen && (
diff --git a/webpack.config.js b/webpack.config.js
index aec11a587..8f71b2b00 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -281,7 +281,7 @@ module.exports = {
             'process.env.GTM_ENV_AUTH': `"${process.env.GTM_ENV_AUTH || ''}"`,
             'process.env.GTM_ID': process.env.GTM_ID ? `"${process.env.GTM_ID}"` : null,
             'process.env.USER_GUIDING_ID': `"${process.env.USER_GUIDING_ID || ''}"`,
-            'process.env.SORTING_HAT_HOST': `"${process.env.SORTING_HAT_HOST || 'http://127.0.0.1:7676'}"`
+            'process.env.SORTING_HAT_HOST': `"${process.env.SORTING_HAT_HOST || ''}"`
         })
     ])
         .concat(process.env.ANALYZE_BUNDLE === 'true' ? [