From 0a79ac75ff1b97a707eac3e7ed5937bc3ae68018 Mon Sep 17 00:00:00 2001 From: MoxAmber Date: Thu, 12 Jun 2025 10:57:35 +0100 Subject: [PATCH] Merge pull request #26312 from overleaf/as-10s-compile-timeout [web] 10s Compile Timeout: Warning Phase GitOrigin-RevId: 749baad646fa7ef0d3a8e14fbbb5edec7b227ed3 --- .../src/Features/Project/ProjectController.js | 10 ++ .../web/app/views/project/editor/_meta.pug | 1 + .../web/frontend/extracted-translations.json | 15 ++- .../components/compile-time-changing-soon.tsx | 122 ++++++++++++++++++ ...pile-time-warning-upgrade-prompt-inner.tsx | 9 +- .../compile-time-warning-upgrade-prompt.tsx | 90 +++++++++++-- .../components/pdf-logs-viewer.tsx | 5 +- .../components/pdf-preview-pane.tsx | 6 +- ...imeout-message-after-paywall-dismissal.tsx | 77 ++++++++--- .../timeout-upgrade-paywall-prompt.tsx | 51 +++++--- .../components/timeout-upgrade-prompt-new.tsx | 60 +++++---- .../web/frontend/js/main/account-upgrade.ts | 11 +- .../components/start-free-trial-button.tsx | 9 +- .../js/shared/components/upgrade-prompt.tsx | 2 +- services/web/frontend/js/utils/meta.ts | 5 +- services/web/locales/da.json | 1 + services/web/locales/en.json | 19 ++- services/web/locales/ru.json | 2 + services/web/locales/zh-CN.json | 2 + .../pdf-preview-detached-root.spec.tsx | 4 + .../pdf-preview/pdf-preview.spec.tsx | 4 + 21 files changed, 411 insertions(+), 94 deletions(-) create mode 100644 services/web/frontend/js/features/pdf-preview/components/compile-time-changing-soon.tsx diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 00c80823e5..08307c2ab9 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -765,6 +765,12 @@ const _ProjectController = { isOverleafAssistBundleEnabled && (await ProjectController._getAddonPrices(req, res)) + const reducedTimeoutWarning = + await SplitTestHandler.promises.getAssignmentForUser( + project.owner_ref, + '10s-timeout-warning' + ) + let planCode = subscription?.planCode if (!planCode && !userInNonIndividualSub) { planCode = 'personal' @@ -879,6 +885,10 @@ const _ProjectController = { paywallPlans, customerIoEnabled, addonPrices, + compileSettings: { + reducedTimeoutWarning: reducedTimeoutWarning?.variant, + compileTimeout: ownerFeatures?.compileTimeout, + }, }) timer.done() } catch (err) { diff --git a/services/web/app/views/project/editor/_meta.pug b/services/web/app/views/project/editor/_meta.pug index e8539e1d76..2c0af8dec8 100644 --- a/services/web/app/views/project/editor/_meta.pug +++ b/services/web/app/views/project/editor/_meta.pug @@ -43,6 +43,7 @@ meta(name="ol-shouldLoadHotjar" data-type="boolean" content=shouldLoadHotjar) meta(name="ol-odcRole" data-type="string" content=odcRole) meta(name="ol-isPaywallChangeCompileTimeoutEnabled" data-type="boolean" content=isPaywallChangeCompileTimeoutEnabled) meta(name='ol-customerIoEnabled' data-type="boolean" content=customerIoEnabled) +meta(name='ol-compileSettings' data-type="json" content=compileSettings) if(isPaywallChangeCompileTimeoutEnabled) //- expose plans info to show prices in paywall-change-compile-timeout test meta(name="ol-paywallPlans", data-type="json" content=paywallPlans) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index e83e822046..e38bfbe44b 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -1,9 +1,9 @@ { "0_free_suggestions": "", - "12x_more_compile_time": "", "1_2_width": "", "1_4_width": "", "1_free_suggestion": "", + "24x_more_compile_time": "", "3_4_width": "", "About": "", "Account": "", @@ -133,6 +133,7 @@ "an_email_has_already_been_sent_to": "", "an_error_occured_while_restoring_project": "", "an_error_occurred_when_verifying_the_coupon_code": "", + "and_upgrade_for_compile_time": "", "annual_discount": "", "anonymous": "", "anyone_with_link_can_edit": "", @@ -846,6 +847,7 @@ "integrations": "", "integrations_like_github": "", "interested_in_cheaper_personal_plan": "", + "introducing_shorter_compile_timeout": "", "invalid_confirmation_code": "", "invalid_email": "", "invalid_file_name": "", @@ -1160,7 +1162,6 @@ "organization_name": "", "organize_tags": "", "other": "", - "other_causes_of_compile_timeouts": "", "other_logs_and_files": "", "other_output_files": "", "our_team_will_get_back_to_you_shortly": "", @@ -1341,6 +1342,8 @@ "reactivate_subscription": "", "read_lines_from_path": "", "read_more": "", + "read_more_about_compile_timeout_changes": "", + "read_more_about_fix_prevent_timeout": "", "read_more_about_free_compile_timeouts_servers": "", "read_only_dropbox_sync_message": "", "read_only_token": "", @@ -1787,6 +1790,7 @@ "this_is_a_new_feature": "", "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "", "this_project_already_has_maximum_collaborators": "", + "this_project_compiled_but_soon_might_not": "", "this_project_contains_a_file_called_output": "", "this_project_exceeded_collaborator_limit": "", "this_project_exceeded_compile_timeout_limit_on_free_plan": "", @@ -1971,7 +1975,7 @@ "updating": "", "upgrade": "", "upgrade_cc_btn": "", - "upgrade_for_12x_more_compile_time": "", + "upgrade_for_more_compile_time": "", "upgrade_my_plan": "", "upgrade_now": "", "upgrade_plan": "", @@ -2069,8 +2073,7 @@ "were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "", "were_performing_maintenance": "", "were_redesigning_our_editor_to_make_it_easier_to_use": "", - "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": "", + "were_reducing_compile_timeout": "", "what_do_you_need_help_with": "", "what_does_this_mean": "", "what_does_this_mean_for_you": "", @@ -2141,6 +2144,7 @@ "you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "", "you_have_x_licenses_and_your_plan_supports_up_to_y": "", "you_have_x_licenses_on_your_subscription": "", + "you_may_be_able_to_fix_issues_to_speed_up_the_compile": "", "you_need_to_configure_your_sso_settings": "", "you_unpaused_your_subscription": "", "you_will_be_able_to_reassign_subscription": "", @@ -2175,6 +2179,7 @@ "your_plan_is_limited_to_n_editors": "", "your_plan_is_limited_to_n_editors_plural": "", "your_premium_plan_is_paused": "", + "your_project_compiled_but_soon_might_not": "", "your_project_exceeded_collaborator_limit": "", "your_project_exceeded_compile_timeout_limit_on_free_plan": "", "your_project_near_compile_timeout_limit": "", diff --git a/services/web/frontend/js/features/pdf-preview/components/compile-time-changing-soon.tsx b/services/web/frontend/js/features/pdf-preview/components/compile-time-changing-soon.tsx new file mode 100644 index 0000000000..9d9bc53711 --- /dev/null +++ b/services/web/frontend/js/features/pdf-preview/components/compile-time-changing-soon.tsx @@ -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' + +export const CompileTimeoutChangingSoon: FC<{ + isProjectOwner?: boolean + handleDismissChangingSoon: () => void + segmentation?: eventTracking.Segmentation +}> = ({ isProjectOwner = false, handleDismissChangingSoon, segmentation }) => { + const { t } = useTranslation() + + const sendInfoClickEvent = () => { + eventTracking.sendMB('paywall-info-click', { + 'paywall-type': 'compile-time-warning', + ...segmentation, + content: 'blog', + }) + } + + const compileTimeoutChangesBlogLink = ( + /* eslint-disable-next-line jsx-a11y/anchor-has-content */ + + ) + + const fixingCompileTimeoutsLearnLink = ( + /* eslint-disable-next-line jsx-a11y/anchor-has-content */ + + ) + + if (isProjectOwner) { + return ( + + {t('start_free_trial_without_exclamation')} + + } + ariaLive="polite" + content={ +
+

