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

[web] Compile time warning split test (#8813)

* Implement compile time warning split test

* Add i18n for compile time warning

* Memoize CompileTimeWarning component

* Reuse startFreeTrial() function from account-upgrade

* Improve usage of react bootstrap native properties

* Cleanup compile-time-warning

* Move all logic into CompileTimeWarning component

* Only show compile time warning to project owners

* Handle compile time warning in detached mode and while switching layout

GitOrigin-RevId: 4fc40acfc113f91c81a6744c14c0f8b3ef766e39
Alexandre Bourdin 4 лет назад
Родитель
Сommit
d501bc4820

+ 19 - 0
services/web/app/src/Features/Project/ProjectController.js

@@ -949,6 +949,25 @@ const ProjectController = {
             }
           )
         },
+        compileTimeWarningAssignment: [
+          'user',
+          (results, cb) => {
+            if (results.user?.features?.compileTimeout <= 60) {
+              SplitTestHandler.getAssignment(
+                req,
+                res,
+                'compile-time-warning',
+                {},
+                () => {
+                  // do not fail editor load if assignment fails
+                  cb()
+                }
+              )
+            } else {
+              cb()
+            }
+          },
+        ],
       },
       (
         err,

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -10,6 +10,7 @@
   "also": "",
   "anyone_with_link_can_edit": "",
   "anyone_with_link_can_view": "",
+  "approaching_compile_timeout_limit_upgrade_for_more_compile_time": "<0></0>",
   "are_you_still_at": "",
   "ask_proj_owner_to_upgrade_for_git_bridge": "",
   "ask_proj_owner_to_upgrade_for_longer_compiles": "",

+ 62 - 0
services/web/frontend/js/features/pdf-preview/components/compile-time-warning.tsx

@@ -0,0 +1,62 @@
+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 { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
+import { startFreeTrial } from '../../../main/account-upgrade'
+
+function CompileTimeWarning() {
+  const { t } = useTranslation()
+
+  const { showCompileTimeWarning, setShowCompileTimeWarning } =
+    useDetachCompileContext()
+
+  useEffect(() => {
+    if (showCompileTimeWarning) {
+      eventTracking.sendMB('compile-time-warning-displayed', {})
+    }
+  }, [showCompileTimeWarning])
+
+  const closeWarning = () => {
+    eventTracking.sendMB('compile-time-warning-dismissed', {})
+    setShowCompileTimeWarning(false)
+  }
+
+  const handleUpgradeClick = useCallback(event => {
+    event.preventDefault()
+    startFreeTrial('compile-time-warning')
+  }, [])
+
+  if (!showCompileTimeWarning) {
+    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>
+        <div className="warning-text">
+          <Trans
+            i18nKey="approaching_compile_timeout_limit_upgrade_for_more_compile_time"
+            // eslint-disable-next-line react/jsx-key
+            components={[<strong style={{ display: 'inline-block' }} />]}
+          />
+        </div>
+        <div className="upgrade-prompt">
+          <Button bsStyle="primary" bsSize="sm" onClick={handleUpgradeClick}>
+            {t('upgrade')}
+          </Button>
+        </div>
+      </div>
+    </div>
+  )
+}
+
+export default memo(CompileTimeWarning)

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

@@ -1,5 +1,6 @@
 import { memo, Suspense } from 'react'
 import classNames from 'classnames'
+import CompileTimeWarning from './compile-time-warning'
 import PdfLogsViewer from './pdf-logs-viewer'
 import PdfViewer from './pdf-viewer'
 import LoadingSpinner from '../../../shared/components/loading-spinner'
@@ -15,6 +16,7 @@ function PdfPreviewPane() {
   return (
     <div className={classes}>
       <PdfHybridPreviewToolbar />
+      <CompileTimeWarning />
       <Suspense fallback={<LoadingPreview />}>
         <div className="pdf-viewer">
           <PdfViewer />

+ 18 - 0
services/web/frontend/js/shared/context/detach-compile-context.js

@@ -48,11 +48,13 @@ export function DetachCompileProvider({ children }) {
     setHasLintingError: _setHasLintingError,
     setHighlights: _setHighlights,
     setPosition: _setPosition,
+    setShowCompileTimeWarning: _setShowCompileTimeWarning,
     setShowLogs: _setShowLogs,
     toggleLogs: _toggleLogs,
     setStopOnFirstError: _setStopOnFirstError,
     setStopOnValidationError: _setStopOnValidationError,
     showLogs: _showLogs,
+    showCompileTimeWarning: _showCompileTimeWarning,
     showFasterCompilesFeedbackUI: _showFasterCompilesFeedbackUI,
     stopOnFirstError: _stopOnFirstError,
     stopOnValidationError: _stopOnValidationError,
@@ -185,6 +187,12 @@ export function DetachCompileProvider({ children }) {
     'detacher',
     'detached'
   )
+  const [showCompileTimeWarning] = useDetachStateWatcher(
+    'showCompileTimeWarning',
+    _showCompileTimeWarning,
+    'detacher',
+    'detached'
+  )
   const [showLogs] = useDetachStateWatcher(
     'showLogs',
     _showLogs,
@@ -276,6 +284,12 @@ export function DetachCompileProvider({ children }) {
     'detacher',
     'detached'
   )
+  const setShowCompileTimeWarning = useDetachAction(
+    'setShowCompileTimeWarning',
+    _setShowCompileTimeWarning,
+    'detached',
+    'detacher'
+  )
   const setShowLogs = useDetachAction(
     'setShowLogs',
     _setShowLogs,
@@ -376,11 +390,13 @@ export function DetachCompileProvider({ children }) {
       setHasLintingError,
       setHighlights,
       setPosition,
+      setShowCompileTimeWarning,
       setShowLogs,
       toggleLogs,
       setStopOnFirstError,
       setStopOnValidationError,
       showLogs,
+      showCompileTimeWarning,
       showFasterCompilesFeedbackUI,
       startCompile,
       stopCompile,
@@ -425,10 +441,12 @@ export function DetachCompileProvider({ children }) {
       setHasLintingError,
       setHighlights,
       setPosition,
+      setShowCompileTimeWarning,
       setShowLogs,
       toggleLogs,
       setStopOnFirstError,
       setStopOnValidationError,
+      showCompileTimeWarning,
       showLogs,
       showFasterCompilesFeedbackUI,
       startCompile,

+ 46 - 0
services/web/frontend/js/shared/context/local-compile-context.js

@@ -27,6 +27,9 @@ import { useIdeContext } from './ide-context'
 import { useProjectContext } from './project-context'
 import { useEditorContext } from './editor-context'
 import { buildFileList } from '../../features/pdf-preview/util/file-list'
+import { useSplitTestContext } from './split-test-context'
+
+const ONE_DAY = 24 * 60 * 60 * 24 * 1000
 
 export const LocalCompileContext = createContext()
 
@@ -57,10 +60,12 @@ export const CompileContextPropTypes = {
     setHasLintingError: PropTypes.func.isRequired, // only for storybook
     setHighlights: PropTypes.func.isRequired,
     setPosition: PropTypes.func.isRequired,
+    setShowCompileTimeWarning: PropTypes.func.isRequired,
     setShowLogs: PropTypes.func.isRequired,
     toggleLogs: PropTypes.func.isRequired,
     setStopOnFirstError: PropTypes.func.isRequired,
     setStopOnValidationError: PropTypes.func.isRequired,
+    showCompileTimeWarning: PropTypes.bool.isRequired,
     showLogs: PropTypes.bool.isRequired,
     showFasterCompilesFeedbackUI: PropTypes.bool.isRequired,
     stopOnFirstError: PropTypes.bool.isRequired,
@@ -82,9 +87,21 @@ export function LocalCompileProvider({ children }) {
 
   const { _id: projectId, rootDocId } = useProjectContext()
 
+  const { splitTestVariants } = useSplitTestContext()
+
   // whether a compile is in progress
   const [compiling, setCompiling] = useState(false)
 
+  // whether to show the compile time warning
+  const [showCompileTimeWarning, setShowCompileTimeWarning] = useState(false)
+
+  // the last time the compile time warning was displayed
+  const [lastDisplay, setLastDisplay] = usePersistedState(
+    'compile-time-warning-displayed-at',
+    0,
+    true
+  )
+
   // the log entries parsed from the compile output log
   const [logEntries, setLogEntries] = useScopeValueSetterOnly('pdf.logEntries')
 
@@ -264,6 +281,31 @@ export function LocalCompileProvider({ children }) {
     }
   }, [compiledOnce, currentDoc, compiler])
 
+  useEffect(() => {
+    const compileTimeWarningEnabled =
+      splitTestVariants['compile-time-warning'] === 'show-upgrade-prompt'
+
+    if (compileTimeWarningEnabled && compiling && isProjectOwner) {
+      const timeout = window.setTimeout(() => {
+        if (lastDisplay && Date.now() - lastDisplay < ONE_DAY) {
+          return
+        }
+        setShowCompileTimeWarning(true)
+        setLastDisplay(Date.now())
+      }, 30000)
+
+      return () => {
+        window.clearTimeout(timeout)
+      }
+    }
+  }, [
+    compiling,
+    isProjectOwner,
+    lastDisplay,
+    setLastDisplay,
+    splitTestVariants,
+  ])
+
   // handle the data returned from a compile request
   // note: this should _only_ run when `data` changes,
   // the other dependencies must all be static
@@ -522,6 +564,8 @@ export function LocalCompileProvider({ children }) {
       setHasLintingError, // only for stories
       setHighlights,
       setPosition,
+      showCompileTimeWarning,
+      setShowCompileTimeWarning,
       setShowLogs,
       toggleLogs,
       setStopOnFirstError,
@@ -570,8 +614,10 @@ export function LocalCompileProvider({ children }) {
       setHasLintingError, // only for stories
       setHighlights,
       setPosition,
+      setShowCompileTimeWarning,
       setStopOnFirstError,
       setStopOnValidationError,
+      showCompileTimeWarning,
       showLogs,
       showFasterCompilesFeedbackUI,
       startCompile,

+ 33 - 0
services/web/frontend/stylesheets/app/editor.less

@@ -743,3 +743,36 @@ CodeMirror
 .editor-container {
   overflow: visible !important;
 }
+
+.compile-time-warning {
+  position: absolute;
+  right: @margin-sm;
+  top: @margin-xl;
+  padding: @padding-sm;
+  background-color: @ol-green;
+  width: 400px;
+  z-index: @zindex-popover;
+  box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.3);
+
+  .warning-text {
+    max-width: 300px;
+    padding-right: @alert-padding;
+    display: inline-block;
+    font-size: @font-size-small;
+  }
+  .upgrade-prompt {
+    display: inline-block;
+    position: absolute;
+    height: 100%;
+    top: @margin-lg;
+    right: 32px;
+  }
+
+  button.btn {
+    background-color: @ol-darker-green;
+
+    &.close {
+      background-color: @ol-green;
+    }
+  }
+}

+ 1 - 0
services/web/frontend/stylesheets/core/variables.less

@@ -22,6 +22,7 @@
 @ol-green: #138a07;
 @ol-type-green: #107206;
 @ol-dark-green: #004a0e;
+@ol-darker-green: #083c03;
 @ol-blue: #3e70bb;
 @ol-dark-blue: #2857a1;
 @ol-red: #c9453e;

+ 2 - 1
services/web/locales/en.json

@@ -1792,5 +1792,6 @@
   "get_most_subscription_by_checking_premium_features": "Get the most out of your __appName__ subscription by checking out the list of <0>__appName__’s premium features</0>.",
   "would_you_like_to_see_a_university_subscription": "Would you like to see a university-wide __appName__ subscription at your university?",
   "student_and_faculty_support_make_difference": "Student and faculty support make a difference! We can share this information with our contacts at your university when discussing an Overleaf institutional account.",
-  "show_your_support": "Show your support"
+  "show_your_support": "Show your support",
+  "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."
 }