feat: [UEPR-40, UEPR-42] added integration with user guiding

This commit is contained in:
MiroslavDionisiev 2024-08-20 17:13:23 +03:00 committed by Aleksandar Shumakov
parent 37575a5e5d
commit 442e90bcca
5 changed files with 219 additions and 1 deletions

163
src/lib/user-guiding.js Normal file
View file

@ -0,0 +1,163 @@
const api = require('./api');
const USER_GUIDING_ID = process.env.USER_GUIDING_ID;
const SCRIPT_ID = 'UserGuiding';
const CONDITIONS = {condition_list: [
'IsLoggedIn',
'IsNewScratcher',
'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: []
};
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;
}
}
};
const activateUserGuiding = (userId, callback) => {
if (!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) {
callback();
}
};
const displayUserGuiding = (userId, permissions, guideId, callback) => (
api({
uri: '/user_guiding',
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-USERID': userId,
'X-PERMISSIONS': JSON.stringify(permissions),
'X-CONDITIONS': JSON.stringify(CONDITIONS),
'X-QUESTION-NUMBER': guideId
},
host: process.env.SORTING_HAT_HOST
}, (err, body, res) => {
if (err || res.statusCode !== 200) {
return;
}
const {result} = JSON.stringify(body);
if (result) {
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(
userId,
permissions,
AUTONOMY_SURVEY_ID,
() => launchSurvey(AUTONOMY_SURVEY_ID)
);
};
const loadRelationshipsSurvey = (userId, permissions) => {
const RELATIONSHIP_SURVEY_ID = 3049;
displayUserGuiding(
userId,
permissions,
RELATIONSHIP_SURVEY_ID,
() => launchSurvey(RELATIONSHIP_SURVEY_ID)
);
};
const loadJoySurvey = (userId, permissions) => {
const JOY_SURVEY_ID = 3050;
displayUserGuiding(
userId,
permissions,
JOY_SURVEY_ID,
() => launchSurvey(JOY_SURVEY_ID)
);
};
const loadRandomPrompt = (userId, permissions, data) => {
const prompt = probabilityPicker(data);
prompt(userId, permissions);
};
module.exports = {
loadCompetenceSurvey,
loadAutonomySurvey,
loadRelationshipsSurvey,
loadJoySurvey,
loadRandomPrompt
};

View file

@ -360,6 +360,7 @@ class ComposeComment extends React.Component {
type="textarea" type="textarea"
value={this.state.message} value={this.state.message}
onInput={this.handleInput} onInput={this.handleInput}
onClick={this.props.onClick}
autoFocus={this.props.isReply} autoFocus={this.props.isReply}
/> />
<FlexRow className="compose-bottom-row"> <FlexRow className="compose-bottom-row">
@ -437,6 +438,7 @@ ComposeComment.propTypes = {
}), }),
onAddComment: PropTypes.func, onAddComment: PropTypes.func,
onCancel: PropTypes.func, onCancel: PropTypes.func,
onClick: PropTypes.func,
parentId: PropTypes.number, parentId: PropTypes.number,
postURI: PropTypes.string, postURI: PropTypes.string,
user: PropTypes.shape({ user: PropTypes.shape({

View file

@ -34,11 +34,13 @@ const thumbnailUrl = require('../../lib/user-thumbnail');
const FormsyProjectUpdater = require('./formsy-project-updater.jsx'); const FormsyProjectUpdater = require('./formsy-project-updater.jsx');
const EmailConfirmationModal = require('../../components/modal/email-confirmation/modal.jsx'); const EmailConfirmationModal = require('../../components/modal/email-confirmation/modal.jsx');
const EmailConfirmationBanner = require('../../components/dropdown-banner/email-confirmation/banner.jsx'); const EmailConfirmationBanner = require('../../components/dropdown-banner/email-confirmation/banner.jsx');
const {loadAutonomySurvey} = require('../../lib/user-guiding.js');
const projectShape = require('./projectshape.jsx').projectShape; const projectShape = require('./projectshape.jsx').projectShape;
require('./preview.scss'); require('./preview.scss');
const frameless = require('../../lib/frameless'); const frameless = require('../../lib/frameless');
const {useState, useCallback} = require('react');
// disable enter key submission on formsy input fields; otherwise formsy thinks // disable enter key submission on formsy input fields; otherwise formsy thinks
// we meant to trigger the "See inside" button. Instead, treat these keypresses // we meant to trigger the "See inside" button. Instead, treat these keypresses
@ -127,6 +129,7 @@ const PreviewPresentation = ({
showCloudDataAlert, showCloudDataAlert,
showCloudDataAndVideoAlert, showCloudDataAndVideoAlert,
showUsernameBlockAlert, showUsernameBlockAlert,
permissions,
projectHost, projectHost,
projectId, projectId,
projectInfo, projectInfo,
@ -140,9 +143,11 @@ const PreviewPresentation = ({
showEmailConfirmationBanner, showEmailConfirmationBanner,
singleCommentId, singleCommentId,
socialOpen, socialOpen,
user,
userOwnsProject, userOwnsProject,
visibilityInfo visibilityInfo
}) => { }) => {
const [interactionWithComment, setInteractionWithComment] = useState(false);
const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : ''; const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : '';
const revisedDate = ((projectInfo.history && projectInfo.history.modified)) ? projectInfo.history.modified : ''; const revisedDate = ((projectInfo.history && projectInfo.history.modified)) ? projectInfo.history.modified : '';
const showInstructions = editable || projectInfo.instructions || const showInstructions = editable || projectInfo.instructions ||
@ -215,6 +220,14 @@ const PreviewPresentation = ({
))} ))}
</FlexRow> </FlexRow>
); );
const onCommentClick = useCallback(() => {
if (!interactionWithComment && user) {
setInteractionWithComment(!interactionWithComment);
loadAutonomySurvey(user.id, permissions);
}
}, [interactionWithComment, user]);
return ( return (
<div className="preview"> <div className="preview">
{showEmailConfirmationModal && <EmailConfirmationModal {showEmailConfirmationModal && <EmailConfirmationModal
@ -611,6 +624,7 @@ const PreviewPresentation = ({
isShared && <ComposeComment isShared && <ComposeComment
postURI={`/proxy/comments/project/${projectId}`} postURI={`/proxy/comments/project/${projectId}`}
onAddComment={onAddComment} onAddComment={onAddComment}
onClick={onCommentClick}
/> />
) : ( ) : (
/* TODO add box for signing in to leave a comment */ /* TODO add box for signing in to leave a comment */
@ -784,6 +798,7 @@ PreviewPresentation.propTypes = {
onUpdateProjectThumbnail: PropTypes.func, onUpdateProjectThumbnail: PropTypes.func,
originalInfo: projectShape, originalInfo: projectShape,
parentInfo: projectShape, parentInfo: projectShape,
permissions: PropTypes.object,
projectHost: PropTypes.string, projectHost: PropTypes.string,
projectId: PropTypes.string, projectId: PropTypes.string,
projectInfo: projectShape, projectInfo: projectShape,
@ -800,6 +815,17 @@ PreviewPresentation.propTypes = {
showUsernameBlockAlert: PropTypes.bool, showUsernameBlockAlert: PropTypes.bool,
singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]), singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
socialOpen: 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
}),
userOwnsProject: PropTypes.bool, userOwnsProject: PropTypes.bool,
visibilityInfo: PropTypes.shape({ visibilityInfo: PropTypes.shape({
censored: PropTypes.bool, censored: PropTypes.bool,

View file

@ -25,6 +25,12 @@ const ConnectedLogin = require('../../components/login/connected-login.jsx');
const CanceledDeletionModal = require('../../components/login/canceled-deletion-modal.jsx'); const CanceledDeletionModal = require('../../components/login/canceled-deletion-modal.jsx');
const NotAvailable = require('../../components/not-available/not-available.jsx'); const NotAvailable = require('../../components/not-available/not-available.jsx');
const Meta = require('./meta.jsx'); const Meta = require('./meta.jsx');
const {
loadRelationshipsSurvey,
loadCompetenceSurvey,
loadJoySurvey,
loadRandomPrompt
} = require('../../lib/user-guiding.js');
const sessionActions = require('../../redux/session.js'); const sessionActions = require('../../redux/session.js');
const {selectProjectCommentsGloballyEnabled, selectIsTotallyNormal} = require('../../redux/session'); const {selectProjectCommentsGloballyEnabled, selectIsTotallyNormal} = require('../../redux/session');
@ -83,6 +89,7 @@ class Preview extends React.Component {
'handleUpdateProjectId', 'handleUpdateProjectId',
'handleUpdateProjectTitle', 'handleUpdateProjectTitle',
'handleToggleComments', 'handleToggleComments',
'handleLoadRandomPrompt',
'initCounts', 'initCounts',
'pushHistory', 'pushHistory',
'renderLogin', 'renderLogin',
@ -627,6 +634,7 @@ class Preview extends React.Component {
justRemixed: false, justRemixed: false,
justShared: true justShared: true
}); });
loadRelationshipsSurvey(this.props.user.id, this.props.permissions);
} }
handleShareAttempt () { handleShareAttempt () {
this.setState({ this.setState({
@ -694,6 +702,18 @@ class Preview extends React.Component {
this.props.user.token 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) { initCounts (favorites, loves) {
this.setState({ this.setState({
favoriteCount: favorites, favoriteCount: favorites,
@ -786,6 +806,7 @@ class Preview extends React.Component {
moreCommentsToLoad={this.props.moreCommentsToLoad} moreCommentsToLoad={this.props.moreCommentsToLoad}
originalInfo={this.props.original} originalInfo={this.props.original}
parentInfo={this.props.parent} parentInfo={this.props.parent}
permissions={this.props.permissions}
projectHost={this.props.projectHost} projectHost={this.props.projectHost}
projectId={this.state.projectId} projectId={this.state.projectId}
projectInfo={this.props.projectInfo} projectInfo={this.props.projectInfo}
@ -802,6 +823,7 @@ class Preview extends React.Component {
showUsernameBlockAlert={this.state.showUsernameBlockAlert} showUsernameBlockAlert={this.state.showUsernameBlockAlert}
singleCommentId={this.state.singleCommentId} singleCommentId={this.state.singleCommentId}
socialOpen={this.state.socialOpen} socialOpen={this.state.socialOpen}
user={this.props.user}
userOwnsProject={this.props.userOwnsProject} userOwnsProject={this.props.userOwnsProject}
visibilityInfo={this.props.visibilityInfo} visibilityInfo={this.props.visibilityInfo}
onAddComment={this.handleAddComment} onAddComment={this.handleAddComment}
@ -879,6 +901,7 @@ class Preview extends React.Component {
onUpdateProjectId={this.handleUpdateProjectId} onUpdateProjectId={this.handleUpdateProjectId}
onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail} onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail}
onUpdateProjectTitle={this.handleUpdateProjectTitle} onUpdateProjectTitle={this.handleUpdateProjectTitle}
onLoadRandomPrompt={this.handleLoadRandomPrompt}
/> />
)} )}
{this.props.registrationOpen && ( {this.props.registrationOpen && (
@ -957,6 +980,7 @@ Preview.propTypes = {
moreCommentsToLoad: PropTypes.bool, moreCommentsToLoad: PropTypes.bool,
original: projectShape, original: projectShape,
parent: projectShape, parent: projectShape,
permissions: PropTypes.object,
playerMode: PropTypes.bool, playerMode: PropTypes.bool,
projectHost: PropTypes.string.isRequired, projectHost: PropTypes.string.isRequired,
projectInfo: projectShape, projectInfo: projectShape,
@ -1076,6 +1100,7 @@ const mapStateToProps = state => {
moreCommentsToLoad: state.comments.moreCommentsToLoad, moreCommentsToLoad: state.comments.moreCommentsToLoad,
original: state.preview.original, original: state.preview.original,
parent: state.preview.parent, parent: state.preview.parent,
permissions: state.permissions,
playerMode: state.scratchGui.mode.isPlayerOnly, playerMode: state.scratchGui.mode.isPlayerOnly,
projectInfo: state.preview.projectInfo, projectInfo: state.preview.projectInfo,
projectNotAvailable: state.preview.projectNotAvailable, projectNotAvailable: state.preview.projectNotAvailable,

View file

@ -279,7 +279,9 @@ module.exports = {
'process.env.DEBUG': Boolean(process.env.DEBUG), 'process.env.DEBUG': Boolean(process.env.DEBUG),
'process.env.GA_ID': `"${process.env.GA_ID || 'UA-000000-01'}"`, 'process.env.GA_ID': `"${process.env.GA_ID || 'UA-000000-01'}"`,
'process.env.GTM_ENV_AUTH': `"${process.env.GTM_ENV_AUTH || ''}"`, '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.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'}"`
}) })
]) ])
.concat(process.env.ANALYZE_BUNDLE === 'true' ? [ .concat(process.env.ANALYZE_BUNDLE === 'true' ? [