+ +

+

+ {' '} + }} + /> +

+
+ } + title={t('your_project_compiled_but_soon_might_not')} + type="warning" + isActionBelowContent + isDismissible + onDismiss={handleDismissChangingSoon} + /> + ) + } + + return ( + +

+ {' '} + +

+

+ , + ]} + /> +

+ + } + title={t('this_project_compiled_but_soon_might_not')} + type="warning" + isDismissible + onDismiss={handleDismissChangingSoon} + /> + ) +} diff --git a/services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner.tsx b/services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner.tsx index 09cfb5ddee..463ba55be8 100644 --- a/services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner.tsx @@ -1,21 +1,24 @@ import Notification from '@/shared/components/notification' import StartFreeTrialButton from '@/shared/components/start-free-trial-button' +import * as eventTracking from '../../../infrastructure/event-tracking' import { useTranslation } from 'react-i18next' import { FC } from 'react' export const CompileTimeWarningUpgradePromptInner: FC<{ handleDismissWarning: () => void -}> = ({ handleDismissWarning }) => { + segmentation: eventTracking.Segmentation +}> = ({ handleDismissWarning, segmentation }) => { const { t } = useTranslation() return ( {t('start_free_trial_without_exclamation')} @@ -27,7 +30,7 @@ export const CompileTimeWarningUpgradePromptInner: FC<{
{t('your_project_near_compile_timeout_limit')}
- {t('upgrade_for_12x_more_compile_time')} + {t('upgrade_for_more_compile_time')} {'. '} } diff --git a/services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx b/services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx index 6595df854c..76f0108ee6 100644 --- a/services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx @@ -1,23 +1,56 @@ -import { memo, useCallback, useEffect, useState } from 'react' +import { memo, useCallback, useEffect, useMemo, useState } from 'react' import * as eventTracking from '@/infrastructure/event-tracking' import { useDetachCompileContext } from '@/shared/context/detach-compile-context' import usePersistedState from '@/shared/hooks/use-persisted-state' import { CompileTimeWarningUpgradePromptInner } from '@/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner' import getMeta from '@/utils/meta' +import { CompileTimeoutChangingSoon } from './compile-time-changing-soon' function CompileTimeWarningUpgradePrompt() { const { isProjectOwner, deliveryLatencies, compiling, showLogs, error } = useDetachCompileContext() const [showWarning, setShowWarning] = useState(false) + const [showChangingSoon, setShowChangingSoon] = useState(false) const [dismissedUntilWarning, setDismissedUntilWarning] = usePersistedState< Date | undefined >(`has-dismissed-10s-compile-time-warning-until`) + const { reducedTimeoutWarning } = getMeta('ol-compileSettings') + const warningThreshold = reducedTimeoutWarning === 'enabled' ? 7 : 10 + + const sharedSegmentation = useMemo( + () => ({ + '10s-timeout-warning': reducedTimeoutWarning, + 'is-owner': isProjectOwner, + }), + [isProjectOwner, reducedTimeoutWarning] + ) + + const warningSegmentation = useMemo( + () => ({ + content: 'warning', + time: warningThreshold, + ...sharedSegmentation, + }), + [sharedSegmentation, warningThreshold] + ) + + const changingSoonSegmentation = useMemo( + () => ({ + content: 'changes', + time: 10, + ...sharedSegmentation, + }), + [sharedSegmentation] + ) const handleNewCompile = useCallback( (compileTime: number) => { setShowWarning(false) - if (compileTime > 10000) { + setShowChangingSoon(false) + if (reducedTimeoutWarning === 'enabled' && compileTime > 10000) { + setShowChangingSoon(true) + } else if (compileTime > warningThreshold * 1000) { if (isProjectOwner) { if ( !dismissedUntilWarning || @@ -25,26 +58,52 @@ function CompileTimeWarningUpgradePrompt() { ) { setShowWarning(true) eventTracking.sendMB('compile-time-warning-displayed', { - time: 10, + time: warningThreshold, isProjectOwner, }) } } } }, - [isProjectOwner, dismissedUntilWarning] + [ + isProjectOwner, + dismissedUntilWarning, + reducedTimeoutWarning, + warningThreshold, + ] ) const handleDismissWarning = useCallback(() => { eventTracking.sendMB('compile-time-warning-dismissed', { - time: 10, + time: warningThreshold, isProjectOwner, }) + eventTracking.sendMB('paywall-dismiss', { + 'paywall-type': 'compile-time-warning', + content: 'warning', + time: warningThreshold, + ...sharedSegmentation, + }) setShowWarning(false) const until = new Date() until.setDate(until.getDate() + 1) // 1 day setDismissedUntilWarning(until) - }, [isProjectOwner, setDismissedUntilWarning]) + }, [ + isProjectOwner, + setDismissedUntilWarning, + warningThreshold, + sharedSegmentation, + ]) + + const handleDismissChangingSoon = useCallback(() => { + eventTracking.sendMB('paywall-dismiss', { + 'paywall-type': 'compile-time-warning', + time: 10, + content: 'changes', + ...sharedSegmentation, + }) + setShowChangingSoon(false) + }, [sharedSegmentation]) useEffect(() => { if (compiling || error || showLogs) return @@ -55,21 +114,32 @@ function CompileTimeWarningUpgradePrompt() { return null } - if (compiling || error || showLogs) { + if ( + compiling || + error || + showLogs || + !deliveryLatencies.compileTimeServerE2E + ) { return null } - if (!showWarning) { + if (!showWarning && !showChangingSoon) { return null } - // if showWarning is true then the 10s warning is shown - return (
{showWarning && isProjectOwner && ( + )} + {showChangingSoon && ( + )}
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx index ec834432fe..f9fbcae42a 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx @@ -21,7 +21,6 @@ function PdfLogsViewer({ alwaysVisible = false }: { alwaysVisible?: boolean }) { const { codeCheckFailed, error, - hasShortCompileTimeout, logEntries, rawLog, validationIssues, @@ -32,6 +31,8 @@ function PdfLogsViewer({ alwaysVisible = false }: { alwaysVisible?: boolean }) { const { loadingError } = usePdfPreviewContext() + const { compileTimeout } = getMeta('ol-compileSettings') + const { t } = useTranslation() const [ @@ -58,7 +59,7 @@ function PdfLogsViewer({ alwaysVisible = false }: { alwaysVisible?: boolean }) { {loadingError && } - {hasShortCompileTimeout && error === 'timedout' ? ( + {compileTimeout < 60 && error === 'timedout' ? ( isCompileTimeoutPaywallDisplay ? ( } - {hasShortCompileTimeout && } + {compileTimeout < 60 && } }>
diff --git a/services/web/frontend/js/features/pdf-preview/components/timeout-message-after-paywall-dismissal.tsx b/services/web/frontend/js/features/pdf-preview/components/timeout-message-after-paywall-dismissal.tsx index 3a1d66fd3d..64ef0fbfc1 100644 --- a/services/web/frontend/js/features/pdf-preview/components/timeout-message-after-paywall-dismissal.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/timeout-message-after-paywall-dismissal.tsx @@ -1,20 +1,31 @@ import getMeta from '@/utils/meta' import { Trans, useTranslation } from 'react-i18next' -import { memo, useEffect } from 'react' +import { memo, useMemo } from 'react' import { useDetachCompileContext } from '@/shared/context/detach-compile-context' import StartFreeTrialButton from '@/shared/components/start-free-trial-button' import MaterialIcon from '@/shared/components/material-icon' import * as eventTracking from '@/infrastructure/event-tracking' import PdfLogEntry from './pdf-log-entry' -function TimeoutMessageAfterPaywallDismissal() { - const { lastCompileOptions, isProjectOwner } = useDetachCompileContext() +type TimeoutMessageProps = { + segmentation?: eventTracking.Segmentation +} +function TimeoutMessageAfterPaywallDismissal({ + segmentation, +}: TimeoutMessageProps) { + const { lastCompileOptions, isProjectOwner } = useDetachCompileContext() return (
- + {getMeta('ol-ExposedSettings').enableSubscriptions && ( - + )}
) @@ -22,26 +33,22 @@ function TimeoutMessageAfterPaywallDismissal() { type CompileTimeoutProps = { isProjectOwner: boolean + segmentation?: eventTracking.Segmentation } const CompileTimeout = memo(function CompileTimeout({ isProjectOwner, + segmentation, }: CompileTimeoutProps) { const { t } = useTranslation() - useEffect(() => { - eventTracking.sendMB('paywall-prompt', { - 'paywall-type': 'compile-timeout', + const eventSegmentation = useMemo( + () => ({ + ...segmentation, 'paywall-version': 'secondary', - }) - }, []) - - function onPaywallClick() { - eventTracking.sendMB('paywall-click', { - 'paywall-type': 'compile-timeout', - 'paywall-version': 'secondary', - }) - } + }), + [segmentation] + ) return ( {t('try_for_free')} @@ -104,18 +111,50 @@ const CompileTimeout = memo(function CompileTimeout({ type PreventTimeoutHelpMessageProps = { lastCompileOptions: any + segmentation?: eventTracking.Segmentation } const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({ lastCompileOptions, + segmentation, }: PreventTimeoutHelpMessageProps) { const { t } = useTranslation() + function sendInfoClickEvent() { + eventTracking.sendMB('paywall-info-click', { + 'paywall-type': 'compile-timeout', + content: 'blog', + ...segmentation, + }) + } + + const compileTimeoutChangesBlogLink = ( + /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */ +
+ ) + return ( + {segmentation?.['10s-timeout-warning'] === 'enabled' && ( +

+ + + +

+ )} +

{t('common_causes_of_compile_timeouts_include')}: