Просмотр исходного кода

Merge pull request #26312 from overleaf/as-10s-compile-timeout

[web] 10s Compile Timeout: Warning Phase

GitOrigin-RevId: 749baad646fa7ef0d3a8e14fbbb5edec7b227ed3
MoxAmber 1 год назад
Родитель
Сommit
0a79ac75ff
21 измененных файлов с 411 добавлено и 94 удалено
  1. 10 0
      services/web/app/src/Features/Project/ProjectController.js
  2. 1 0
      services/web/app/views/project/editor/_meta.pug
  3. 10 5
      services/web/frontend/extracted-translations.json
  4. 122 0
      services/web/frontend/js/features/pdf-preview/components/compile-time-changing-soon.tsx
  5. 6 3
      services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner.tsx
  6. 80 10
      services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx
  7. 3 2
      services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx
  8. 4 2
      services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx
  9. 58 19
      services/web/frontend/js/features/pdf-preview/components/timeout-message-after-paywall-dismissal.tsx
  10. 36 15
      services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-paywall-prompt.tsx
  11. 36 24
      services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx
  12. 9 2
      services/web/frontend/js/main/account-upgrade.ts
  13. 6 3
      services/web/frontend/js/shared/components/start-free-trial-button.tsx
  14. 1 1
      services/web/frontend/js/shared/components/upgrade-prompt.tsx
  15. 4 1
      services/web/frontend/js/utils/meta.ts
  16. 1 0
      services/web/locales/da.json
  17. 12 7
      services/web/locales/en.json
  18. 2 0
      services/web/locales/ru.json
  19. 2 0
      services/web/locales/zh-CN.json
  20. 4 0
      services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx
  21. 4 0
      services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx

+ 10 - 0
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) {

+ 1 - 0
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)

+ 10 - 5
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": "",

+ 122 - 0
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 */
+    <a
+      aria-label={t('read_more_about_compile_timeout_changes')}
+      href="/blog/changes-to-free-compile-timeout"
+      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
+            segmentation={segmentation}
+            source="compile-time-warning"
+            buttonProps={{
+              variant: 'secondary',
+              size: 'sm',
+            }}
+          >
+            {t('start_free_trial_without_exclamation')}
+          </StartFreeTrialButton>
+        }
+        ariaLive="polite"
+        content={
+          <div>
+            <p>
+              <Trans
+                i18nKey="introducing_shorter_compile_timeout"
+                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_upgrade_for_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="introducing_shorter_compile_timeout"
+              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_and_ask_them_to_upgrade"
+              components={[
+                // eslint-disable-next-line react/jsx-key
+                <strong />,
+              ]}
+            />
+          </p>
+        </div>
+      }
+      title={t('this_project_compiled_but_soon_might_not')}
+      type="warning"
+      isDismissible
+      onDismiss={handleDismissChangingSoon}
+    />
+  )
+}

+ 6 - 3
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 (
     <Notification
       action={
         <StartFreeTrialButton
-          variant="new-10s"
           source="compile-time-warning"
+          segmentation={segmentation}
           buttonProps={{
             variant: 'secondary',
+            size: 'sm',
           }}
         >
           {t('start_free_trial_without_exclamation')}
@@ -27,7 +30,7 @@ export const CompileTimeWarningUpgradePromptInner: FC<{
           <div>
             <span>{t('your_project_near_compile_timeout_limit')}</span>
           </div>
-          <strong>{t('upgrade_for_12x_more_compile_time')}</strong>
+          <strong>{t('upgrade_for_more_compile_time')}</strong>
           {'. '}
         </div>
       }

+ 80 - 10
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 (
     <div>
       {showWarning && isProjectOwner && (
         <CompileTimeWarningUpgradePromptInner
           handleDismissWarning={handleDismissWarning}
+          segmentation={warningSegmentation}
+        />
+      )}
+      {showChangingSoon && (
+        <CompileTimeoutChangingSoon
+          isProjectOwner={isProjectOwner}
+          handleDismissChangingSoon={handleDismissChangingSoon}
+          segmentation={changingSoonSegmentation}
         />
       )}
     </div>

+ 3 - 2
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 && <PdfPreviewError error="pdf-viewer-loading-error" />}
 
-        {hasShortCompileTimeout && error === 'timedout' ? (
+        {compileTimeout < 60 && error === 'timedout' ? (
           isCompileTimeoutPaywallDisplay ? (
             <TimeoutUpgradePaywallPrompt
               setIsShowingPrimary={setIsShowingPrimaryCompileTimeoutPaywall}

+ 4 - 2
services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx

@@ -13,9 +13,11 @@ import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-er
 import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
 import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
 import PdfCodeCheckFailedBanner from '@/features/ide-redesign/components/pdf-preview/pdf-code-check-failed-banner'
+import getMeta from '@/utils/meta'
 
 function PdfPreviewPane() {
-  const { pdfUrl, hasShortCompileTimeout } = useCompileContext()
+  const { pdfUrl } = useCompileContext()
+  const { compileTimeout } = getMeta('ol-compileSettings')
   const classes = classNames('pdf', 'full-size', {
     'pdf-empty': !pdfUrl,
   })
@@ -35,7 +37,7 @@ function PdfPreviewPane() {
         )}
         {newEditor && <PdfCodeCheckFailedBanner />}
         <PdfPreviewMessages>
-          {hasShortCompileTimeout && <CompileTimeWarningUpgradePrompt />}
+          {compileTimeout < 60 && <CompileTimeWarningUpgradePrompt />}
         </PdfPreviewMessages>
         <Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
           <div className="pdf-viewer">

+ 58 - 19
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 (
     <div className="website-redesign timeout-upgrade-paywall-prompt">
-      <CompileTimeout isProjectOwner={isProjectOwner} />
+      <CompileTimeout
+        isProjectOwner={isProjectOwner}
+        segmentation={segmentation}
+      />
       {getMeta('ol-ExposedSettings').enableSubscriptions && (
-        <PreventTimeoutHelpMessage lastCompileOptions={lastCompileOptions} />
+        <PreventTimeoutHelpMessage
+          lastCompileOptions={lastCompileOptions}
+          segmentation={segmentation}
+        />
       )}
     </div>
   )
@@ -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',
-      'paywall-version': 'secondary',
-    })
-  }, [])
-
-  function onPaywallClick() {
-    eventTracking.sendMB('paywall-click', {
-      'paywall-type': 'compile-timeout',
+  const eventSegmentation = useMemo(
+    () => ({
+      ...segmentation,
       'paywall-version': 'secondary',
-    })
-  }
+    }),
+    [segmentation]
+  )
 
   return (
     <PdfLogEntry
@@ -86,7 +93,7 @@ const CompileTimeout = memo(function CompileTimeout({
                 <StartFreeTrialButton
                   source="compile-timeout"
                   buttonProps={{ variant: 'secondary' }}
-                  handleClick={onPaywallClick}
+                  segmentation={eventSegmentation}
                 >
                   {t('try_for_free')}
                 </StartFreeTrialButton>
@@ -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 */
+    <a
+      aria-label={t('read_more_about_free_compile_timeouts_servers')}
+      href="/blog/changes-to-free-compile-timeout"
+      rel="noopener noreferrer"
+      target="_blank"
+      onClick={sendInfoClickEvent}
+    />
+  )
+
   return (
     <PdfLogEntry
-      headerTitle={t('other_causes_of_compile_timeouts')}
+      headerTitle={t('reasons_for_compile_timeouts')}
       formattedContent={
         <>
+          {segmentation?.['10s-timeout-warning'] === 'enabled' && (
+            <p>
+              <em>
+                <Trans
+                  i18nKey="were_reducing_compile_timeout"
+                  components={[compileTimeoutChangesBlogLink]}
+                />
+              </em>
+            </p>
+          )}
+          <p>{t('common_causes_of_compile_timeouts_include')}:</p>
           <ul>
             <li>
               {t('large_or_high_resolution_images_taking_too_long_to_process')}

+ 36 - 15
services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-paywall-prompt.tsx

@@ -1,8 +1,16 @@
-import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react'
+import {
+  Dispatch,
+  SetStateAction,
+  useCallback,
+  useEffect,
+  useMemo,
+  useState,
+} from 'react'
 import getMeta from '@/utils/meta'
 import * as eventTracking from '@/infrastructure/event-tracking'
 import TimeoutMessageAfterPaywallDismissal from './timeout-message-after-paywall-dismissal'
 import { UpgradePrompt } from '@/shared/components/upgrade-prompt'
+import { useDetachCompileContext } from '@/shared/context/detach-compile-context'
 
 const studentRoles = [
   'High-school student',
@@ -11,11 +19,6 @@ const studentRoles = [
   'Doctoral student (e.g. PhD, MD, EngD)',
 ]
 
-type Segmentation = Record<
-  string,
-  string | number | boolean | undefined | unknown | any
->
-
 interface TimeoutUpgradePaywallPromptProps {
   setIsShowingPrimary?: Dispatch<SetStateAction<boolean>>
 }
@@ -26,16 +29,32 @@ function TimeoutUpgradePaywallPrompt({
   const odcRole = getMeta('ol-odcRole')
   const planPrices = getMeta('ol-paywallPlans')
   const isStudent = useMemo(() => studentRoles.includes(odcRole), [odcRole])
+  const { isProjectOwner } = useDetachCompileContext()
 
   const [isPaywallDismissed, setIsPaywallDismissed] = useState<boolean>(false)
+  const { reducedTimeoutWarning, compileTimeout } =
+    getMeta('ol-compileSettings')
 
-  function sendPaywallEvent(event: string, segmentation?: Segmentation) {
-    eventTracking.sendMB(event, {
-      'paywall-type': 'compile-timeout',
-      'paywall-version': 'primary',
-      ...segmentation,
-    })
-  }
+  const sharedSegmentation = useMemo(
+    () => ({
+      '10s-timeout-warning': reducedTimeoutWarning,
+      'is-owner': isProjectOwner,
+      time: compileTimeout,
+    }),
+    [isProjectOwner, reducedTimeoutWarning, compileTimeout]
+  )
+
+  const sendPaywallEvent = useCallback(
+    (event: string, segmentation?: eventTracking.Segmentation) => {
+      eventTracking.sendMB(event, {
+        'paywall-type': 'compile-timeout',
+        'paywall-version': 'primary',
+        ...sharedSegmentation,
+        ...segmentation,
+      })
+    },
+    [sharedSegmentation]
+  )
 
   function onClose() {
     sendPaywallEvent('paywall-dismiss')
@@ -62,7 +81,7 @@ function TimeoutUpgradePaywallPrompt({
     if (setIsShowingPrimary) {
       setIsShowingPrimary(true)
     }
-  }, [isStudent, setIsShowingPrimary])
+  }, [isStudent, setIsShowingPrimary, sendPaywallEvent])
 
   return (
     <div>
@@ -81,7 +100,9 @@ function TimeoutUpgradePaywallPrompt({
           onClickPaywall={onClickPaywall}
         />
       ) : (
-        <TimeoutMessageAfterPaywallDismissal />
+        <TimeoutMessageAfterPaywallDismissal
+          segmentation={sharedSegmentation}
+        />
       )}
     </div>
   )

+ 36 - 24
services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx

@@ -1,7 +1,7 @@
 import { Trans, useTranslation } from 'react-i18next'
 import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
 import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
-import { memo, useCallback } from 'react'
+import { memo, useCallback, useMemo } from 'react'
 import PdfLogEntry from './pdf-log-entry'
 import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
 import OLButton from '@/features/ui/components/ol/ol-button'
@@ -26,14 +26,29 @@ function TimeoutUpgradePromptNew() {
     setAnimateCompileDropdownArrow(true)
   }, [enableStopOnFirstError, startCompile, setAnimateCompileDropdownArrow])
 
+  const { reducedTimeoutWarning, compileTimeout } =
+    getMeta('ol-compileSettings')
+
+  const sharedSegmentation = useMemo(
+    () => ({
+      '10s-timeout-warning': reducedTimeoutWarning,
+      'is-owner': isProjectOwner,
+      time: compileTimeout,
+    }),
+    [isProjectOwner, reducedTimeoutWarning, compileTimeout]
+  )
+
   return (
     <>
-      <CompileTimeout isProjectOwner={isProjectOwner} />
+      <CompileTimeout
+        isProjectOwner={isProjectOwner}
+        segmentation={sharedSegmentation}
+      />
       {getMeta('ol-ExposedSettings').enableSubscriptions && (
         <PreventTimeoutHelpMessage
           handleEnableStopOnFirstErrorClick={handleEnableStopOnFirstErrorClick}
           lastCompileOptions={lastCompileOptions}
-          isProjectOwner={isProjectOwner}
+          segmentation={sharedSegmentation}
         />
       )}
     </>
@@ -42,10 +57,12 @@ function TimeoutUpgradePromptNew() {
 
 type CompileTimeoutProps = {
   isProjectOwner: boolean
+  segmentation: eventTracking.Segmentation
 }
 
 const CompileTimeout = memo(function CompileTimeout({
   isProjectOwner,
+  segmentation,
 }: CompileTimeoutProps) {
   const { t } = useTranslation()
 
@@ -62,7 +79,7 @@ const CompileTimeout = memo(function CompileTimeout({
             </p>
             {isProjectOwner ? (
               <p>
-                <strong>{t('upgrade_for_12x_more_compile_time')}</strong>{' '}
+                <strong>{t('upgrade_for_more_compile_time')}</strong>{' '}
                 {t(
                   'plus_additional_collaborators_document_history_track_changes_and_more'
                 )}
@@ -82,6 +99,7 @@ const CompileTimeout = memo(function CompileTimeout({
                 <StartFreeTrialButton
                   source="compile-timeout"
                   buttonProps={{ variant: 'primary', className: 'w-100' }}
+                  segmentation={segmentation}
                 >
                   {t('start_a_free_trial')}
                 </StartFreeTrialButton>
@@ -100,18 +118,19 @@ const CompileTimeout = memo(function CompileTimeout({
 type PreventTimeoutHelpMessageProps = {
   lastCompileOptions: any
   handleEnableStopOnFirstErrorClick: () => void
-  isProjectOwner: boolean
+  segmentation: eventTracking.Segmentation
 }
 
 const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
   lastCompileOptions,
   handleEnableStopOnFirstErrorClick,
-  isProjectOwner,
+  segmentation,
 }: PreventTimeoutHelpMessageProps) {
   const { t } = useTranslation()
 
   function sendInfoClickEvent() {
     eventTracking.sendMB('paywall-info-click', {
+      ...segmentation,
       'paywall-type': 'compile-timeout',
       content: 'blog',
     })
@@ -121,7 +140,7 @@ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
     /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
     <a
       aria-label={t('read_more_about_free_compile_timeouts_servers')}
-      href="/blog/changes-to-free-compile-timeouts-and-servers"
+      href="/blog/changes-to-free-compile-timeout"
       rel="noopener noreferrer"
       target="_blank"
       onClick={sendInfoClickEvent}
@@ -133,6 +152,16 @@ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
       headerTitle={t('reasons_for_compile_timeouts')}
       formattedContent={
         <>
+          {segmentation?.['10s-timeout-warning'] === 'enabled' && (
+            <p>
+              <em>
+                <Trans
+                  i18nKey="were_reducing_compile_timeout"
+                  components={[compileTimeoutChangesBlogLink]}
+                />
+              </em>
+            </p>
+          )}
           <p>{t('common_causes_of_compile_timeouts_include')}:</p>
           <ul>
             <li>
@@ -194,23 +223,6 @@ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
               ]}
             />
           </p>
-          <p>
-            <em>
-              <>
-                {isProjectOwner ? (
-                  <Trans
-                    i18nKey="weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project"
-                    components={[compileTimeoutChangesBlogLink]}
-                  />
-                ) : (
-                  <Trans
-                    i18nKey="weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project"
-                    components={[compileTimeoutChangesBlogLink]}
-                  />
-                )}
-              </>
-            </em>
-          </p>
         </>
       }
       // @ts-ignore

+ 9 - 2
services/web/frontend/js/main/account-upgrade.ts

@@ -1,7 +1,14 @@
 import * as eventTracking from '../infrastructure/event-tracking'
 
-export function startFreeTrial(source: string, variant?: string) {
-  const eventSegmentation: Record<string, string> = { 'paywall-type': source }
+export function startFreeTrial(
+  source: string,
+  variant?: string,
+  segmentation?: eventTracking.Segmentation
+) {
+  const eventSegmentation: Record<string, string> = {
+    'paywall-type': source,
+    ...segmentation,
+  }
   if (variant) {
     eventSegmentation.variant = variant
   }

+ 6 - 3
services/web/frontend/js/shared/components/start-free-trial-button.tsx

@@ -10,6 +10,7 @@ type StartFreeTrialButtonProps = {
   buttonProps?: React.ComponentProps<typeof OLButton>
   children?: React.ReactNode
   handleClick?: React.ComponentProps<typeof OLButton>['onClick']
+  segmentation?: eventTracking.Segmentation
 }
 
 export default function StartFreeTrialButton({
@@ -20,18 +21,20 @@ export default function StartFreeTrialButton({
   handleClick,
   source,
   variant,
+  segmentation,
 }: StartFreeTrialButtonProps) {
   const { t } = useTranslation()
 
   useEffect(() => {
     const eventSegmentation: { [key: string]: unknown } = {
       'paywall-type': source,
+      ...segmentation,
     }
     if (variant) {
       eventSegmentation.variant = variant
     }
     eventTracking.sendMB('paywall-prompt', eventSegmentation)
-  }, [source, variant])
+  }, [source, variant, segmentation])
 
   const onClick = useCallback(
     (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
@@ -41,9 +44,9 @@ export default function StartFreeTrialButton({
         handleClick(event)
       }
 
-      startFreeTrial(source, variant)
+      startFreeTrial(source, variant, segmentation)
     },
-    [handleClick, source, variant]
+    [handleClick, source, variant, segmentation]
   )
 
   return (

+ 1 - 1
services/web/frontend/js/shared/components/upgrade-prompt.tsx

@@ -104,7 +104,7 @@ export function UpgradePrompt({
             <OLRow>
               <ul className="upgrade-prompt-list">
                 <IconListItem icon="hourglass_top">
-                  {t('12x_more_compile_time')}
+                  {t('24x_more_compile_time')}
                 </IconListItem>
                 <IconListItem icon="group_add">
                   {t('collabs_per_proj', { collabcount: isStudent ? 6 : 10 })}

+ 4 - 1
services/web/frontend/js/utils/meta.ts

@@ -86,7 +86,10 @@ export interface Meta {
   'ol-cannot-reactivate-subscription': boolean
   'ol-cannot-use-ai': boolean
   'ol-chatEnabled': boolean
-
+  'ol-compileSettings': {
+    reducedTimeoutWarning: string
+    compileTimeout: number
+  }
   'ol-compilesUserContentDomain': string
   'ol-countryCode': PricingFormState['country']
   'ol-couponCode': PricingFormState['coupon']

+ 1 - 0
services/web/locales/da.json

@@ -2,6 +2,7 @@
   "12x_basic": "12x basis",
   "1_2_width": "½ bredde",
   "1_4_width": "¼ bredde",
+  "24x_basic": "24x basis",
   "3_4_width": "¾ bredde",
   "About": "Om",
   "Account": "Konto",

+ 12 - 7
services/web/locales/en.json

@@ -1,10 +1,10 @@
 {
   "0_free_suggestions": "0 free suggestions",
-  "12x_basic": "12x Basic",
-  "12x_more_compile_time": "12x more compile time on our fastest servers",
   "1_2_width": "½ width",
   "1_4_width": "¼ width",
   "1_free_suggestion": "1 free suggestion",
+  "24x_basic": "24x Basic",
+  "24x_more_compile_time": "24x more compile time on our fastest servers",
   "3_4_width": "¾ width",
   "About": "About",
   "Account": "Account",
@@ -163,6 +163,7 @@
   "an_error_occured_while_restoring_project": "An error occured while restoring the project",
   "an_error_occurred_when_verifying_the_coupon_code": "An error occurred when verifying the coupon code",
   "and": "and",
+  "and_upgrade_for_compile_time": "And you can <strong>upgrade to get more compile time</strong>.",
   "annual": "Annual",
   "annual_discount": "Annual discount",
   "anonymous": "Anonymous",
@@ -1085,6 +1086,7 @@
   "integrations": "Integrations",
   "integrations_like_github": "Integrations like GitHub Sync",
   "interested_in_cheaper_personal_plan": "Would you be interested in the cheaper <0>__price__</0> Personal plan?",
+  "introducing_shorter_compile_timeout": "We’re <0>introducing a shorter compile timeout limit</0>. This project currently exceeds the new limit, so it might not compile from 16th July 2025.",
   "invalid_certificate": "Invalid certificate. Please check the certificate and try again.",
   "invalid_confirmation_code": "That didn’t work. Please check the code and try again.",
   "invalid_email": "An email address is invalid",
@@ -1524,7 +1526,6 @@
   "original_price": "Original price",
   "other": "Other",
   "other_actions": "Other Actions",
-  "other_causes_of_compile_timeouts": "Other causes of compile timeouts",
   "other_logs_and_files": "Other logs and files",
   "other_output_files": "Download other output files",
   "other_sessions": "Other Sessions",
@@ -1756,6 +1757,8 @@
   "reactivate_subscription": "Reactivate your subscription",
   "read_lines_from_path": "Read lines from __path__",
   "read_more": "Read more",
+  "read_more_about_compile_timeout_changes": "Read more about changes to compile timeout limits",
+  "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_only_dropbox_sync_message": "As a read-only viewer you can sync the current project version to Dropbox, but changes made in Dropbox will <0>not</0> sync back to Overleaf.",
   "read_only_token": "Read-Only Token",
@@ -2312,6 +2315,7 @@
   "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "This is the file that references pulled from your reference manager will be added to.",
   "this_is_your_template": "This is your template from your project",
   "this_project_already_has_maximum_collaborators": "This project already has the maximum number of collaborators permitted on the owner’s plan. This means you can view but not edit or review the project.",
+  "this_project_compiled_but_soon_might_not": "This project compiled, but soon it might not",
   "this_project_contains_a_file_called_output": "This project contains a file called output.pdf. If that file exists, please rename it and compile again.",
   "this_project_exceeded_collaborator_limit": "This project exceeded the collaborator limit for your plan. All other users now have view-only access.",
   "this_project_exceeded_compile_timeout_limit_on_free_plan": "This project exceeded the compile timeout limit on our free plan.",
@@ -2507,7 +2511,7 @@
   "updating_site": "Updating Site",
   "upgrade": "Upgrade",
   "upgrade_cc_btn": "Upgrade now, pay after 7 days",
-  "upgrade_for_12x_more_compile_time": "Upgrade to get 12x more compile time",
+  "upgrade_for_more_compile_time": "Upgrade to get more compile time",
   "upgrade_my_plan": "Upgrade my plan",
   "upgrade_now": "Upgrade now",
   "upgrade_plan": "Upgrade plan",
@@ -2515,7 +2519,7 @@
   "upgrade_to_add_more_collaborators_and_access_collaboration_features": "Upgrade to add more collaborators and access collaboration features like track changes and full project history.",
   "upgrade_to_get_feature": "Upgrade to get __feature__, plus:",
   "upgrade_to_review": "Upgrade to Review",
-  "upgrade_to_unlock_more_time": "Upgrade now to unlock 12x more compile time on our fastest servers.",
+  "upgrade_to_unlock_more_time": "Upgrade now to unlock 24x more compile time on our fastest servers.",
   "upgrade_your_subscription": "Upgrade your subscription",
   "upload": "Upload",
   "upload_failed": "Upload failed",
@@ -2617,8 +2621,7 @@
   "were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "We’re making some <0>changes to project sharing</0>. This means, as someone with edit access, your name and email address will be visible to the project owner and other editors.",
   "were_performing_maintenance": "We’re performing maintenance on Overleaf and you need to wait a moment. Sorry for any inconvenience. The editor will refresh automatically in __seconds__ seconds.",
   "were_redesigning_our_editor_to_make_it_easier_to_use": "We’re redesigning our editor to make it easier to use and ensure it’s future ready. Try it out and give us your feedback to help us get this right. (Some features are still in the works, so you can switch back at any time.)",
-  "weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "We’ve 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": "We’ve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected your project.",
+  "were_reducing_compile_timeout": "We’re in the process of <0>reducing the compile timeout limit</0> on our free plan, which may affect your project in future.",
   "what_do_you_need": "What do you need?",
   "what_do_you_need_help_with": "What do you need help with?",
   "what_does_this_mean": "What does this mean?",
@@ -2698,6 +2701,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_x_licenses_and_your_plan_supports_up_to_y": "You have __addedUsersSize__ licenses and your plan supports up to __groupSize__.",
   "you_have_x_licenses_on_your_subscription": "You have __groupSize__ licenses on your subscription.",
+  "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_unpaused_your_subscription": "You unpaused your subscription.",
   "you_will_be_able_to_contact_us_any_time_to_share_your_feedback": "<0>You will be able to contact us</0> any time to share your feedback",
@@ -2735,6 +2739,7 @@
   "your_plan_is_limited_to_n_editors": "Your plan allows __count__ collaborator with edit access and unlimited viewers.",
   "your_plan_is_limited_to_n_editors_plural": "Your plan allows __count__ collaborators with edit access and unlimited viewers.",
   "your_premium_plan_is_paused": "Your Premium plan is <0>paused</0>.",
+  "your_project_compiled_but_soon_might_not": "Your project compiled, but soon it might not",
   "your_project_exceeded_collaborator_limit": "Your project exceeded the collaborator limit and access levels were changed. Select a new access level for your collaborators, or upgrade to add more editors or reviewers.",
   "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.",

+ 2 - 0
services/web/locales/ru.json

@@ -3,6 +3,8 @@
   "12x_more_compile_time": "В 12 раз больше времени компиляции на наших быстрейших серверах",
   "1_2_width": "½ ширины",
   "1_4_width": "¼ ширины",
+  "24x_basic": "24х базовый",
+  "24x_more_compile_time": "В 24 раз больше времени компиляции на наших быстрейших серверах",
   "3_4_width": "¾ ширины",
   "About": "О сайте",
   "Account": "Аккаунт",

+ 2 - 0
services/web/locales/zh-CN.json

@@ -3,6 +3,8 @@
   "12x_more_compile_time": "在更快的服务器上获取12倍的编译时间",
   "1_2_width": "½ 宽度",
   "1_4_width": "¼ 宽度",
+  "24x_basic": "24倍 免费时长 (240s)",
+  "24x_more_compile_time": "在更快的服务器上获取24倍的编译时间",
   "3_4_width": "¾ 宽度",
   "About": "关于",
   "Account": "账户",

+ 4 - 0
services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx

@@ -8,6 +8,10 @@ describe('<PdfPreviewDetachedRoot/>', function () {
     window.metaAttributesCache.set('ol-detachRole', 'detached')
     window.metaAttributesCache.set('ol-projectName', 'Project Name')
     window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
+    window.metaAttributesCache.set('ol-compileSettings', {
+      reducedWarningThreshold: 'default',
+      compileTimeout: 240,
+    })
 
     cy.interceptEvents()
   })

+ 4 - 0
services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx

@@ -50,6 +50,10 @@ describe('<PdfPreview/>', function () {
       'populate-clsi-cache': 'enabled',
     })
     window.metaAttributesCache.set('ol-projectOwnerHasPremiumOnPageLoad', true)
+    window.metaAttributesCache.set('ol-compileSettings', {
+      reducedWarningThreshold: 'default',
+      compileTimeout: 240,
+    })
     cy.interceptEvents()
   })