mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-05-30 22:44:18 -04:00
fix: [UEPR-177] prevent multiple submission of feedback answers
This commit is contained in:
commit
8a73da07b1
15 changed files with 115 additions and 86 deletions
9
.github/workflows/ci-cd.yml
vendored
9
.github/workflows/ci-cd.yml
vendored
|
@ -84,11 +84,12 @@ jobs:
|
|||
ONBOARDING_TESTING_ENDING_DATE: "${{ vars.ONBOARDING_TESTING_ENDING_DATE }}"
|
||||
QUALITATIVE_FEEDBACK_ACTIVE: "${{ vars.QUALITATIVE_FEEDBACK_ACTIVE }}"
|
||||
QUALITATIVE_FEEDBACK_STARTING_DATE: "${{ vars.QUALITATIVE_FEEDBACK_STARTING_DATE }}"
|
||||
QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_RATE: "${{ vars.QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_RATE }}"
|
||||
QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_RATE: "${{ vars.QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_RATE }}"
|
||||
QUALITATIVE_FEEDBACK_DEBUGGING_USER_RATE: "${{ vars.QUALITATIVE_FEEDBACK_DEBUGGING_USER_RATE }}"
|
||||
QUALITATIVE_FEEDBACK_TUTORIALS_USER_RATE: "${{ vars.QUALITATIVE_FEEDBACK_TUTORIALS_USER_RATE }}"
|
||||
QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_FREQUENCY: "${{ vars.QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_FREQUENCY }}"
|
||||
QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_FREQUENCY: "${{ vars.QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_FREQUENCY }}"
|
||||
QUALITATIVE_FEEDBACK_DEBUGGING_USER_FREQUENCY: "${{ vars.QUALITATIVE_FEEDBACK_DEBUGGING_USER_FREQUENCY }}"
|
||||
QUALITATIVE_FEEDBACK_TUTORIALS_USER_FREQUENCY: "${{ vars.QUALITATIVE_FEEDBACK_TUTORIALS_USER_FREQUENCY }}"
|
||||
QUALITATIVE_FEEDBACK_ENDING_DATE: "${{ vars.QUALITATIVE_FEEDBACK_ENDING_DATE }}"
|
||||
IDEAS_GENERATOR_SOURCE: "${{ vars.IDEAS_GENERATOR_SOURCE }}"
|
||||
|
||||
# used by src/template-config.js
|
||||
GTM_ID: ${{ secrets.GTM_ID }}
|
||||
|
|
|
@ -59,9 +59,11 @@ const Carousel = props => {
|
|||
href = `/studios/${item.id}/`;
|
||||
break;
|
||||
case 'project':
|
||||
href = props.fromStarterProjectsPage ?
|
||||
`/projects/${item.id}?fromStarterProjectsPage=${props.fromStarterProjectsPage}` :
|
||||
`/projects/${item.id}/`;
|
||||
href = `/projects/${item.id}${
|
||||
props.fromStarterProjectsPage ?
|
||||
'?fromStarterProjectsPage=true' :
|
||||
''
|
||||
}`;
|
||||
break;
|
||||
default:
|
||||
href = `/${item.type}/${item.id}/`;
|
||||
|
|
|
@ -4,8 +4,8 @@ import {hideQualitativeFeedback} from '../../../redux/qualitative-feedback.js';
|
|||
import {
|
||||
QUALITATIVE_FEEDBACK_DATA,
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID
|
||||
} from './qualitative_feedback_data.js';
|
||||
import {QualitativeFeedback} from './qualitative_feedback.jsx';
|
||||
} from './qualitative-feedback-data.js';
|
||||
import {QualitativeFeedback} from './qualitative-feedback.jsx';
|
||||
import {connect} from 'react-redux';
|
||||
import {triggerAnalyticsEvent} from '../../../lib/google-analytics-utils.js';
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import {hideQualitativeFeedback} from '../../../redux/qualitative-feedback.js';
|
|||
import {
|
||||
QUALITATIVE_FEEDBACK_DATA,
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID
|
||||
} from './qualitative_feedback_data.js';
|
||||
import {QualitativeFeedback} from './qualitative_feedback.jsx';
|
||||
} from './qualitative-feedback-data.js';
|
||||
import {QualitativeFeedback} from './qualitative-feedback.jsx';
|
||||
import {connect} from 'react-redux';
|
||||
import {triggerAnalyticsEvent} from '../../../lib/google-analytics-utils.js';
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {Formik} from 'formik';
|
|||
import classNames from 'classnames';
|
||||
import FormikRadioButton from '../../formik-forms/formik-radio-button.jsx';
|
||||
|
||||
import './qualitative_feedback.scss';
|
||||
import './qualitative-feedback.scss';
|
||||
|
||||
const FeedbackOption = ({
|
||||
id,
|
||||
|
@ -45,7 +45,12 @@ FeedbackOption.propTypes = {
|
|||
value: PropTypes.string
|
||||
};
|
||||
|
||||
export const QualitativeFeedback = ({feedbackData, hideFeedback, isOpen, sendGAEvent}) => {
|
||||
export const QualitativeFeedback = ({
|
||||
feedbackData,
|
||||
hideFeedback,
|
||||
isOpen,
|
||||
sendGAEvent
|
||||
}) => {
|
||||
const [displayModal, setDisplayModal] = useState(false);
|
||||
const [_, setFilledFeedback] = useLocalStorage(
|
||||
feedbackData.questionId,
|
||||
|
@ -54,23 +59,37 @@ export const QualitativeFeedback = ({feedbackData, hideFeedback, isOpen, sendGAE
|
|||
const intl = useIntl();
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
sendGAEvent('closed');
|
||||
if (displayModal) {
|
||||
setDisplayModal(false);
|
||||
sendGAEvent('closed');
|
||||
}
|
||||
|
||||
setFilledFeedback(true);
|
||||
hideFeedback();
|
||||
setDisplayModal(false);
|
||||
}, [setFilledFeedback, hideFeedback, setDisplayModal]);
|
||||
}, [
|
||||
displayModal,
|
||||
setFilledFeedback,
|
||||
hideFeedback,
|
||||
setDisplayModal,
|
||||
sendGAEvent
|
||||
]);
|
||||
|
||||
// TBD: add logic for sending events to GA
|
||||
const onSubmit = useCallback(
|
||||
formData => {
|
||||
if (formData.feedback) {
|
||||
if (formData.feedback && displayModal) {
|
||||
setDisplayModal(false);
|
||||
sendGAEvent(formData.feedback);
|
||||
setFilledFeedback(true);
|
||||
hideFeedback();
|
||||
setDisplayModal(false);
|
||||
}
|
||||
},
|
||||
[hideFeedback, setDisplayModal, setFilledFeedback]
|
||||
[
|
||||
displayModal,
|
||||
hideFeedback,
|
||||
setDisplayModal,
|
||||
setFilledFeedback,
|
||||
sendGAEvent
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -78,7 +97,7 @@ export const QualitativeFeedback = ({feedbackData, hideFeedback, isOpen, sendGAE
|
|||
const timer = setTimeout(() => {
|
||||
setDisplayModal(true);
|
||||
}, 5000);
|
||||
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
@ -102,33 +121,31 @@ export const QualitativeFeedback = ({feedbackData, hideFeedback, isOpen, sendGAE
|
|||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({handleSubmit, setFieldValue, values}) =>
|
||||
(
|
||||
<form
|
||||
className="feedback-form"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="feedback-question">
|
||||
<FormattedMessage id={feedbackData.questionId} />
|
||||
</div>
|
||||
<div className="feedback-options">
|
||||
{feedbackData.options.map(option => (
|
||||
<FeedbackOption
|
||||
key={option.value}
|
||||
id={option.label}
|
||||
label={intl.formatMessage({id: option.label})}
|
||||
selectedValue={values.feedback}
|
||||
value={option.value}
|
||||
onSetFieldValue={setFieldValue}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<Button className="feedback-submit">
|
||||
<FormattedMessage id="feedback.submit" />
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
{({handleSubmit, setFieldValue, values}) => (
|
||||
<form
|
||||
className="feedback-form"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="feedback-question">
|
||||
<FormattedMessage id={feedbackData.questionId} />
|
||||
</div>
|
||||
<div className="feedback-options">
|
||||
{feedbackData.options.map(option => (
|
||||
<FeedbackOption
|
||||
key={option.value}
|
||||
id={option.label}
|
||||
label={intl.formatMessage({id: option.label})}
|
||||
selectedValue={values.feedback}
|
||||
value={option.value}
|
||||
onSetFieldValue={setFieldValue}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<Button className="feedback-submit">
|
||||
<FormattedMessage id="feedback.submit" />
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalInnerContent>
|
||||
</Modal>
|
|
@ -4,8 +4,8 @@ import {hideQualitativeFeedback} from '../../../redux/qualitative-feedback.js';
|
|||
import {
|
||||
QUALITATIVE_FEEDBACK_DATA,
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID
|
||||
} from './qualitative_feedback_data.js';
|
||||
import {QualitativeFeedback} from './qualitative_feedback.jsx';
|
||||
} from './qualitative-feedback-data.js';
|
||||
import {QualitativeFeedback} from './qualitative-feedback.jsx';
|
||||
import {connect} from 'react-redux';
|
||||
import {triggerAnalyticsEvent} from '../../../lib/google-analytics-utils.js';
|
||||
|
||||
|
@ -23,7 +23,7 @@ const StarterProjectsFeedback = ({hideFeedback, isOpen, projectName}) => {
|
|||
projectName: projectName,
|
||||
feedbackResponse: data
|
||||
}),
|
||||
[]
|
||||
[projectName]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -4,8 +4,8 @@ import {hideQualitativeFeedback} from '../../../redux/qualitative-feedback.js';
|
|||
import {
|
||||
QUALITATIVE_FEEDBACK_DATA,
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID
|
||||
} from './qualitative_feedback_data.js';
|
||||
import {QualitativeFeedback} from './qualitative_feedback.jsx';
|
||||
} from './qualitative-feedback-data.js';
|
||||
import {QualitativeFeedback} from './qualitative-feedback.jsx';
|
||||
import {connect} from 'react-redux';
|
||||
import {triggerAnalyticsEvent} from '../../../lib/google-analytics-utils.js';
|
||||
|
||||
|
|
|
@ -16,10 +16,13 @@ const isCurrentDayInRange = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const hasGivenFeedback = feedbackQuestionId =>
|
||||
const notRespondedToFeedback = feedbackQuestionId =>
|
||||
localStorageAvailable && localStorage.getItem(feedbackQuestionId) !== 'true';
|
||||
|
||||
const canShowFeedbackWidget = feedbackUserRate => {
|
||||
// randomNumber will be in the range [0, feedbackUserRate - 1]
|
||||
// and we are assering against one number from the range
|
||||
// this way we simulate picking one from n users
|
||||
const randomNum = Math.floor(
|
||||
Math.random() * feedbackUserRate
|
||||
);
|
||||
|
@ -35,11 +38,11 @@ const isUserEligibleForFeedback = (user, permissions, feedbackQuestionId, feedba
|
|||
JSON.parse(process.env.QUALITATIVE_FEEDBACK_ACTIVE) &&
|
||||
canShowFeedbackWidget(feedbackUserRate) &&
|
||||
isCurrentDayInRange() &&
|
||||
hasGivenFeedback(feedbackQuestionId)
|
||||
notRespondedToFeedback(feedbackQuestionId)
|
||||
);
|
||||
|
||||
const isFeedbackDisplayed = feedbacksDisplayed =>
|
||||
!Object.entries(feedbacksDisplayed).some(feedback =>
|
||||
Object.entries(feedbacksDisplayed).some(feedback =>
|
||||
feedback.value
|
||||
);
|
||||
|
||||
|
@ -51,7 +54,7 @@ export const shouldDisplayFeedbackWidget = (
|
|||
feedbacksDisplayed
|
||||
) => (
|
||||
isUserEligibleForFeedback(user, permissions, feedbackQuestionId, feedbackUserRate) &&
|
||||
isFeedbackDisplayed(feedbacksDisplayed)
|
||||
!isFeedbackDisplayed(feedbacksDisplayed)
|
||||
);
|
||||
|
||||
export const sendUserPropertiesForFeedback = (user, permissions, shouldDisplayFeedback) => {
|
||||
|
|
|
@ -17,7 +17,7 @@ export const isDateInRange = (date, startDate, endDate) => {
|
|||
|
||||
export const calculateAgeGroup = (birthYear, birthMonth) => {
|
||||
if (!birthMonth || !birthYear) {
|
||||
return '[unset]';
|
||||
return '[no-data]';
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {QUALITATIVE_FEEDBACK_QUESTION_ID} from '../components/modal/feedback/qualitative_feedback_data';
|
||||
import {QUALITATIVE_FEEDBACK_QUESTION_ID} from '../components/modal/feedback/qualitative-feedback-data';
|
||||
|
||||
const initialState = {
|
||||
[QUALITATIVE_FEEDBACK_QUESTION_ID.ideasGenerator]: false,
|
||||
|
|
|
@ -29,7 +29,7 @@ const {
|
|||
const {useRef} = require('react');
|
||||
const {
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID
|
||||
} = require('../../components/modal/feedback/qualitative_feedback_data.js');
|
||||
} = require('../../components/modal/feedback/qualitative-feedback-data.js');
|
||||
const {shouldDisplayFeedbackWidget, sendUserPropertiesForFeedback} = require('../../lib/feedback.js');
|
||||
|
||||
require('./ideas.scss');
|
||||
|
@ -165,25 +165,30 @@ const Ideas = ({
|
|||
}
|
||||
};
|
||||
|
||||
const shoulDisplayFeedback = shouldDisplayFeedbackWidget(
|
||||
const shouldDisplayFeedback = shouldDisplayFeedbackWidget(
|
||||
user,
|
||||
permissions,
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID.ideasGenerator,
|
||||
process.env.QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_RATE,
|
||||
process.env.QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_FREQUENCY,
|
||||
feedback
|
||||
);
|
||||
|
||||
if (iframe && shoulDisplayFeedback) {
|
||||
if (iframe && shouldDisplayFeedback) {
|
||||
sendUserPropertiesForFeedback(
|
||||
user,
|
||||
permissions,
|
||||
shoulDisplayFeedback
|
||||
shouldDisplayFeedback
|
||||
);
|
||||
iframe.addEventListener('load', onIframeLoad);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (iframe) {
|
||||
iframe.contentWindow.document
|
||||
.querySelectorAll('[class*="green-flag"]')
|
||||
.forEach(element =>
|
||||
element.removeEventListener('click', onGreenFlagClick)
|
||||
);
|
||||
iframe.removeEventListener('load', onIframeLoad);
|
||||
}
|
||||
};
|
||||
|
@ -197,7 +202,7 @@ const Ideas = ({
|
|||
<div className="banner-wrapper">
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={`http://localhost:8333/projects/9999923/embed`}
|
||||
src={`${process.env.IDEAS_GENERATOR_SOURCE}/embed`}
|
||||
width="485"
|
||||
height="402"
|
||||
allowfullscreen
|
||||
|
|
|
@ -47,7 +47,7 @@ const TutorialsHighlight = require('../../components/journeys/tutorials-highligh
|
|||
const {sendUserPropertiesForOnboarding, shouldDisplayOnboarding} = require('../../lib/onboarding.js');
|
||||
const {triggerAnalyticsEvent} = require('../../lib/google-analytics-utils.js');
|
||||
const {StarterProjectsFeedback} = require('../../components/modal/feedback/starter-projects-feedback.jsx');
|
||||
const {QUALITATIVE_FEEDBACK_QUESTION_ID} = require('../../components/modal/feedback/qualitative_feedback_data.js');
|
||||
const {QUALITATIVE_FEEDBACK_QUESTION_ID} = require('../../components/modal/feedback/qualitative-feedback-data.js');
|
||||
const {shouldDisplayFeedbackWidget, sendUserPropertiesForFeedback} = require('../../lib/feedback.js');
|
||||
const {displayQualitativeFeedback} = require('../../redux/qualitative-feedback.js');
|
||||
const {DebuggingFeedback} = require('../../components/modal/feedback/debugging-feedback.jsx');
|
||||
|
@ -73,7 +73,7 @@ const IntlGUIWithProjectHandler = ({...props}) => {
|
|||
}, [props.projectId, prevProjectId, props.user, props.permissions]);
|
||||
|
||||
const displayGuiFeedback = useCallback((feedbackQuestionId, feedbackUserRate) => {
|
||||
const shoulDisplayFeedback = shouldDisplayFeedbackWidget(
|
||||
const shouldDisplayFeedback = shouldDisplayFeedbackWidget(
|
||||
props.user,
|
||||
props.permissions,
|
||||
feedbackQuestionId,
|
||||
|
@ -81,11 +81,11 @@ const IntlGUIWithProjectHandler = ({...props}) => {
|
|||
props.feedback
|
||||
);
|
||||
|
||||
if (shoulDisplayFeedback) {
|
||||
if (shouldDisplayFeedback) {
|
||||
sendUserPropertiesForFeedback(
|
||||
props.user,
|
||||
props.permissions,
|
||||
shoulDisplayFeedback
|
||||
shouldDisplayFeedback
|
||||
);
|
||||
props.displayFeedback(feedbackQuestionId);
|
||||
}
|
||||
|
@ -95,14 +95,14 @@ const IntlGUIWithProjectHandler = ({...props}) => {
|
|||
<>
|
||||
<IntlGUI
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
displayDebugFeedback={() => displayGuiFeedback(
|
||||
onDebugModalClose={() => displayGuiFeedback(
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID.debugging,
|
||||
process.env.QUALITATIVE_FEEDBACK_DEBUGGING_USER_RATE
|
||||
process.env.QUALITATIVE_FEEDBACK_DEBUGGING_USER_FREQUENCY
|
||||
)}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
displayTutorialsFeedback={() => displayGuiFeedback(
|
||||
onTutorialSelect={() => displayGuiFeedback(
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID.tutorials,
|
||||
process.env.QUALITATIVE_FEEDBACK_TUTORIALS_USER_RATE
|
||||
process.env.QUALITATIVE_FEEDBACK_TUTORIALS_USER_FREQUENCY
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
@ -305,18 +305,18 @@ class Preview extends React.Component {
|
|||
sendUserPropertiesForOnboarding(this.props.user, this.props.permissions);
|
||||
|
||||
const fromStarterProjectsPage = queryString.parse(location.search).fromStarterProjectsPage === 'true';
|
||||
const shoulDisplayFeedback = shouldDisplayFeedbackWidget(
|
||||
const shouldDisplayFeedback = shouldDisplayFeedbackWidget(
|
||||
this.props.user,
|
||||
this.props.permissions,
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID.starterProjects,
|
||||
process.env.QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_RATE,
|
||||
process.env.QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_FREQUENCY,
|
||||
this.props.feedback
|
||||
);
|
||||
if (fromStarterProjectsPage && shoulDisplayFeedback) {
|
||||
if (fromStarterProjectsPage && shouldDisplayFeedback) {
|
||||
sendUserPropertiesForFeedback(
|
||||
this.props.user,
|
||||
this.props.permissions,
|
||||
shoulDisplayFeedback
|
||||
shouldDisplayFeedback
|
||||
);
|
||||
this.props.displayFeedback(
|
||||
QUALITATIVE_FEEDBACK_QUESTION_ID.starterProjects
|
||||
|
|
|
@ -308,19 +308,20 @@ module.exports = {
|
|||
process.env.QUALITATIVE_FEEDBACK_STARTING_DATE || '2024-01-20'
|
||||
}"`,
|
||||
'process.env.QUALITATIVE_FEEDBACK_ENDING_DATE': `"${
|
||||
process.env.QUALITATIVE_FEEDBACK_ENDING_DATE || '2030-11-20'
|
||||
process.env.QUALITATIVE_FEEDBACK_ENDING_DATE || '2024-11-20'
|
||||
}"`,
|
||||
'process.env.QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_RATE': `"${
|
||||
process.env.QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_RATE || 2
|
||||
// Given user frequency X, show qualitative feedback to 1 in X users
|
||||
'process.env.QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_FREQUENCY': `"${
|
||||
process.env.QUALITATIVE_FEEDBACK_IDEAS_GENERATOR_USER_FREQUENCY || 2
|
||||
}"`,
|
||||
'process.env.QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_RATE': `"${
|
||||
process.env.QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_RATE || 2
|
||||
'process.env.QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_FREQUENCY': `"${
|
||||
process.env.QUALITATIVE_FEEDBACK_STARTER_PROJECTS_USER_FREQUENCY || 2
|
||||
}"`,
|
||||
'process.env.QUALITATIVE_FEEDBACK_DEBUGGING_USER_RATE': `"${
|
||||
process.env.QUALITATIVE_FEEDBACK_DEBUGGING_USER_RATE || 2
|
||||
'process.env.QUALITATIVE_FEEDBACK_DEBUGGING_USER_FREQUENCY': `"${
|
||||
process.env.QUALITATIVE_FEEDBACK_DEBUGGING_USER_FREQUENCY || 2
|
||||
}"`,
|
||||
'process.env.QUALITATIVE_FEEDBACK_TUTORIALS_USER_RATE': `"${
|
||||
process.env.QUALITATIVE_FEEDBACK_TUTORIALS_USER_RATE || 2
|
||||
'process.env.QUALITATIVE_FEEDBACK_TUTORIALS_USER_FREQUENCY': `"${
|
||||
process.env.QUALITATIVE_FEEDBACK_TUTORIALS_USER_FREQUENCY || 2
|
||||
}"`,
|
||||
'process.env.IDEAS_GENERATOR_SOURCE': `"${
|
||||
process.env.IDEAS_GENERATOR_SOURCE || 'https://scratch.mit.edu/projects/1108790117'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue