Merge pull request #17899 from overleaf/revert-17700-ac-tear-down-compile-timeout-tests

Revert "[web] Remove split-tests `compile-backend-class*` and `compile-timeout-20s*`"

GitOrigin-RevId: d5070ced06adbd435e782a44b7ef767e395bd6a0
This commit is contained in:
Antoine Clausse 2024-04-12 11:50:48 +02:00 committed by Copybot
parent 2dd10c7fee
commit 491bc2628d
27 changed files with 980 additions and 302 deletions

View file

@ -105,6 +105,15 @@ module.exports = CompileController = {
stopOnFirstError, stopOnFirstError,
} }
// temporary override to force the new compile timeout
const forceNewCompileTimeout = req.query.force_new_compile_timeout
if (
forceNewCompileTimeout === 'active' ||
forceNewCompileTimeout === 'changing'
) {
options.forceNewCompileTimeout = forceNewCompileTimeout
}
if (req.body.rootDoc_id) { if (req.body.rootDoc_id) {
options.rootDoc_id = req.body.rootDoc_id options.rootDoc_id = req.body.rootDoc_id
} else if ( } else if (

View file

@ -8,9 +8,18 @@ const UserGetter = require('../User/UserGetter')
const ClsiManager = require('./ClsiManager') const ClsiManager = require('./ClsiManager')
const Metrics = require('@overleaf/metrics') const Metrics = require('@overleaf/metrics')
const { RateLimiter } = require('../../infrastructure/RateLimiter') const { RateLimiter } = require('../../infrastructure/RateLimiter')
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
const UserAnalyticsIdCache = require('../Analytics/UserAnalyticsIdCache') const UserAnalyticsIdCache = require('../Analytics/UserAnalyticsIdCache')
const NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF = new Date('2023-09-18T11:00:00.000Z')
const NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF_DEFAULT_BASELINE = new Date(
'2023-10-10T11:00:00.000Z'
)
module.exports = CompileManager = { module.exports = CompileManager = {
NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF,
NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF_DEFAULT_BASELINE,
compile(projectId, userId, options = {}, _callback) { compile(projectId, userId, options = {}, _callback) {
const timer = new Metrics.Timer('editor.compile') const timer = new Metrics.Timer('editor.compile')
const callback = function (...args) { const callback = function (...args) {
@ -53,6 +62,16 @@ module.exports = CompileManager = {
const value = limits[key] const value = limits[key]
options[key] = value options[key] = value
} }
if (options.timeout !== 20) {
// temporary override to force the new compile timeout
if (options.forceNewCompileTimeout === 'active') {
options.timeout = 20
} else if (
options.forceNewCompileTimeout === 'changing'
) {
options.timeout = 60
}
}
// Put a lower limit on autocompiles for free users, based on compileGroup // Put a lower limit on autocompiles for free users, based on compileGroup
CompileManager._checkCompileGroupAutoCompileLimit( CompileManager._checkCompileGroupAutoCompileLimit(
options.isAutoCompile, options.isAutoCompile,
@ -165,25 +184,78 @@ module.exports = CompileManager = {
if (err) { if (err) {
return callback(err) return callback(err)
} }
const compileGroup =
ownerFeatures.compileGroup ||
Settings.defaultFeatures.compileGroup
const compileTimeout =
ownerFeatures.compileTimeout ||
Settings.defaultFeatures.compileTimeout
const limits = { const limits = {
timeout: timeout:
// temporary override until users' compileTimeout is migrated ownerFeatures.compileTimeout ||
compileGroup === 'standard' && compileTimeout <= 60 Settings.defaultFeatures.compileTimeout,
? 20 compileGroup:
: compileTimeout, ownerFeatures.compileGroup ||
compileGroup, Settings.defaultFeatures.compileGroup,
compileBackendClass:
compileGroup === 'standard' ? 'n2d' : 'c2d',
ownerAnalyticsId: analyticsId, ownerAnalyticsId: analyticsId,
} }
CompileManager._getCompileBackendClassDetails(
owner,
limits.compileGroup,
(
err,
{ compileBackendClass, showFasterCompilesFeedbackUI }
) => {
if (err) return callback(err)
limits.compileBackendClass = compileBackendClass
limits.showFasterCompilesFeedbackUI =
showFasterCompilesFeedbackUI
if (compileBackendClass === 'n2d' && limits.timeout <= 60) {
// project owners with faster compiles but with <= 60 compile timeout (default)
// will have a 20s compile timeout
// The compile-timeout-20s split test exists to enable a gradual rollout
SplitTestHandler.getAssignmentForMongoUser(
owner,
'compile-timeout-20s',
(err, assignment) => {
if (err) return callback(err)
// users who were on the 'default' servers at time of original rollout
// will have a later cutoff date for the 20s timeout in the next phase
// we check the backend class at version 8 (baseline)
const backendClassHistory =
owner.splitTests?.['compile-backend-class-n2d'] ||
[]
const backendClassBaselineVariant =
backendClassHistory.find(version => {
return version.versionNumber === 8
})?.variantName
const timeoutEnforcedCutoff =
backendClassBaselineVariant === 'default'
? NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF_DEFAULT_BASELINE
: NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF
if (assignment?.variant === '20s') {
if (owner.signUpDate > timeoutEnforcedCutoff) {
limits.timeout = 20
callback(null, limits) callback(null, limits)
} else {
SplitTestHandler.getAssignmentForMongoUser(
owner,
'compile-timeout-20s-existing-users',
(err, assignmentExistingUsers) => {
if (err) return callback(err)
if (
assignmentExistingUsers?.variant === '20s'
) {
limits.timeout = 20
}
callback(null, limits)
}
)
}
} else {
callback(null, limits)
}
}
)
} else {
callback(null, limits)
}
}
)
} }
) )
} }
@ -250,6 +322,39 @@ module.exports = CompileManager = {
}) })
}, },
_getCompileBackendClassDetails(owner, compileGroup, callback) {
const { defaultBackendClass } = Settings.apis.clsi
if (compileGroup === 'standard') {
return SplitTestHandler.getAssignmentForMongoUser(
owner,
'compile-backend-class-n2d',
(err, assignment) => {
if (err) return callback(err, {})
const { variant } = assignment
callback(null, {
compileBackendClass:
variant === 'default' ? defaultBackendClass : variant,
showFasterCompilesFeedbackUI: false,
})
}
)
}
SplitTestHandler.getAssignmentForMongoUser(
owner,
'compile-backend-class',
(err, assignment) => {
if (err) return callback(err, {})
const { analytics, variant } = assignment
const activeForUser = analytics?.segmentation?.splitTest != null
callback(null, {
compileBackendClass:
variant === 'default' ? defaultBackendClass : variant,
showFasterCompilesFeedbackUI: activeForUser,
})
}
)
},
wordCount(projectId, userId, file, clsiserverid, callback) { wordCount(projectId, userId, file, clsiserverid, callback) {
CompileManager.getProjectCompileLimits(projectId, function (error, limits) { CompileManager.getProjectCompileLimits(projectId, function (error, limits) {
if (error) { if (error) {

View file

@ -13,6 +13,12 @@ const Errors = require('../Errors/Errors')
const DocstoreManager = require('../Docstore/DocstoreManager') const DocstoreManager = require('../Docstore/DocstoreManager')
const logger = require('@overleaf/logger') const logger = require('@overleaf/logger')
const { expressify } = require('@overleaf/promise-utils') const { expressify } = require('@overleaf/promise-utils')
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
const {
NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF,
NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF_DEFAULT_BASELINE,
} = require('../Compile/CompileManager')
const UserGetter = require('../User/UserGetter')
module.exports = { module.exports = {
joinProject: expressify(joinProject), joinProject: expressify(joinProject),
@ -66,6 +72,58 @@ async function joinProject(req, res, next) {
if (!project) { if (!project) {
return res.sendStatus(403) return res.sendStatus(403)
} }
// Compile timeout 20s test
if (project.features?.compileTimeout <= 60) {
const compileAssignment =
await SplitTestHandler.promises.getAssignmentForUser(
project.owner._id,
'compile-backend-class-n2d'
)
if (compileAssignment?.variant === 'n2d') {
const timeoutAssignment =
await SplitTestHandler.promises.getAssignmentForUser(
project.owner._id,
'compile-timeout-20s'
)
if (timeoutAssignment?.variant === '20s') {
// users who were on the 'default' servers at time of original rollout
// will have a later cutoff date for the 20s timeout in the next phase
// we check the backend class at version 8 (baseline)
const owner = await UserGetter.promises.getUser(project.owner._id, {
_id: 1,
'splitTests.compile-backend-class-n2d': 1,
})
const backendClassHistory =
owner.splitTests?.['compile-backend-class-n2d'] || []
const backendClassBaselineVariant = backendClassHistory.find(
version => {
return version.versionNumber === 8
}
)?.variantName
const timeoutEnforcedCutoff =
backendClassBaselineVariant === 'default'
? NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF_DEFAULT_BASELINE
: NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF
if (project.owner.signUpDate > timeoutEnforcedCutoff) {
// New users will see a 10s warning and compile fail at 20s
project.showNewCompileTimeoutUI = 'active'
} else {
const existingUserTimeoutAssignment =
await SplitTestHandler.promises.getAssignmentForUser(
project.owner._id,
'compile-timeout-20s-existing-users'
)
if (existingUserTimeoutAssignment?.variant === '20s') {
// Older users in treatment see 10s warning and compile fail at 20s
project.showNewCompileTimeoutUI = 'active'
} else {
// Older users in control aren't limited to 20s, but will see a notice of upcoming changes if compile >20s
project.showNewCompileTimeoutUI = 'changing'
}
}
}
}
}
// Hide sensitive data if the user is restricted // Hide sensitive data if the user is restricted
if (isRestrictedUser) { if (isRestrictedUser) {
project.owner = { _id: project.owner._id } project.owner = { _id: project.owner._id }

View file

@ -272,6 +272,7 @@ module.exports = {
url: `http://${process.env.CLSI_HOST || 'localhost'}:3013`, url: `http://${process.env.CLSI_HOST || 'localhost'}:3013`,
// url: "http://#{process.env['CLSI_LB_HOST']}:3014" // url: "http://#{process.env['CLSI_LB_HOST']}:3014"
backendGroupName: undefined, backendGroupName: undefined,
defaultBackendClass: process.env.CLSI_DEFAULT_BACKEND_CLASS || 'e2',
submissionBackendClass: submissionBackendClass:
process.env.CLSI_SUBMISSION_BACKEND_CLASS || 'n2d', process.env.CLSI_SUBMISSION_BACKEND_CLASS || 'n2d',
}, },

View file

@ -70,9 +70,11 @@
"also": "", "also": "",
"an_email_has_already_been_sent_to": "", "an_email_has_already_been_sent_to": "",
"an_error_occurred_when_verifying_the_coupon_code": "", "an_error_occurred_when_verifying_the_coupon_code": "",
"and_you_can_upgrade_for_plenty_more_compile_time": "",
"anonymous": "", "anonymous": "",
"anyone_with_link_can_edit": "", "anyone_with_link_can_edit": "",
"anyone_with_link_can_view": "", "anyone_with_link_can_view": "",
"approaching_compile_timeout_limit_upgrade_for_more_compile_time": "",
"archive": "", "archive": "",
"archive_projects": "", "archive_projects": "",
"archived": "", "archived": "",
@ -84,6 +86,7 @@
"are_you_sure": "", "are_you_sure": "",
"ask_proj_owner_to_unlink_from_current_github": "", "ask_proj_owner_to_unlink_from_current_github": "",
"ask_proj_owner_to_upgrade_for_full_history": "", "ask_proj_owner_to_upgrade_for_full_history": "",
"ask_proj_owner_to_upgrade_for_longer_compiles": "",
"ask_proj_owner_to_upgrade_for_references_search": "", "ask_proj_owner_to_upgrade_for_references_search": "",
"ask_repo_owner_to_reconnect": "", "ask_repo_owner_to_reconnect": "",
"ask_repo_owner_to_renew_overleaf_subscription": "", "ask_repo_owner_to_renew_overleaf_subscription": "",
@ -189,6 +192,7 @@
"compile_mode": "", "compile_mode": "",
"compile_terminated_by_user": "", "compile_terminated_by_user": "",
"compiler": "", "compiler": "",
"compiles_on_our_free_plan_are_now_on_faster_servers": "",
"compiling": "", "compiling": "",
"configure_sso": "", "configure_sso": "",
"confirm": "", "confirm": "",
@ -281,6 +285,7 @@
"disabling": "", "disabling": "",
"disconnected": "", "disconnected": "",
"discount_of": "", "discount_of": "",
"dismiss": "",
"dismiss_error_popup": "", "dismiss_error_popup": "",
"display_deleted_user": "", "display_deleted_user": "",
"do_you_want_to_change_your_primary_email_address_to": "", "do_you_want_to_change_your_primary_email_address_to": "",
@ -381,6 +386,11 @@
"failed_to_send_managed_user_invite_to_email": "", "failed_to_send_managed_user_invite_to_email": "",
"failed_to_send_sso_link_invite_to_email": "", "failed_to_send_sso_link_invite_to_email": "",
"fast": "", "fast": "",
"faster_compiles_feedback_question": "",
"faster_compiles_feedback_seems_faster": "",
"faster_compiles_feedback_seems_same": "",
"faster_compiles_feedback_seems_slower": "",
"faster_compiles_feedback_thanks": "",
"file_action_created": "", "file_action_created": "",
"file_action_deleted": "", "file_action_deleted": "",
"file_action_edited": "", "file_action_edited": "",
@ -416,6 +426,7 @@
"found_matching_deleted_users": "", "found_matching_deleted_users": "",
"free_7_day_trial_billed_annually": "", "free_7_day_trial_billed_annually": "",
"free_7_day_trial_billed_monthly": "", "free_7_day_trial_billed_monthly": "",
"free_accounts_have_timeout_upgrade_to_increase": "",
"free_plan_label": "", "free_plan_label": "",
"free_plan_tooltip": "", "free_plan_tooltip": "",
"from_another_project": "", "from_another_project": "",
@ -907,6 +918,7 @@
"please_wait": "", "please_wait": "",
"plus_additional_collaborators_document_history_track_changes_and_more": "", "plus_additional_collaborators_document_history_track_changes_and_more": "",
"plus_more": "", "plus_more": "",
"plus_upgraded_accounts_receive": "",
"postal_code": "", "postal_code": "",
"premium_feature": "", "premium_feature": "",
"premium_plan_label": "", "premium_plan_label": "",
@ -968,6 +980,7 @@
"react_history_tutorial_content": "", "react_history_tutorial_content": "",
"react_history_tutorial_title": "", "react_history_tutorial_title": "",
"reactivate_subscription": "", "reactivate_subscription": "",
"read_more_about_fix_prevent_timeout": "",
"read_more_about_free_compile_timeouts_servers": "", "read_more_about_free_compile_timeouts_servers": "",
"read_only": "", "read_only": "",
"read_only_token": "", "read_only_token": "",
@ -1279,6 +1292,7 @@
"tc_switch_guests_tip": "", "tc_switch_guests_tip": "",
"tc_switch_user_tip": "", "tc_switch_user_tip": "",
"tell_the_project_owner_and_ask_them_to_upgrade": "", "tell_the_project_owner_and_ask_them_to_upgrade": "",
"tell_the_project_owner_to_upgrade_plan_for_more_compile_time": "",
"template_approved_by_publisher": "", "template_approved_by_publisher": "",
"template_description": "", "template_description": "",
"template_title_taken_from_project_title": "", "template_title_taken_from_project_title": "",
@ -1308,6 +1322,7 @@
"this_could_be_because_we_cant_support_some_elements_of_the_table": "", "this_could_be_because_we_cant_support_some_elements_of_the_table": "",
"this_field_is_required": "", "this_field_is_required": "",
"this_grants_access_to_features_2": "", "this_grants_access_to_features_2": "",
"this_project_compiled_but_soon_might_not": "",
"this_project_exceeded_compile_timeout_limit_on_free_plan": "", "this_project_exceeded_compile_timeout_limit_on_free_plan": "",
"this_project_is_public": "", "this_project_is_public": "",
"this_project_is_public_read_only": "", "this_project_is_public_read_only": "",
@ -1449,6 +1464,8 @@
"upgrade": "", "upgrade": "",
"upgrade_cc_btn": "", "upgrade_cc_btn": "",
"upgrade_for_12x_more_compile_time": "", "upgrade_for_12x_more_compile_time": "",
"upgrade_for_longer_compiles": "",
"upgrade_for_plenty_more_compile_time": "",
"upgrade_now": "", "upgrade_now": "",
"upgrade_to_get_feature": "", "upgrade_to_get_feature": "",
"upgrade_to_track_changes": "", "upgrade_to_track_changes": "",
@ -1502,6 +1519,8 @@
"we_sent_new_code": "", "we_sent_new_code": "",
"wed_love_you_to_stay": "", "wed_love_you_to_stay": "",
"welcome_to_sl": "", "welcome_to_sl": "",
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_this_project": "",
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_your_project": "",
"were_performing_maintenance": "", "were_performing_maintenance": "",
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "", "weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "",
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "", "weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "",
@ -1552,6 +1571,7 @@
"you_have_been_invited_to_transfer_management_of_your_account": "", "you_have_been_invited_to_transfer_management_of_your_account": "",
"you_have_been_invited_to_transfer_management_of_your_account_to": "", "you_have_been_invited_to_transfer_management_of_your_account_to": "",
"you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "", "you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "",
"you_may_be_able_to_fix_issues_to_speed_up_the_compile": "",
"you_need_to_configure_your_sso_settings": "", "you_need_to_configure_your_sso_settings": "",
"you_will_be_able_to_reassign_subscription": "", "you_will_be_able_to_reassign_subscription": "",
"youll_get_best_results_in_visual_but_can_be_used_in_source": "", "youll_get_best_results_in_visual_but_can_be_used_in_source": "",
@ -1571,6 +1591,7 @@
"your_new_plan": "", "your_new_plan": "",
"your_plan": "", "your_plan": "",
"your_plan_is_changing_at_term_end": "", "your_plan_is_changing_at_term_end": "",
"your_project_compiled_but_soon_might_not": "",
"your_project_exceeded_compile_timeout_limit_on_free_plan": "", "your_project_exceeded_compile_timeout_limit_on_free_plan": "",
"your_project_near_compile_timeout_limit": "", "your_project_near_compile_timeout_limit": "",
"your_projects": "", "your_projects": "",

View file

@ -0,0 +1,116 @@
import { memo, useCallback, useEffect } from 'react'
import { Button } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import * as eventTracking from '../../../infrastructure/event-tracking'
import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
import { useSplitTestContext } from '@/shared/context/split-test-context'
const TWENTY_FOUR_DAYS = 24 * 60 * 60 * 24 * 1000
function CompileTimeWarning() {
const { t } = useTranslation()
const [displayStatus, setDisplayStatus] = usePersistedState(
'compile-time-warning-display-status',
{ lastDisplayTime: 0, dismissed: false },
true
)
const {
showCompileTimeWarning,
setShowCompileTimeWarning,
deliveryLatencies,
isProjectOwner,
} = useDetachCompileContext()
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
useEffect(() => {
if (deliveryLatencies && deliveryLatencies.compileTimeServerE2E) {
// compile-timeout-20s test
if (deliveryLatencies.compileTimeServerE2E > 10000) {
eventTracking.sendMB('compile-time-warning-would-display', {
time: 10,
newCompileTimeout: 'control',
isProjectOwner,
})
}
}
}, [deliveryLatencies, isProjectOwner])
useEffect(() => {
if (showCompileTimeWarning) {
if (
displayStatus &&
Date.now() - displayStatus.lastDisplayTime < TWENTY_FOUR_DAYS
) {
return
}
setDisplayStatus({ lastDisplayTime: Date.now(), dismissed: false })
eventTracking.sendMB('compile-time-warning-displayed', { time: 30 })
}
}, [showCompileTimeWarning, displayStatus, setDisplayStatus])
const getTimeSinceDisplayed = useCallback(() => {
return (Date.now() - displayStatus.lastDisplayTime) / 1000
}, [displayStatus])
const closeWarning = useCallback(() => {
eventTracking.sendMB('compile-time-warning-dismissed', {
'time-since-displayed': getTimeSinceDisplayed(),
time: 30,
})
setShowCompileTimeWarning(false)
setDisplayStatus(displayStatus => ({ ...displayStatus, dismissed: true }))
}, [getTimeSinceDisplayed, setShowCompileTimeWarning, setDisplayStatus])
const handleUpgradeClick = useCallback(() => {
eventTracking.sendMB('compile-time-warning-upgrade-click', {
'time-since-displayed': getTimeSinceDisplayed(),
time: 30,
})
setShowCompileTimeWarning(false)
setDisplayStatus(displayStatus => ({ ...displayStatus, dismissed: true }))
}, [getTimeSinceDisplayed, setShowCompileTimeWarning, setDisplayStatus])
if (!showCompileTimeWarning || displayStatus.dismissed) {
return null
}
return (
<div className="alert alert-success compile-time-warning" role="alert">
<Button
className="close"
data-dismiss="alert"
aria-label="Close"
onClick={closeWarning}
>
<span aria-hidden="true">&times;</span>
</Button>
<div className="warning-content">
<div className="warning-text">
<Trans
i18nKey="approaching_compile_timeout_limit_upgrade_for_more_compile_time"
components={{
strong: <strong style={{ display: 'inline-block' }} />,
}}
/>
</div>
<div className="upgrade-prompt">
<StartFreeTrialButton
buttonProps={{ bsStyle: 'primary', bsSize: 'sm' }}
handleClick={handleUpgradeClick}
source="compile-time-warning"
>
{hasNewPaywallCta ? t('get_more_compile_time') : t('upgrade')}
</StartFreeTrialButton>
</div>
</div>
</div>
)
}
export default memo(CompileTimeWarning)

View file

@ -0,0 +1,122 @@
import Notification from '@/shared/components/notification'
import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
import { Trans, useTranslation } from 'react-i18next'
import * as eventTracking from '@/infrastructure/event-tracking'
import { FC } from 'react'
import { useSplitTestContext } from '@/shared/context/split-test-context'
const sendInfoClickEvent = () => {
eventTracking.sendMB('paywall-info-click', {
'paywall-type': 'compile-time-warning',
content: 'blog',
})
}
export const CompileTimeoutChangingSoon: FC<{
isProjectOwner?: boolean
handleDismissChangingSoon: () => void
}> = ({ isProjectOwner = false, handleDismissChangingSoon }) => {
const { t } = useTranslation()
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
const compileTimeoutChangesBlogLink = (
/* eslint-disable-next-line jsx-a11y/anchor-has-content */
<a
aria-label={t('read_more_about_free_compile_timeouts_servers')}
href="/blog/changes-to-free-compile-timeouts-and-servers"
key="compileTimeoutBlogLink1"
rel="noopener noreferrer"
target="_blank"
onClick={sendInfoClickEvent}
/>
)
const fixingCompileTimeoutsLearnLink = (
/* eslint-disable-next-line jsx-a11y/anchor-has-content */
<a
aria-label={t('read_more_about_fix_prevent_timeout')}
href="/learn/how-to/Fixing_and_preventing_compile_timeouts"
key="compileTimeoutBlogLink2"
target="_blank"
rel="noopener noreferrer"
/>
)
if (isProjectOwner) {
return (
<Notification
action={
<StartFreeTrialButton
variant="new-changing"
source="compile-time-warning"
buttonProps={{
className: 'btn-secondary-compile-timeout-override',
}}
>
{hasNewPaywallCta
? t('get_more_compile_time')
: t('start_free_trial_without_exclamation')}
</StartFreeTrialButton>
}
ariaLive="polite"
content={
<div>
<p>
<Trans
i18nKey="compiles_on_our_free_plan_are_now_on_faster_servers"
components={[compileTimeoutChangesBlogLink]}
/>
</p>
<p className="row-spaced">
<Trans
i18nKey="you_may_be_able_to_fix_issues_to_speed_up_the_compile"
components={[fixingCompileTimeoutsLearnLink]}
/>{' '}
<Trans
i18nKey="and_you_can_upgrade_for_plenty_more_compile_time"
components={{ strong: <strong /> }}
/>
</p>
</div>
}
title={t('your_project_compiled_but_soon_might_not')}
type="warning"
isActionBelowContent
isDismissible
onDismiss={handleDismissChangingSoon}
/>
)
}
return (
<Notification
ariaLive="polite"
content={
<div>
<p>
<Trans
i18nKey="compiles_on_our_free_plan_are_now_on_faster_servers"
components={[compileTimeoutChangesBlogLink]}
/>{' '}
<Trans
i18nKey="you_may_be_able_to_fix_issues_to_speed_up_the_compile"
components={[fixingCompileTimeoutsLearnLink]}
/>
</p>
<p className="row-spaced">
<Trans
i18nKey="tell_the_project_owner_to_upgrade_plan_for_more_compile_time"
components={{ strong: <strong /> }}
/>
</p>
</div>
}
title={t('this_project_compiled_but_soon_might_not')}
type="warning"
isDismissible
onDismiss={handleDismissChangingSoon}
/>
)
}

View file

@ -1,23 +1,51 @@
import { memo, useCallback, useEffect, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import * as eventTracking from '@/infrastructure/event-tracking' import * as eventTracking from '@/infrastructure/event-tracking'
import { useDetachCompileContext } from '@/shared/context/detach-compile-context' import { useDetachCompileContext } from '@/shared/context/detach-compile-context'
import usePersistedState from '@/shared/hooks/use-persisted-state' import usePersistedState from '@/shared/hooks/use-persisted-state'
import { CompileTimeoutWarning } from '@/features/pdf-preview/components/compile-timeout-warning' import { CompileTimeoutWarning } from '@/features/pdf-preview/components/compile-timeout-warning'
import { CompileTimeoutChangingSoon } from '@/features/pdf-preview/components/compile-timeout-changing-soon'
function CompileTimeoutMessages() { function CompileTimeoutMessages() {
const { isProjectOwner, deliveryLatencies, compiling, showLogs, error } = const {
useDetachCompileContext() showNewCompileTimeoutUI,
isProjectOwner,
deliveryLatencies,
compiling,
showLogs,
error,
} = useDetachCompileContext()
const [showWarning, setShowWarning] = useState(false) const [showWarning, setShowWarning] = useState(false)
const [showChangingSoon, setShowChangingSoon] = useState(false)
const [dismissedUntilWarning, setDismissedUntilWarning] = usePersistedState< const [dismissedUntilWarning, setDismissedUntilWarning] = usePersistedState<
Date | undefined Date | undefined
>(`has-dismissed-10s-compile-time-warning-until`) >(`has-dismissed-10s-compile-time-warning-until`)
const segmentation = useMemo(() => {
return {
newCompileTimeout: showNewCompileTimeoutUI,
isProjectOwner,
}
}, [showNewCompileTimeoutUI, isProjectOwner])
const handleNewCompile = useCallback( const handleNewCompile = useCallback(
compileTime => { compileTime => {
setShowWarning(false) setShowWarning(false)
if (compileTime > 10000) { setShowChangingSoon(false)
if (isProjectOwner) { if (compileTime > 20000) {
if (showNewCompileTimeoutUI === 'changing') {
setShowChangingSoon(true)
eventTracking.sendMB('compile-time-warning-displayed', {
time: 20,
...segmentation,
})
}
} else if (compileTime > 10000) {
setShowChangingSoon(false)
if (
(isProjectOwner && showNewCompileTimeoutUI === 'active') ||
showNewCompileTimeoutUI === 'changing'
) {
if ( if (
!dismissedUntilWarning || !dismissedUntilWarning ||
new Date(dismissedUntilWarning) < new Date() new Date(dismissedUntilWarning) < new Date()
@ -25,25 +53,37 @@ function CompileTimeoutMessages() {
setShowWarning(true) setShowWarning(true)
eventTracking.sendMB('compile-time-warning-displayed', { eventTracking.sendMB('compile-time-warning-displayed', {
time: 10, time: 10,
isProjectOwner, ...segmentation,
}) })
} }
} }
} }
}, },
[isProjectOwner, dismissedUntilWarning] [
isProjectOwner,
showNewCompileTimeoutUI,
dismissedUntilWarning,
segmentation,
]
) )
const handleDismissWarning = useCallback(() => { const handleDismissWarning = useCallback(() => {
eventTracking.sendMB('compile-time-warning-dismissed', { eventTracking.sendMB('compile-time-warning-dismissed', {
time: 10, time: 10,
isProjectOwner, ...segmentation,
}) })
setShowWarning(false) setShowWarning(false)
const until = new Date() const until = new Date()
until.setDate(until.getDate() + 1) // 1 day until.setDate(until.getDate() + 1) // 1 day
setDismissedUntilWarning(until) setDismissedUntilWarning(until)
}, [isProjectOwner, setDismissedUntilWarning]) }, [setDismissedUntilWarning, segmentation])
const handleDismissChangingSoon = useCallback(() => {
eventTracking.sendMB('compile-time-warning-dismissed', {
time: 20,
...segmentation,
})
}, [segmentation])
useEffect(() => { useEffect(() => {
if (compiling || error || showLogs) return if (compiling || error || showLogs) return
@ -58,16 +98,26 @@ function CompileTimeoutMessages() {
return null return null
} }
if (!showWarning) { if (!showWarning && !showChangingSoon) {
return null return null
} }
// if showWarning is true then the 10s warning is shown // if showWarning is true then the 10s warning is shown
// and if showChangingSoon is true then the 20s-60s should show
return ( return (
<div> <div>
{showWarning && isProjectOwner && ( {showWarning && isProjectOwner && (
<CompileTimeoutWarning handleDismissWarning={handleDismissWarning} /> <CompileTimeoutWarning
showNewCompileTimeoutUI={showNewCompileTimeoutUI}
handleDismissWarning={handleDismissWarning}
/>
)}
{showChangingSoon && (
<CompileTimeoutChangingSoon
isProjectOwner={isProjectOwner}
handleDismissChangingSoon={handleDismissChangingSoon}
/>
)} )}
</div> </div>
) )

View file

@ -6,7 +6,8 @@ import { useSplitTestContext } from '@/shared/context/split-test-context'
export const CompileTimeoutWarning: FC<{ export const CompileTimeoutWarning: FC<{
handleDismissWarning: () => void handleDismissWarning: () => void
}> = ({ handleDismissWarning }) => { showNewCompileTimeoutUI?: string
}> = ({ handleDismissWarning, showNewCompileTimeoutUI }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { splitTestVariants } = useSplitTestContext() const { splitTestVariants } = useSplitTestContext()
@ -33,8 +34,14 @@ export const CompileTimeoutWarning: FC<{
<div> <div>
<span>{t('your_project_near_compile_timeout_limit')}</span> <span>{t('your_project_near_compile_timeout_limit')}</span>
</div> </div>
{showNewCompileTimeoutUI === 'active' ? (
<>
<strong>{t('upgrade_for_12x_more_compile_time')}</strong> <strong>{t('upgrade_for_12x_more_compile_time')}</strong>
{'. '} {'. '}
</>
) : (
<strong>{t('upgrade_for_plenty_more_compile_time')}</strong>
)}
</div> </div>
} }
type="warning" type="warning"

View file

@ -0,0 +1,135 @@
import { memo, useEffect, useRef, useState } from 'react'
import { Button, Alert } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import Icon from '../../../shared/components/icon'
import { sendMB } from '../../../infrastructure/event-tracking'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import { useProjectContext } from '../../../shared/context/project-context'
const SAY_THANKS_TIMEOUT = 10 * 1000
function FasterCompilesFeedbackContent() {
const { clsiServerId, deliveryLatencies, pdfFile, pdfUrl } =
useCompileContext()
const { _id: projectId } = useProjectContext()
const [incrementalCompiles, setIncrementalCompiles] = useState(0)
const [hasRatedProject, setHasRatedProject] = usePersistedState(
`faster-compiles-feedback:${projectId}`,
false,
true
)
const [dismiss, setDismiss] = usePersistedState(
'faster-compiles-feedback:dismiss',
false,
true
)
const [sayThanks, setSayThanks] = useState(false)
const lastClsiServerId = useRef<string | undefined>(undefined)
const lastPdfUrl = useRef<string | undefined>(undefined)
useEffect(() => {
if (
!pdfUrl ||
!lastPdfUrl.current ||
clsiServerId !== lastClsiServerId.current
) {
// Reset history after
// - clearing cache / server error (both reset pdfUrl)
// - initial compile after reset of pdfUrl
// - switching the clsi server, aka we get a _slow_ full compile.
setIncrementalCompiles(0)
lastClsiServerId.current = clsiServerId
} else {
setIncrementalCompiles(n => n + 1)
}
lastPdfUrl.current = pdfUrl
}, [clsiServerId, lastPdfUrl, pdfUrl, setIncrementalCompiles])
function submitFeedback(feedback = '') {
sendMB('faster-compiles-feedback', {
projectId,
server: clsiServerId?.includes('-c2d-') ? 'faster' : 'normal',
feedback,
pdfSize: pdfFile?.size,
...deliveryLatencies,
})
setHasRatedProject(true)
setSayThanks(true)
window.setTimeout(() => {
setSayThanks(false)
}, SAY_THANKS_TIMEOUT)
}
function dismissFeedback() {
sendMB('faster-compiles-feedback-dismiss')
setDismiss(true)
}
const { t } = useTranslation()
// Hide the feedback prompt in all these cases:
// - the initial compile (0), its always perceived as _slow_.
// - the first incremental compile (1), its always _faster_ than ^.
// - the user has dismissed the prompt
// - the user has rated compile speed already (say thanks if needed)
switch (true) {
case sayThanks:
return (
<Alert
bsStyle="info"
className="faster-compiles-feedback"
onClick={() => setSayThanks(false)}
>
{t('faster_compiles_feedback_thanks')}
</Alert>
)
case dismiss || hasRatedProject:
return null
case incrementalCompiles > 1:
return (
<Alert bsStyle="info" className="faster-compiles-feedback">
<button
type="button"
aria-label={t('dismiss')}
className="btn-inline-link faster-compiles-feedback-dismiss"
onClick={dismissFeedback}
>
<Icon type="close" fw />
</button>
{t('faster_compiles_feedback_question')}
<div className="faster-compiles-feedback-options">
{['slower', 'same', 'faster'].map(feedback => (
<Button
bsStyle="default"
bsSize="xs"
className="faster-compiles-feedback-option"
onClick={() => submitFeedback(feedback)}
key={feedback}
>
{feedback === 'faster'
? t('faster_compiles_feedback_seems_faster')
: feedback === 'same'
? t('faster_compiles_feedback_seems_same')
: t('faster_compiles_feedback_seems_slower')}
</Button>
))}
</div>
</Alert>
)
default:
return null
}
}
function FasterCompilesFeedback() {
const { showFasterCompilesFeedbackUI } = useCompileContext()
if (!showFasterCompilesFeedbackUI) {
return null
}
return <FasterCompilesFeedbackContent />
}
export default memo(FasterCompilesFeedback)

View file

@ -3,6 +3,7 @@ import { memo } from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import PdfValidationIssue from './pdf-validation-issue' import PdfValidationIssue from './pdf-validation-issue'
import StopOnFirstErrorPrompt from './stop-on-first-error-prompt' import StopOnFirstErrorPrompt from './stop-on-first-error-prompt'
import TimeoutUpgradePrompt from './timeout-upgrade-prompt'
import TimeoutUpgradePromptNew from './timeout-upgrade-prompt-new' import TimeoutUpgradePromptNew from './timeout-upgrade-prompt-new'
import PdfPreviewError from './pdf-preview-error' import PdfPreviewError from './pdf-preview-error'
import PdfClearCacheButton from './pdf-clear-cache-button' import PdfClearCacheButton from './pdf-clear-cache-button'
@ -24,6 +25,7 @@ function PdfLogsViewer() {
validationIssues, validationIssues,
showLogs, showLogs,
stoppedOnFirstError, stoppedOnFirstError,
showNewCompileTimeoutUI,
} = useCompileContext() } = useCompileContext()
const { loadingError } = usePdfPreviewContext() const { loadingError } = usePdfPreviewContext()
@ -43,10 +45,13 @@ function PdfLogsViewer() {
{loadingError && <PdfPreviewError error="pdf-viewer-loading-error" />} {loadingError && <PdfPreviewError error="pdf-viewer-loading-error" />}
{error === 'timedout' ? ( {showNewCompileTimeoutUI && error === 'timedout' ? (
<TimeoutUpgradePromptNew /> <TimeoutUpgradePromptNew />
) : ( ) : (
<>{error && <PdfPreviewError error={error} />}</> <>
{error && <PdfPreviewError error={error} />}
{error === 'timedout' && <TimeoutUpgradePrompt />}
</>
)} )}
{validationIssues && {validationIssues &&

View file

@ -5,12 +5,14 @@ import PdfViewer from './pdf-viewer'
import { FullSizeLoadingSpinner } from '../../../shared/components/loading-spinner' import { FullSizeLoadingSpinner } from '../../../shared/components/loading-spinner'
import PdfHybridPreviewToolbar from './pdf-preview-hybrid-toolbar' import PdfHybridPreviewToolbar from './pdf-preview-hybrid-toolbar'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context' import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import FasterCompilesFeedback from './faster-compiles-feedback'
import { PdfPreviewMessages } from './pdf-preview-messages' import { PdfPreviewMessages } from './pdf-preview-messages'
import CompileTimeWarning from './compile-time-warning'
import CompileTimeoutMessages from './compile-timeout-messages' import CompileTimeoutMessages from './compile-timeout-messages'
import { PdfPreviewProvider } from './pdf-preview-provider' import { PdfPreviewProvider } from './pdf-preview-provider'
function PdfPreviewPane() { function PdfPreviewPane() {
const { pdfUrl } = useCompileContext() const { pdfUrl, showNewCompileTimeoutUI } = useCompileContext()
const classes = classNames('pdf', 'full-size', { const classes = classNames('pdf', 'full-size', {
'pdf-empty': !pdfUrl, 'pdf-empty': !pdfUrl,
}) })
@ -19,11 +21,16 @@ function PdfPreviewPane() {
<PdfPreviewProvider> <PdfPreviewProvider>
<PdfHybridPreviewToolbar /> <PdfHybridPreviewToolbar />
<PdfPreviewMessages> <PdfPreviewMessages>
{showNewCompileTimeoutUI ? (
<CompileTimeoutMessages /> <CompileTimeoutMessages />
) : (
<CompileTimeWarning />
)}
</PdfPreviewMessages> </PdfPreviewMessages>
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}> <Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
<div className="pdf-viewer"> <div className="pdf-viewer">
<PdfViewer /> <PdfViewer />
<FasterCompilesFeedback />
</div> </div>
</Suspense> </Suspense>
<PdfLogsViewer /> <PdfLogsViewer />

View file

@ -13,6 +13,7 @@ function TimeoutUpgradePromptNew() {
startCompile, startCompile,
lastCompileOptions, lastCompileOptions,
setAnimateCompileDropdownArrow, setAnimateCompileDropdownArrow,
showNewCompileTimeoutUI,
isProjectOwner, isProjectOwner,
} = useDetachCompileContext() } = useDetachCompileContext()
@ -26,25 +27,35 @@ function TimeoutUpgradePromptNew() {
setAnimateCompileDropdownArrow(true) setAnimateCompileDropdownArrow(true)
}, [enableStopOnFirstError, startCompile, setAnimateCompileDropdownArrow]) }, [enableStopOnFirstError, startCompile, setAnimateCompileDropdownArrow])
if (!window.ExposedSettings.enableSubscriptions) {
return null
}
const compileTimeChanging = showNewCompileTimeoutUI === 'changing'
return ( return (
<> <>
<CompileTimeout isProjectOwner={isProjectOwner} /> <CompileTimeout
{window.ExposedSettings.enableSubscriptions && ( compileTimeChanging={compileTimeChanging}
isProjectOwner={isProjectOwner}
/>
<PreventTimeoutHelpMessage <PreventTimeoutHelpMessage
compileTimeChanging={compileTimeChanging}
handleEnableStopOnFirstErrorClick={handleEnableStopOnFirstErrorClick} handleEnableStopOnFirstErrorClick={handleEnableStopOnFirstErrorClick}
lastCompileOptions={lastCompileOptions} lastCompileOptions={lastCompileOptions}
isProjectOwner={isProjectOwner} isProjectOwner={isProjectOwner}
/> />
)}
</> </>
) )
} }
type CompileTimeoutProps = { type CompileTimeoutProps = {
compileTimeChanging?: boolean
isProjectOwner: boolean isProjectOwner: boolean
} }
const CompileTimeout = memo(function CompileTimeout({ const CompileTimeout = memo(function CompileTimeout({
compileTimeChanging,
isProjectOwner, isProjectOwner,
}: CompileTimeoutProps) { }: CompileTimeoutProps) {
const { t } = useTranslation() const { t } = useTranslation()
@ -56,7 +67,6 @@ const CompileTimeout = memo(function CompileTimeout({
<PdfLogEntry <PdfLogEntry
headerTitle={t('your_compile_timed_out')} headerTitle={t('your_compile_timed_out')}
formattedContent={ formattedContent={
window.ExposedSettings.enableSubscriptions && (
<> <>
<p> <p>
{isProjectOwner {isProjectOwner
@ -65,10 +75,21 @@ const CompileTimeout = memo(function CompileTimeout({
</p> </p>
{isProjectOwner ? ( {isProjectOwner ? (
<p> <p>
{compileTimeChanging ? (
<>
<strong>{t('upgrade_for_plenty_more_compile_time')}</strong>{' '}
{t(
'plus_additional_collaborators_document_history_track_changes_and_more'
)}
</>
) : (
<>
<strong>{t('upgrade_for_12x_more_compile_time')}</strong>{' '} <strong>{t('upgrade_for_12x_more_compile_time')}</strong>{' '}
{t( {t(
'plus_additional_collaborators_document_history_track_changes_and_more' 'plus_additional_collaborators_document_history_track_changes_and_more'
)} )}
</>
)}
</p> </p>
) : ( ) : (
<Trans <Trans
@ -83,6 +104,7 @@ const CompileTimeout = memo(function CompileTimeout({
{isProjectOwner && ( {isProjectOwner && (
<p className="text-center"> <p className="text-center">
<StartFreeTrialButton <StartFreeTrialButton
variant={compileTimeChanging ? 'new-changing' : 'new-20s'}
source="compile-timeout" source="compile-timeout"
buttonProps={{ buttonProps={{
bsStyle: 'success', bsStyle: 'success',
@ -97,7 +119,6 @@ const CompileTimeout = memo(function CompileTimeout({
</p> </p>
)} )}
</> </>
)
} }
// @ts-ignore // @ts-ignore
entryAriaLabel={t('your_compile_timed_out')} entryAriaLabel={t('your_compile_timed_out')}
@ -107,12 +128,14 @@ const CompileTimeout = memo(function CompileTimeout({
}) })
type PreventTimeoutHelpMessageProps = { type PreventTimeoutHelpMessageProps = {
compileTimeChanging?: boolean
lastCompileOptions: any lastCompileOptions: any
handleEnableStopOnFirstErrorClick: () => void handleEnableStopOnFirstErrorClick: () => void
isProjectOwner: boolean isProjectOwner: boolean
} }
const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
compileTimeChanging,
lastCompileOptions, lastCompileOptions,
handleEnableStopOnFirstErrorClick, handleEnableStopOnFirstErrorClick,
isProjectOwner, isProjectOwner,
@ -204,6 +227,21 @@ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
</p> </p>
<p> <p>
<em> <em>
{compileTimeChanging ? (
<>
{isProjectOwner ? (
<Trans
i18nKey="were_in_the_process_of_reducing_compile_timeout_which_may_affect_your_project"
components={[compileTimeoutChangesBlogLink]}
/>
) : (
<Trans
i18nKey="were_in_the_process_of_reducing_compile_timeout_which_may_affect_this_project"
components={[compileTimeoutChangesBlogLink]}
/>
)}
</>
) : (
<> <>
{isProjectOwner ? ( {isProjectOwner ? (
<Trans <Trans
@ -217,6 +255,7 @@ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
/> />
)} )}
</> </>
)}
</em> </em>
</p> </p>
</> </>

View file

@ -0,0 +1,62 @@
import { useTranslation } from 'react-i18next'
import { useEditorContext } from '../../../shared/context/editor-context'
import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
import { memo } from 'react'
import PdfLogEntry from './pdf-log-entry'
import UpgradeBenefits from '../../../shared/components/upgrade-benefits'
import { useSplitTestContext } from '@/shared/context/split-test-context'
function TimeoutUpgradePrompt() {
const { t } = useTranslation()
const { hasPremiumCompile, isProjectOwner } = useEditorContext()
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
if (!window.ExposedSettings.enableSubscriptions || hasPremiumCompile) {
return null
}
return (
<PdfLogEntry
headerTitle={
isProjectOwner
? t('upgrade_for_longer_compiles')
: t('ask_proj_owner_to_upgrade_for_longer_compiles')
}
formattedContent={
<>
<p>{t('free_accounts_have_timeout_upgrade_to_increase')}</p>
<p>{t('plus_upgraded_accounts_receive')}:</p>
<div>
<UpgradeBenefits />
</div>
{isProjectOwner && (
<p className="text-center">
<StartFreeTrialButton
source="compile-timeout"
buttonProps={{
bsStyle: 'success',
className: 'row-spaced-small',
}}
>
{hasNewPaywallCta
? t('get_more_compile_time')
: t('start_free_trial')}
</StartFreeTrialButton>
</p>
)}
</>
}
entryAriaLabel={
isProjectOwner
? t('upgrade_for_longer_compiles')
: t('ask_proj_owner_to_upgrade_for_longer_compiles')
}
level="success"
/>
)
}
export default memo(TimeoutUpgradePrompt)

View file

@ -221,6 +221,14 @@ export default class DocumentCompiler {
params.file_line_errors = 'true' params.file_line_errors = 'true'
} }
// temporary override to force the new compile timeout
const newCompileTimeoutOverride = new URLSearchParams(
window.location.search
).get('force_new_compile_timeout')
if (newCompileTimeoutOverride) {
params.set('force_new_compile_timeout', newCompileTimeoutOverride)
}
return params return params
} }

View file

@ -52,6 +52,8 @@ export const DetachCompileProvider: FC = ({ children }) => {
setStopOnValidationError: _setStopOnValidationError, setStopOnValidationError: _setStopOnValidationError,
showLogs: _showLogs, showLogs: _showLogs,
showCompileTimeWarning: _showCompileTimeWarning, showCompileTimeWarning: _showCompileTimeWarning,
showNewCompileTimeoutUI: _showNewCompileTimeoutUI,
showFasterCompilesFeedbackUI: _showFasterCompilesFeedbackUI,
stopOnFirstError: _stopOnFirstError, stopOnFirstError: _stopOnFirstError,
stopOnValidationError: _stopOnValidationError, stopOnValidationError: _stopOnValidationError,
stoppedOnFirstError: _stoppedOnFirstError, stoppedOnFirstError: _stoppedOnFirstError,
@ -190,6 +192,18 @@ export const DetachCompileProvider: FC = ({ children }) => {
'detacher', 'detacher',
'detached' 'detached'
) )
const [showNewCompileTimeoutUI] = useDetachStateWatcher(
'showNewCompileTimeoutUI',
_showNewCompileTimeoutUI,
'detacher',
'detached'
)
const [showFasterCompilesFeedbackUI] = useDetachStateWatcher(
'showFasterCompilesFeedbackUI',
_showFasterCompilesFeedbackUI,
'detacher',
'detached'
)
const [stopOnFirstError] = useDetachStateWatcher( const [stopOnFirstError] = useDetachStateWatcher(
'stopOnFirstError', 'stopOnFirstError',
_stopOnFirstError, _stopOnFirstError,
@ -399,6 +413,8 @@ export const DetachCompileProvider: FC = ({ children }) => {
setStopOnValidationError, setStopOnValidationError,
showLogs, showLogs,
showCompileTimeWarning, showCompileTimeWarning,
showNewCompileTimeoutUI,
showFasterCompilesFeedbackUI,
startCompile, startCompile,
stopCompile, stopCompile,
stopOnFirstError, stopOnFirstError,
@ -450,6 +466,8 @@ export const DetachCompileProvider: FC = ({ children }) => {
setStopOnValidationError, setStopOnValidationError,
showCompileTimeWarning, showCompileTimeWarning,
showLogs, showLogs,
showNewCompileTimeoutUI,
showFasterCompilesFeedbackUI,
startCompile, startCompile,
stopCompile, stopCompile,
stopOnFirstError, stopOnFirstError,

View file

@ -72,6 +72,8 @@ export type CompileContext = {
setStopOnValidationError: (value: boolean) => void setStopOnValidationError: (value: boolean) => void
showCompileTimeWarning: boolean showCompileTimeWarning: boolean
showLogs: boolean showLogs: boolean
showNewCompileTimeoutUI?: string
showFasterCompilesFeedbackUI: boolean
stopOnFirstError: boolean stopOnFirstError: boolean
stopOnValidationError: boolean stopOnValidationError: boolean
stoppedOnFirstError: boolean stoppedOnFirstError: boolean
@ -101,7 +103,11 @@ export const LocalCompileProvider: FC = ({ children }) => {
const { hasPremiumCompile, isProjectOwner } = useEditorContext() const { hasPremiumCompile, isProjectOwner } = useEditorContext()
const { _id: projectId, rootDocId } = useProjectContext() const {
_id: projectId,
rootDocId,
showNewCompileTimeoutUI,
} = useProjectContext()
const { pdfPreviewOpen } = useLayoutContext() const { pdfPreviewOpen } = useLayoutContext()
@ -176,6 +182,10 @@ export const LocalCompileProvider: FC = ({ children }) => {
// whether the logs should be visible // whether the logs should be visible
const [showLogs, setShowLogs] = useState(false) const [showLogs, setShowLogs] = useState(false)
// whether the faster compiles feedback UI should be displayed
const [showFasterCompilesFeedbackUI, setShowFasterCompilesFeedbackUI] =
useState(false)
// whether the compile dropdown arrow should be animated // whether the compile dropdown arrow should be animated
const [animateCompileDropdownArrow, setAnimateCompileDropdownArrow] = const [animateCompileDropdownArrow, setAnimateCompileDropdownArrow] =
useState(false) useState(false)
@ -348,6 +358,9 @@ export const LocalCompileProvider: FC = ({ children }) => {
if (data.clsiServerId) { if (data.clsiServerId) {
setClsiServerId(data.clsiServerId) // set in scope, for PdfSynctexController setClsiServerId(data.clsiServerId) // set in scope, for PdfSynctexController
} }
setShowFasterCompilesFeedbackUI(
Boolean(data.showFasterCompilesFeedbackUI)
)
if (data.outputFiles) { if (data.outputFiles) {
const outputFiles = new Map() const outputFiles = new Map()
@ -620,6 +633,8 @@ export const LocalCompileProvider: FC = ({ children }) => {
setStopOnFirstError, setStopOnFirstError,
setStopOnValidationError, setStopOnValidationError,
showLogs, showLogs,
showNewCompileTimeoutUI,
showFasterCompilesFeedbackUI,
startCompile, startCompile,
stopCompile, stopCompile,
stopOnFirstError, stopOnFirstError,
@ -668,6 +683,8 @@ export const LocalCompileProvider: FC = ({ children }) => {
setStopOnValidationError, setStopOnValidationError,
showCompileTimeWarning, showCompileTimeWarning,
showLogs, showLogs,
showNewCompileTimeoutUI,
showFasterCompilesFeedbackUI,
startCompile, startCompile,
stopCompile, stopCompile,
stopOnFirstError, stopOnFirstError,

View file

@ -30,6 +30,7 @@ const ProjectContext = createContext<
_id: UserId _id: UserId
email: string email: string
} }
showNewCompileTimeoutUI?: string
tags: { tags: {
_id: string _id: string
name: string name: string
@ -73,6 +74,7 @@ export const ProjectProvider: FC = ({ children }) => {
features, features,
publicAccesLevel: publicAccessLevel, publicAccesLevel: publicAccessLevel,
owner, owner,
showNewCompileTimeoutUI,
trackChangesState, trackChangesState,
} = project || projectFallback } = project || projectFallback
@ -84,6 +86,17 @@ export const ProjectProvider: FC = ({ children }) => {
[] []
) )
// temporary override for new compile timeout
const forceNewCompileTimeout = new URLSearchParams(
window.location.search
).get('force_new_compile_timeout')
const newCompileTimeoutOverride =
forceNewCompileTimeout === 'active'
? 'active'
: forceNewCompileTimeout === 'changing'
? 'changing'
: undefined
const value = useMemo(() => { const value = useMemo(() => {
return { return {
_id, _id,
@ -94,6 +107,8 @@ export const ProjectProvider: FC = ({ children }) => {
features, features,
publicAccessLevel, publicAccessLevel,
owner, owner,
showNewCompileTimeoutUI:
newCompileTimeoutOverride || showNewCompileTimeoutUI,
tags, tags,
trackChangesState, trackChangesState,
} }
@ -106,6 +121,8 @@ export const ProjectProvider: FC = ({ children }) => {
features, features,
publicAccessLevel, publicAccessLevel,
owner, owner,
showNewCompileTimeoutUI,
newCompileTimeoutOverride,
tags, tags,
trackChangesState, trackChangesState,
]) ])

View file

@ -2,7 +2,7 @@ import { ScopeDecorator } from './decorators/scope'
import { useLocalCompileContext } from '../js/shared/context/local-compile-context' import { useLocalCompileContext } from '../js/shared/context/local-compile-context'
import { useEffect } from 'react' import { useEffect } from 'react'
import { PdfPreviewMessages } from '../js/features/pdf-preview/components/pdf-preview-messages' import { PdfPreviewMessages } from '../js/features/pdf-preview/components/pdf-preview-messages'
import CompileTimeoutMessages from '@/features/pdf-preview/components/compile-timeout-messages' import CompileTimeWarning from '../js/features/pdf-preview/components/compile-time-warning'
export default { export default {
title: 'Editor / PDF Preview / Messages', title: 'Editor / PDF Preview / Messages',
@ -20,7 +20,7 @@ export const Dismissible = () => {
return ( return (
<div style={{ width: 800, position: 'relative' }}> <div style={{ width: 800, position: 'relative' }}>
<PdfPreviewMessages> <PdfPreviewMessages>
<CompileTimeoutMessages /> <CompileTimeWarning />
</PdfPreviewMessages> </PdfPreviewMessages>
</div> </div>
) )

View file

@ -1,5 +1,9 @@
import { useEffect } from 'react'
import { ScopeDecorator } from './decorators/scope' import { ScopeDecorator } from './decorators/scope'
import { useLocalCompileContext } from '@/shared/context/local-compile-context'
import { PdfPreviewMessages } from '@/features/pdf-preview/components/pdf-preview-messages' import { PdfPreviewMessages } from '@/features/pdf-preview/components/pdf-preview-messages'
import CompileTimeWarning from '@/features/pdf-preview/components/compile-time-warning'
import { CompileTimeoutChangingSoon } from '@/features/pdf-preview/components/compile-timeout-changing-soon'
import { CompileTimeoutWarning } from '@/features/pdf-preview/components/compile-timeout-warning' import { CompileTimeoutWarning } from '@/features/pdf-preview/components/compile-timeout-warning'
export default { export default {
@ -17,9 +21,40 @@ export default {
], ],
} }
export const CompileTime = () => {
const { setShowCompileTimeWarning } = useLocalCompileContext()
useEffect(() => {
setShowCompileTimeWarning(true)
}, [setShowCompileTimeWarning])
return <CompileTimeWarning />
}
export const CompileTimeoutChangingSoonNotProjectOwner = (args: any) => {
return <CompileTimeoutChangingSoon {...args} />
}
CompileTimeoutChangingSoonNotProjectOwner.argTypes = {
handleDismissChangingSoon: { action: 'dismiss changing soon' },
}
export const CompileTimeoutChangingSoonProjectOwner = (args: any) => {
return <CompileTimeoutChangingSoon {...args} isProjectOwner />
}
CompileTimeoutChangingSoonProjectOwner.argTypes = {
handleDismissChangingSoon: { action: 'dismiss changing soon' },
}
export const CompileTimeoutWarningActive = (args: any) => { export const CompileTimeoutWarningActive = (args: any) => {
return <CompileTimeoutWarning {...args} /> return <CompileTimeoutWarning {...args} showNewCompileTimeoutUI="active" />
} }
CompileTimeoutWarningActive.argTypes = { CompileTimeoutWarningActive.argTypes = {
handleDismissWarning: { action: 'dismiss warning' }, handleDismissWarning: { action: 'dismiss warning' },
} }
export const CompileTimeoutWarningChanging = (args: any) => {
return <CompileTimeoutWarning {...args} showNewCompileTimeoutUI="changing" />
}
CompileTimeoutWarningChanging.argTypes = {
handleDismissWarning: { action: 'dismiss warning' },
}

View file

@ -106,6 +106,7 @@
"an_email_has_already_been_sent_to": "An email has already been sent to <0>__email__</0>. Please wait and try again later.", "an_email_has_already_been_sent_to": "An email has already been sent to <0>__email__</0>. Please wait and try again later.",
"an_error_occurred_when_verifying_the_coupon_code": "An error occurred when verifying the coupon code", "an_error_occurred_when_verifying_the_coupon_code": "An error occurred when verifying the coupon code",
"and": "and", "and": "and",
"and_you_can_upgrade_for_plenty_more_compile_time": "And you can <strong>upgrade to get plenty more compile time.</strong>",
"annual": "Annual", "annual": "Annual",
"annual_billing_enabled": "Annual billing enabled", "annual_billing_enabled": "Annual billing enabled",
"anonymous": "Anonymous", "anonymous": "Anonymous",
@ -114,6 +115,7 @@
"app_on_x": "__appName__ on __social__", "app_on_x": "__appName__ on __social__",
"apply_educational_discount": "Apply educational discount", "apply_educational_discount": "Apply educational discount",
"apply_educational_discount_info": "Overleaf offers a 40% educational discount for groups of 10 or more. Applies to students or faculty using Overleaf for teaching.", "apply_educational_discount_info": "Overleaf offers a 40% educational discount for groups of 10 or more. Applies to students or faculty using Overleaf for teaching.",
"approaching_compile_timeout_limit_upgrade_for_more_compile_time": "You are approaching your compile timeout limit. Upgrade to <strong>Overleaf Premium</strong> for <0>4x more</0> compile time.",
"april": "April", "april": "April",
"archive": "Archive", "archive": "Archive",
"archive_projects": "Archive Projects", "archive_projects": "Archive Projects",
@ -130,6 +132,7 @@
"ascending": "Ascending", "ascending": "Ascending",
"ask_proj_owner_to_unlink_from_current_github": "Ask the owner of the project (<0>__projectOwnerEmail__</0>) to unlink the project from the current GitHub repository and create a connection to a different repository.", "ask_proj_owner_to_unlink_from_current_github": "Ask the owner of the project (<0>__projectOwnerEmail__</0>) to unlink the project from the current GitHub repository and create a connection to a different repository.",
"ask_proj_owner_to_upgrade_for_full_history": "Please ask the project owner to upgrade to access this projects full history.", "ask_proj_owner_to_upgrade_for_full_history": "Please ask the project owner to upgrade to access this projects full history.",
"ask_proj_owner_to_upgrade_for_longer_compiles": "Please ask the project owner to upgrade to increase the timeout limit.",
"ask_proj_owner_to_upgrade_for_references_search": "Please ask the project owner to upgrade to use the References Search feature.", "ask_proj_owner_to_upgrade_for_references_search": "Please ask the project owner to upgrade to use the References Search feature.",
"ask_repo_owner_to_reconnect": "Ask the GitHub repository owner (<0>__repoOwnerEmail__</0>) to reconnect the project.", "ask_repo_owner_to_reconnect": "Ask the GitHub repository owner (<0>__repoOwnerEmail__</0>) to reconnect the project.",
"ask_repo_owner_to_renew_overleaf_subscription": "Ask the GitHub repository owner (<0>__repoOwnerEmail__</0>) to renew their __appName__ subscription and reconnect the project.", "ask_repo_owner_to_renew_overleaf_subscription": "Ask the GitHub repository owner (<0>__repoOwnerEmail__</0>) to renew their __appName__ subscription and reconnect the project.",
@ -290,6 +293,7 @@
"compile_timeout_short": "Compile timeout", "compile_timeout_short": "Compile timeout",
"compile_timeout_short_info_basic": "This is how much time you get to compile your project on the Overleaf servers. You may need additional time for longer or more complex projects.", "compile_timeout_short_info_basic": "This is how much time you get to compile your project on the Overleaf servers. You may need additional time for longer or more complex projects.",
"compiler": "Compiler", "compiler": "Compiler",
"compiles_on_our_free_plan_are_now_on_faster_servers": "Compiles on our free plan are now on faster servers, and were gradually <0>introducing a shorter compile timeout limit</0>. This project currently exceeds the new limit, so it might not compile in future.",
"compiling": "Compiling", "compiling": "Compiling",
"complete": "Complete", "complete": "Complete",
"compliance": "Compliance", "compliance": "Compliance",
@ -416,6 +420,7 @@
"disabling": "Disabling", "disabling": "Disabling",
"disconnected": "Disconnected", "disconnected": "Disconnected",
"discount_of": "Discount of __amount__", "discount_of": "Discount of __amount__",
"dismiss": "Dismiss",
"dismiss_error_popup": "Dismiss first error alert", "dismiss_error_popup": "Dismiss first error alert",
"display_deleted_user": "Display deleted users", "display_deleted_user": "Display deleted users",
"do_not_have_acct_or_do_not_want_to_link": "If you dont have an <b>__appName__</b> account, or if you dont want to link to your <b>__institutionName__</b> account, please click <b>__clickText__</b>.", "do_not_have_acct_or_do_not_want_to_link": "If you dont have an <b>__appName__</b> account, or if you dont want to link to your <b>__institutionName__</b> account, please click <b>__clickText__</b>.",
@ -574,6 +579,11 @@
"faq_what_is_the_difference_between_users_and_collaborators_answer_second_paragraph": "In other words, collaborators are just other Overleaf users that you are working with on one of your projects.", "faq_what_is_the_difference_between_users_and_collaborators_answer_second_paragraph": "In other words, collaborators are just other Overleaf users that you are working with on one of your projects.",
"faq_what_is_the_difference_between_users_and_collaborators_question": "Whats the difference between users and collaborators?", "faq_what_is_the_difference_between_users_and_collaborators_question": "Whats the difference between users and collaborators?",
"fast": "Fast", "fast": "Fast",
"faster_compiles_feedback_question": "Was this compile different than usual?",
"faster_compiles_feedback_seems_faster": "Faster",
"faster_compiles_feedback_seems_same": "Same",
"faster_compiles_feedback_seems_slower": "Slower",
"faster_compiles_feedback_thanks": "Thanks for the feedback!",
"fastest": "Fastest", "fastest": "Fastest",
"feature_included": "Feature included", "feature_included": "Feature included",
"feature_not_included": "Feature not included", "feature_not_included": "Feature not included",
@ -640,6 +650,7 @@
"free": "Free", "free": "Free",
"free_7_day_trial_billed_annually": "Free 7-day trial, then billed annually", "free_7_day_trial_billed_annually": "Free 7-day trial, then billed annually",
"free_7_day_trial_billed_monthly": "Free 7-day trial, then billed monthly", "free_7_day_trial_billed_monthly": "Free 7-day trial, then billed monthly",
"free_accounts_have_timeout_upgrade_to_increase": "Free accounts have a one minute timeout, whereas upgraded accounts have a timeout of four minutes.",
"free_dropbox_and_history": "Free Dropbox and History", "free_dropbox_and_history": "Free Dropbox and History",
"free_plan_label": "Youre on the <b>free plan</b>", "free_plan_label": "Youre on the <b>free plan</b>",
"free_plan_tooltip": "Click to find out how you could benefit from Overleaf premium features.", "free_plan_tooltip": "Click to find out how you could benefit from Overleaf premium features.",
@ -1360,6 +1371,7 @@
"please_wait": "Please wait", "please_wait": "Please wait",
"plus_additional_collaborators_document_history_track_changes_and_more": "(plus additional collaborators, document history, track changes, and more).", "plus_additional_collaborators_document_history_track_changes_and_more": "(plus additional collaborators, document history, track changes, and more).",
"plus_more": "plus more", "plus_more": "plus more",
"plus_upgraded_accounts_receive": "Plus with an upgraded account you get",
"popular_tags": "Popular Tags", "popular_tags": "Popular Tags",
"portal_add_affiliation_to_join": "It looks like you are already logged in to __appName__. If you have a __portalTitle__ email you can add it now.", "portal_add_affiliation_to_join": "It looks like you are already logged in to __appName__. If you have a __portalTitle__ email you can add it now.",
"position": "Position", "position": "Position",
@ -1442,6 +1454,7 @@
"react_history_tutorial_content": "To compare a range of versions, use the <0></0> on the versions you want at the start and end of the range. To add a label or to download a version use the options in the three-dot menu. <1>Learn more about using Overleaf History.</1>", "react_history_tutorial_content": "To compare a range of versions, use the <0></0> on the versions you want at the start and end of the range. To add a label or to download a version use the options in the three-dot menu. <1>Learn more about using Overleaf History.</1>",
"react_history_tutorial_title": "History actions have a new home", "react_history_tutorial_title": "History actions have a new home",
"reactivate_subscription": "Reactivate your subscription", "reactivate_subscription": "Reactivate your subscription",
"read_more_about_fix_prevent_timeout": "Read more about how to fix and prevent timeouts",
"read_more_about_free_compile_timeouts_servers": "Read more about changes to free compile timeouts and servers", "read_more_about_free_compile_timeouts_servers": "Read more about changes to free compile timeouts and servers",
"read_only": "Read Only", "read_only": "Read Only",
"read_only_token": "Read-Only Token", "read_only_token": "Read-Only Token",
@ -1851,6 +1864,7 @@
"tc_switch_guests_tip": "Toggle track-changes for all link-sharing guests", "tc_switch_guests_tip": "Toggle track-changes for all link-sharing guests",
"tc_switch_user_tip": "Toggle track-changes for this user", "tc_switch_user_tip": "Toggle track-changes for this user",
"tell_the_project_owner_and_ask_them_to_upgrade": "<0>Tell the project owner</0> and ask them to upgrade their Overleaf plan if you need more compile time.", "tell_the_project_owner_and_ask_them_to_upgrade": "<0>Tell the project owner</0> and ask them to upgrade their Overleaf plan if you need more compile time.",
"tell_the_project_owner_to_upgrade_plan_for_more_compile_time": "<strong>Tell the project owner</strong> and ask them to upgrade their Overleaf plan if you need more compile time.",
"template": "Template", "template": "Template",
"template_approved_by_publisher": "This template has been approved by the publisher", "template_approved_by_publisher": "This template has been approved by the publisher",
"template_description": "Template Description", "template_description": "Template Description",
@ -1904,6 +1918,7 @@
"this_field_is_required": "This field is required", "this_field_is_required": "This field is required",
"this_grants_access_to_features_2": "This grants you access to <0>__appName__</0> <0>__featureType__</0> features.", "this_grants_access_to_features_2": "This grants you access to <0>__appName__</0> <0>__featureType__</0> features.",
"this_is_your_template": "This is your template from your project", "this_is_your_template": "This is your template from your project",
"this_project_compiled_but_soon_might_not": "This project compiled, but soon it might not",
"this_project_exceeded_compile_timeout_limit_on_free_plan": "This project exceeded the compile timeout limit on our free plan.", "this_project_exceeded_compile_timeout_limit_on_free_plan": "This project exceeded the compile timeout limit on our free plan.",
"this_project_is_public": "This project is public and can be edited by anyone with the URL.", "this_project_is_public": "This project is public and can be edited by anyone with the URL.",
"this_project_is_public_read_only": "This project is public and can be viewed but not edited by anyone with the URL", "this_project_is_public_read_only": "This project is public and can be viewed but not edited by anyone with the URL",
@ -2071,6 +2086,8 @@
"upgrade": "Upgrade", "upgrade": "Upgrade",
"upgrade_cc_btn": "Upgrade now, pay after 7 days", "upgrade_cc_btn": "Upgrade now, pay after 7 days",
"upgrade_for_12x_more_compile_time": "Upgrade to get 12x more compile time", "upgrade_for_12x_more_compile_time": "Upgrade to get 12x more compile time",
"upgrade_for_longer_compiles": "Upgrade to increase your timeout limit.",
"upgrade_for_plenty_more_compile_time": "Upgrade to get plenty more compile time.",
"upgrade_now": "Upgrade Now", "upgrade_now": "Upgrade Now",
"upgrade_to_get_feature": "Upgrade to get __feature__, plus:", "upgrade_to_get_feature": "Upgrade to get __feature__, plus:",
"upgrade_to_track_changes": "Upgrade to track changes", "upgrade_to_track_changes": "Upgrade to track changes",
@ -2138,6 +2155,8 @@
"website_status": "Website status", "website_status": "Website status",
"wed_love_you_to_stay": "Wed love you to stay", "wed_love_you_to_stay": "Wed love you to stay",
"welcome_to_sl": "Welcome to __appName__", "welcome_to_sl": "Welcome to __appName__",
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_this_project": "Were in the process of <0>reducing the compile timeout limit</0> on our free plan, which may affect this project in future.",
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_your_project": "Were in the process of <0>reducing the compile timeout limit</0> on our free plan, which may affect your project in future.",
"were_performing_maintenance": "Were performing maintenance on Overleaf and you need to wait a moment. Sorry for any inconvenience. The editor will refresh automatically in __seconds__ seconds.", "were_performing_maintenance": "Were performing maintenance on Overleaf and you need to wait a moment. Sorry for any inconvenience. The editor will refresh automatically in __seconds__ seconds.",
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "Weve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected this project.", "weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "Weve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected this project.",
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "Weve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected your project.", "weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "Weve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected your project.",
@ -2208,6 +2227,7 @@
"you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "You have been removed from this project, and will no longer have access to it. You will be redirected to your project dashboard momentarily.", "you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "You have been removed from this project, and will no longer have access to it. You will be redirected to your project dashboard momentarily.",
"you_introed_high_number": " Youve introduced <0>__numberOfPeople__</0> people to __appName__. Good job!", "you_introed_high_number": " Youve introduced <0>__numberOfPeople__</0> people to __appName__. Good job!",
"you_introed_small_number": " Youve introduced <0>__numberOfPeople__</0> person to __appName__. Good job, but can you get some more?", "you_introed_small_number": " Youve introduced <0>__numberOfPeople__</0> person to __appName__. Good job, but can you get some more?",
"you_may_be_able_to_fix_issues_to_speed_up_the_compile": "You may be able to fix issues to <0>speed up the compile</0>.",
"you_need_to_configure_your_sso_settings": "You need to configure and test your SSO settings before enabling SSO", "you_need_to_configure_your_sso_settings": "You need to configure and test your SSO settings before enabling SSO",
"you_not_introed_anyone_to_sl": "Youve not introduced anyone to __appName__ yet. Get sharing!", "you_not_introed_anyone_to_sl": "Youve not introduced anyone to __appName__ yet. Get sharing!",
"you_plus_1": "You + 1", "you_plus_1": "You + 1",
@ -2235,6 +2255,7 @@
"your_password_has_been_successfully_changed": "Your password has been successfully changed", "your_password_has_been_successfully_changed": "Your password has been successfully changed",
"your_plan": "Your plan", "your_plan": "Your plan",
"your_plan_is_changing_at_term_end": "Your plan is changing to <0>__pendingPlanName__</0> at the end of the current billing period.", "your_plan_is_changing_at_term_end": "Your plan is changing to <0>__pendingPlanName__</0> at the end of the current billing period.",
"your_project_compiled_but_soon_might_not": "Your project compiled, but soon it might not",
"your_project_exceeded_compile_timeout_limit_on_free_plan": "Your project exceeded the compile timeout limit on our free plan.", "your_project_exceeded_compile_timeout_limit_on_free_plan": "Your project exceeded the compile timeout limit on our free plan.",
"your_project_near_compile_timeout_limit": "Your project is near the compile timeout limit for our free plan.", "your_project_near_compile_timeout_limit": "Your project is near the compile timeout limit for our free plan.",
"your_projects": "Your Projects", "your_projects": "Your Projects",

View file

@ -1,57 +0,0 @@
#!/usr/bin/env node
const minimist = require('minimist')
const {
db,
READ_PREFERENCE_SECONDARY,
waitForDb,
} = require('../app/src/infrastructure/mongodb')
const { batchedUpdate } = require('./helpers/batchedUpdate')
async function logCount() {
const count60s = await db.users.countDocuments(
{ 'features.compileTimeout': { $lte: 60, $ne: 20 } },
{ readPreference: READ_PREFERENCE_SECONDARY }
)
const count20s = await db.users.countDocuments(
{ 'features.compileTimeout': 20 },
{ readPreference: READ_PREFERENCE_SECONDARY }
)
console.log(`Found ${count60s} users with compileTimeout <= 60s && != 20s`)
console.log(`Found ${count20s} users with compileTimeout == 20s`)
}
const main = async ({ COMMIT }) => {
console.time('Script Duration')
await waitForDb()
await logCount()
if (COMMIT) {
const nModified = await batchedUpdate(
'users',
{ 'features.compileTimeout': { $lte: 60, $ne: 20 } },
{ $set: { 'features.compileTimeout': 20 } }
)
console.log(`Updated ${nModified} records`)
}
console.timeEnd('Script Duration')
}
const setup = () => {
const argv = minimist(process.argv.slice(2))
const COMMIT = argv.commit !== undefined
if (!COMMIT) {
console.warn('Doing dry run. Add --commit to commit changes')
}
return { COMMIT }
}
main(setup())
.catch(err => {
console.error(err)
process.exit(1)
})
.then(() => process.exit(0))

View file

@ -1,134 +0,0 @@
const { expect } = require('chai')
const { promisify } = require('node:util')
const { exec } = require('node:child_process')
const logger = require('@overleaf/logger')
const { db } = require('../../../app/src/infrastructure/mongodb')
async function runScript(args = []) {
try {
return await promisify(exec)(
['node', 'scripts/migration_compile_timeout_60s_to_20s.js', ...args].join(
' '
)
)
} catch (error) {
logger.error({ error }, 'script failed')
throw error
}
}
describe('MigrateUserFeatureTimeoutTests', function () {
const usersInput = {
noFeatures: {},
noFeatureTimeout: { features: {} },
timeout10s: { features: { compileTimeout: 10, other: 'val1' }, bar: '1' },
timeout20s: { features: { compileTimeout: 20, other: 'val2' }, bar: '2' },
timeout30s: { features: { compileTimeout: 30, other: 'val3' }, bar: '3' },
timeout60s: { features: { compileTimeout: 60, other: 'val4' }, bar: '4' },
timeout120s: { features: { compileTimeout: 120, other: 'val5' }, bar: '5' },
timeout180s: { features: { compileTimeout: 180, other: 'val6' }, bar: '6' },
}
const usersKeys = Object.keys(usersInput)
const userIds = {}
beforeEach('insert users', async function () {
const usersInsertedValues = await db.users.insertMany(
usersKeys.map(key => ({
...usersInput[key],
email: `${key}@example.com`,
}))
)
usersKeys.forEach(
(key, index) => (userIds[key] = usersInsertedValues.insertedIds[index])
)
})
it('gives correct counts in dry mode', async function () {
const users = await db.users.find().toArray()
expect(users).to.have.lengthOf(usersKeys.length)
const result = await runScript([])
expect(result.stderr).to.contain(
'Doing dry run. Add --commit to commit changes'
)
expect(result.stdout).to.contain(
'Found 3 users with compileTimeout <= 60s && != 20s'
)
expect(result.stdout).to.contain('Found 1 users with compileTimeout == 20s')
expect(result.stdout).not.to.contain('Updated')
const usersAfter = await db.users.find().toArray()
expect(usersAfter).to.deep.equal(users)
})
it("updates users compileTimeout when '--commit' is set", async function () {
const users = await db.users.find().toArray()
expect(users).to.have.lengthOf(usersKeys.length)
const result = await runScript(['--commit'])
expect(result.stdout).to.contain(
'Found 3 users with compileTimeout <= 60s && != 20s'
)
expect(result.stdout).to.contain('Found 1 users with compileTimeout == 20s')
expect(result.stdout).to.contain('Updated 3 records')
const usersAfter = await db.users.find().toArray()
expect(usersAfter).to.deep.equal([
{ _id: userIds.noFeatures, email: 'noFeatures@example.com' },
{
_id: userIds.noFeatureTimeout,
email: 'noFeatureTimeout@example.com',
features: {},
},
{
_id: userIds.timeout10s,
email: 'timeout10s@example.com',
features: { compileTimeout: 20, other: 'val1' },
bar: '1',
},
{
_id: userIds.timeout20s,
email: 'timeout20s@example.com',
features: { compileTimeout: 20, other: 'val2' },
bar: '2',
},
{
_id: userIds.timeout30s,
email: 'timeout30s@example.com',
features: { compileTimeout: 20, other: 'val3' },
bar: '3',
},
{
_id: userIds.timeout60s,
email: 'timeout60s@example.com',
features: { compileTimeout: 20, other: 'val4' },
bar: '4',
},
{
_id: userIds.timeout120s,
email: 'timeout120s@example.com',
features: { compileTimeout: 120, other: 'val5' },
bar: '5',
},
{
_id: userIds.timeout180s,
email: 'timeout180s@example.com',
features: { compileTimeout: 180, other: 'val6' },
bar: '6',
},
])
const result2 = await runScript([])
expect(result2.stdout).to.contain(
'Found 0 users with compileTimeout <= 60s && != 20s'
)
expect(result2.stdout).to.contain(
'Found 4 users with compileTimeout == 20s'
)
})
})

View file

@ -314,7 +314,7 @@ describe('<PdfPreview/>', function () {
'project-too-large': 'Project too large', 'project-too-large': 'Project too large',
'rate-limited': 'Compile rate limit hit', 'rate-limited': 'Compile rate limit hit',
terminated: 'Compilation cancelled', terminated: 'Compilation cancelled',
timedout: 'Your compile timed out', timedout: 'Timed out',
'too-recently-compiled': 'too-recently-compiled':
'This project was compiled very recently, so this compile has been skipped.', 'This project was compiled very recently, so this compile has been skipped.',
unavailable: unavailable:

View file

@ -120,6 +120,7 @@ describe('ClsiManager', function () {
}, },
clsi: { clsi: {
url: `http://${CLSI_HOST}`, url: `http://${CLSI_HOST}`,
defaultBackendClass: 'e2',
submissionBackendClass: 'n2d', submissionBackendClass: 'n2d',
}, },
clsi_priority: { clsi_priority: {

View file

@ -32,6 +32,7 @@ describe('CompileController', function () {
apis: { apis: {
clsi: { clsi: {
url: 'http://clsi.example.com', url: 'http://clsi.example.com',
defaultBackendClass: 'e2',
submissionBackendClass: 'n2d', submissionBackendClass: 'n2d',
}, },
clsi_priority: { clsi_priority: {

View file

@ -23,7 +23,7 @@ describe('CompileManager', function () {
requires: { requires: {
'@overleaf/settings': (this.settings = { '@overleaf/settings': (this.settings = {
apis: { apis: {
clsi: { submissionBackendClass: 'n2d' }, clsi: { defaultBackendClass: 'e2', submissionBackendClass: 'n2d' },
}, },
redis: { web: { host: 'localhost', port: 42 } }, redis: { web: { host: 'localhost', port: 42 } },
rateLimit: { autoCompile: {} }, rateLimit: { autoCompile: {} },
@ -235,8 +235,9 @@ describe('CompileManager', function () {
.calledWith(null, { .calledWith(null, {
timeout: this.timeout, timeout: this.timeout,
compileGroup: this.group, compileGroup: this.group,
compileBackendClass: 'c2d', compileBackendClass: 'e2',
ownerAnalyticsId: 'abc', ownerAnalyticsId: 'abc',
showFasterCompilesFeedbackUI: false,
}) })
.should.equal(true) .should.equal(true)
}) })
@ -302,7 +303,7 @@ describe('CompileManager', function () {
this.callback this.callback
) )
this.callback this.callback
.calledWith(null, sinon.match({ timeout: 20 })) .calledWith(null, sinon.match({ timeout: 60 }))
.should.equal(true) .should.equal(true)
}) })
describe('user is in the compile-timeout-20s-existing-users treatment', function () { describe('user is in the compile-timeout-20s-existing-users treatment', function () {
@ -387,12 +388,20 @@ describe('CompileManager', function () {
this.callback this.callback
) )
this.callback this.callback
.calledWith(null, sinon.match({ timeout: 20 })) .calledWith(null, sinon.match({ timeout: 60 }))
.should.equal(true) .should.equal(true)
}) })
}) })
describe('user signed up after the second phase rollout', function () { describe('user signed up after the second phase rollout', function () {
beforeEach(function () {
const signUpDate = new Date(
this.CompileManager.NEW_COMPILE_TIMEOUT_ENFORCED_CUTOFF_DEFAULT_BASELINE
)
signUpDate.setDate(signUpDate.getDate() + 1)
this.user.signUpDate = signUpDate
})
it('should reduce compile timeout to 20s', function () { it('should reduce compile timeout to 20s', function () {
this.CompileManager.getProjectCompileLimits( this.CompileManager.getProjectCompileLimits(
this.project_id, this.project_id,
@ -432,12 +441,13 @@ describe('CompileManager', function () {
variant: 'default', variant: 'default',
}) })
}) })
it('should return the n2d class and disable the ui', function (done) { it('should return the e2 class and disable the ui', function (done) {
this.CompileManager.getProjectCompileLimits( this.CompileManager.getProjectCompileLimits(
this.project_id, this.project_id,
(err, { compileBackendClass }) => { (err, { compileBackendClass, showFasterCompilesFeedbackUI }) => {
if (err) return done(err) if (err) return done(err)
expect(compileBackendClass).to.equal('n2d') expect(compileBackendClass).to.equal('e2')
expect(showFasterCompilesFeedbackUI).to.equal(false)
done() done()
} }
) )
@ -453,9 +463,10 @@ describe('CompileManager', function () {
it('should return the n2d class and disable the ui', function (done) { it('should return the n2d class and disable the ui', function (done) {
this.CompileManager.getProjectCompileLimits( this.CompileManager.getProjectCompileLimits(
this.project_id, this.project_id,
(err, { compileBackendClass }) => { (err, { compileBackendClass, showFasterCompilesFeedbackUI }) => {
if (err) return done(err) if (err) return done(err)
expect(compileBackendClass).to.equal('n2d') expect(compileBackendClass).to.equal('n2d')
expect(showFasterCompilesFeedbackUI).to.equal(false)
done() done()
} }
) )
@ -478,9 +489,10 @@ describe('CompileManager', function () {
it('should return the default class and disable ui', function (done) { it('should return the default class and disable ui', function (done) {
this.CompileManager.getProjectCompileLimits( this.CompileManager.getProjectCompileLimits(
this.project_id, this.project_id,
(err, { compileBackendClass }) => { (err, { compileBackendClass, showFasterCompilesFeedbackUI }) => {
if (err) return done(err) if (err) return done(err)
expect(compileBackendClass).to.equal('c2d') expect(compileBackendClass).to.equal('e2')
expect(showFasterCompilesFeedbackUI).to.equal(false)
done() done()
} }
) )
@ -498,9 +510,10 @@ describe('CompileManager', function () {
it('should return the default class and enable ui', function (done) { it('should return the default class and enable ui', function (done) {
this.CompileManager.getProjectCompileLimits( this.CompileManager.getProjectCompileLimits(
this.project_id, this.project_id,
(err, { compileBackendClass }) => { (err, { compileBackendClass, showFasterCompilesFeedbackUI }) => {
if (err) return done(err) if (err) return done(err)
expect(compileBackendClass).to.equal('c2d') expect(compileBackendClass).to.equal('e2')
expect(showFasterCompilesFeedbackUI).to.equal(true)
done() done()
} }
) )
@ -517,9 +530,10 @@ describe('CompileManager', function () {
it('should return the c2d class and enable ui', function (done) { it('should return the c2d class and enable ui', function (done) {
this.CompileManager.getProjectCompileLimits( this.CompileManager.getProjectCompileLimits(
this.project_id, this.project_id,
(err, { compileBackendClass }) => { (err, { compileBackendClass, showFasterCompilesFeedbackUI }) => {
if (err) return done(err) if (err) return done(err)
expect(compileBackendClass).to.equal('c2d') expect(compileBackendClass).to.equal('c2d')
expect(showFasterCompilesFeedbackUI).to.equal(true)
done() done()
} }
) )