mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 01:25:52 -05:00
feat: [UEPR-40, UEPR-42] added integration with user guiding
This commit is contained in:
parent
37575a5e5d
commit
442e90bcca
5 changed files with 219 additions and 1 deletions
163
src/lib/user-guiding.js
Normal file
163
src/lib/user-guiding.js
Normal 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
|
||||||
|
};
|
|
@ -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({
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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' ? [
|
||||||
|
|
Loading…
Reference in a new issue