Merge pull request #34843 from overleaf/bg-add-compile-timer

add timer for compile with checkpoints

GitOrigin-RevId: ec758d34594f242a5cac8192233bfe2db783e42a
This commit is contained in:
Brian Gough
2026-06-23 08:05:55 +00:00
committed by Copybot
parent b1ed38c2d0
commit 23dca72cd3
9 changed files with 143 additions and 0 deletions
@@ -496,6 +496,7 @@ const _ProjectController = {
'markdown-visual',
'ai-disabled-collaborators',
'group-link-sharing',
'compile-with-checkpoint',
].filter(Boolean)
const getUserValues = async userId =>
@@ -1062,6 +1062,7 @@
"language_suggestions": "",
"last_active": "",
"last_active_description": "",
"last_compile_duration": "",
"last_edit": "",
"last_logged_in": "",
"last_modified": "",
@@ -0,0 +1,61 @@
import { memo, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDetachCompileContext } from '@/shared/context/detach-compile-context'
import MaterialIcon from '@/shared/components/material-icon'
import OLTooltip from '@/shared/components/ol/ol-tooltip'
function PdfCompileTime() {
const { t } = useTranslation()
const { compiling, deliveryLatencies } = useDetachCompileContext()
const startRef = useRef<number | null>(null)
const [elapsedMs, setElapsedMs] = useState<number | null>(null)
useEffect(() => {
if (!compiling) {
startRef.current = null
return
}
startRef.current = performance.now()
setElapsedMs(0)
const intervalId = window.setInterval(() => {
if (startRef.current !== null) {
setElapsedMs(performance.now() - startRef.current)
}
}, 100)
return () => {
window.clearInterval(intervalId)
}
}, [compiling])
const ms = compiling
? (elapsedMs ?? 0)
: deliveryLatencies?.compileTimeClientE2E
if (ms == null) {
return null
}
const seconds = (ms / 1000).toFixed(1)
return (
<OLTooltip
id="pdf-compile-time"
description={t('last_compile_duration', { seconds })}
overlayProps={{ placement: 'bottom' }}
>
<span
className="toolbar-pdf-compile-time"
aria-label={t('last_compile_duration', { seconds })}
>
{compiling ? (
<MaterialIcon type="hourglass_top" />
) : (
<MaterialIcon type="timer" />
)}
<span>{seconds} s</span>
</span>
</OLTooltip>
)
}
export default memo(PdfCompileTime)
@@ -3,6 +3,7 @@ import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import OLButtonToolbar from '@/shared/components/ol/ol-button-toolbar'
import PdfCompileButton from '@/features/pdf-preview/components/pdf-compile-button'
import PdfCompileTime from '@/features/pdf-preview/components/pdf-compile-time'
import PdfHybridDownloadButton from '@/features/pdf-preview/components/pdf-hybrid-download-button'
import { DetachedSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control'
import SwitchToEditorButton from '@/features/pdf-preview/components/switch-to-editor-button'
@@ -10,6 +11,7 @@ import PdfHybridLogsButton from '@/features/pdf-preview/components/pdf-hybrid-lo
import PdfPreviewHybridToolbarOrphanRefreshInner from './pdf-preview-hybrid-toolbar-orphan-refresh-inner'
import PdfPreviewHybridToolbarConnectingInner from './pdf-preview-hybrid-toolbar-connecting-inner'
import useDetachedOrphanDetection from '../hooks/use-detached-orphan-detection'
import { useFeatureFlag } from '@/shared/context/split-test-context'
function PdfPreviewHybridToolbar() {
const { t } = useTranslation()
@@ -36,12 +38,15 @@ function PdfPreviewHybridToolbar() {
function PdfPreviewHybridToolbarInner() {
const { focusMode } = useLayoutContext()
const showCompileTimer = useFeatureFlag('compile-with-checkpoint')
return (
<>
<div className="toolbar-pdf-left">
<PdfCompileButton />
<PdfHybridLogsButton />
<PdfHybridDownloadButton />
{showCompileTimer && <PdfCompileTime />}
</div>
<div className="toolbar-pdf-right">
<div className="toolbar-pdf-controls" id="toolbar-pdf-controls" />
@@ -376,6 +376,16 @@
}
}
.toolbar-pdf-compile-time {
color: var(--toolbar-btn-color);
font-size: var(--font-size-02);
padding: 0 var(--spacing-04);
display: inline-flex;
align-items: center;
gap: var(--spacing-02);
white-space: nowrap;
}
.pdfjs-page-number-input {
color: var(--toolbar-btn-color);
font-size: var(--font-size-02);
+1
View File
@@ -1370,6 +1370,7 @@
"larger_discounts_for_groups_of_20_plus": "Larger discounts for groups of 20+",
"last_active": "Last Active",
"last_active_description": "Last time a project was opened.",
"last_compile_duration": "Last compile = __seconds__ s",
"last_edit": "Last edit",
"last_logged_in": "Last logged in",
"last_modified": "Last modified",
@@ -0,0 +1,52 @@
import { render, screen } from '@testing-library/react'
import { expect } from 'chai'
import PdfCompileTime from '../../../../frontend/js/features/pdf-preview/components/pdf-compile-time'
import { DetachCompileContext } from '../../../../frontend/js/shared/context/detach-compile-context'
function renderComponent(compileTimeClientE2E?: number) {
return render(
<DetachCompileContext.Provider
value={
{
compiling: false,
deliveryLatencies: {
compileTimeClientE2E,
},
} as any
}
>
<PdfCompileTime />
</DetachCompileContext.Provider>
)
}
describe('<PdfCompileTime />', function () {
it('does not render when compile duration is missing', function () {
renderComponent(undefined)
expect(screen.queryByText('1.2 s')).to.not.exist
})
it('renders when compile duration is available', function () {
renderComponent(1200)
screen.getByText('1.2 s')
})
it('renders a running timer while compiling', function () {
render(
<DetachCompileContext.Provider
value={
{
compiling: true,
deliveryLatencies: {},
} as any
}
>
<PdfCompileTime />
</DetachCompileContext.Provider>
)
screen.getByText('0.0 s')
})
})
@@ -909,6 +909,18 @@ describe('ProjectController', function () {
})
})
it('should request compile-with-checkpoint split test assignment', async function (ctx) {
await new Promise(resolve => {
ctx.res.render = () => {
expect(
ctx.SplitTestHandler.promises.getAssignment
).to.have.been.calledWith(ctx.req, ctx.res, 'compile-with-checkpoint')
resolve()
}
ctx.ProjectController.loadEditor(ctx.req, ctx.res)
})
})
it('should redirect to domain capture page', async function (ctx) {
ctx.Features.hasFeature.withArgs('saas').returns(true)
ctx.SplitTestHandler.promises.getAssignment