Merge pull request #28441 from overleaf/dp-4-way-test

Initial setup for 4 way split test

GitOrigin-RevId: c15496111b32e1b9bf0b7798d9d8ff79d229b1c8
This commit is contained in:
Mathias Jakobsen
2025-09-17 08:05:10 +00:00
committed by Copybot
parent d8c7acfe62
commit 959823515a
11 changed files with 213 additions and 106 deletions
@@ -121,8 +121,10 @@
"ai_feedback_the_suggestion_wasnt_the_best_fix_available": "",
"ai_feedback_there_was_no_code_fix_suggested": "",
"alignment": "",
"all": "",
"all_borders": "",
"all_features_in_group_standard_plus": "",
"all_logs": "",
"all_premium_features": "",
"all_premium_features_including": "",
"all_projects": "",
@@ -551,6 +553,7 @@
"error_opening_document_detail": "",
"error_performing_request": "",
"error_processing_file": "",
"errors": "",
"essential_cookies_only": "",
"example_project": "",
"existing_plan_active_until_term_end": "",
@@ -833,6 +836,7 @@
"included_as_part_of_your_writefull_subscription": "",
"increase_indent": "",
"increased_compile_timeout": "",
"info": "",
"initiator": "",
"inline": "",
"inline_math": "",
@@ -2091,6 +2095,7 @@
"visual_editor": "",
"visual_editor_is_only_available_for_tex_files": "",
"want_change_to_apply_before_plan_end": "",
"warnings": "",
"we_are_unable_to_opt_you_into_this_experiment": "",
"we_cant_find_any_sections_or_subsections_in_this_file": "",
"we_do_not_share_personal_information": "",
@@ -14,6 +14,9 @@ import LogEntry from './log-entry'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
import TimeoutUpgradePromptNew from '@/features/pdf-preview/components/timeout-upgrade-prompt-new'
import getMeta from '@/utils/meta'
import PdfClearCacheButton from '@/features/pdf-preview/components/pdf-clear-cache-button'
import PdfDownloadFilesButton from '@/features/pdf-preview/components/pdf-download-files-button'
import { useIsNewErrorLogsPositionEnabled } from '../../utils/new-editor-utils'
const logsComponents: Array<{
import: { default: ElementType }
@@ -26,24 +29,32 @@ type ErrorLogTab = {
entries: LogEntryData[] | undefined
}
function ErrorLogs() {
function ErrorLogs({
includeActionButtons,
}: {
includeActionButtons?: boolean
}) {
const { error, logEntries, rawLog, validationIssues, stoppedOnFirstError } =
useCompileContext()
const { compileTimeout } = getMeta('ol-compileSettings')
const newLogsPosition = useIsNewErrorLogsPositionEnabled()
const { t } = useTranslation()
const tabs = useMemo(() => {
return [
{ key: 'all', label: 'All', entries: logEntries?.all },
{ key: 'errors', label: 'Errors', entries: logEntries?.errors },
{ key: 'warnings', label: 'Warnings', entries: logEntries?.warnings },
{ key: 'info', label: 'Info', entries: logEntries?.typesetting },
{
key: 'all',
label: newLogsPosition ? t('all') : t('all_logs'),
entries: logEntries?.all,
},
{ key: 'errors', label: t('errors'), entries: logEntries?.errors },
{ key: 'warnings', label: t('warnings'), entries: logEntries?.warnings },
{ key: 'info', label: t('info'), entries: logEntries?.typesetting },
]
}, [logEntries])
}, [logEntries, newLogsPosition, t])
const { loadingError } = usePdfPreviewContext()
const { t } = useTranslation()
const [activeTab, setActiveTab] = useState<string | null>('all')
const entries = useMemo(() => {
@@ -108,6 +119,13 @@ function ErrorLogs() {
showSourceLocationLink={false}
/>
)}
{includeActionButtons && (
<div className="logs-pane-actions">
<PdfClearCacheButton />
<PdfDownloadFilesButton />
</div>
)}
</div>
</TabContent>
</TabContainer>
@@ -0,0 +1,19 @@
import classnames from 'classnames'
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
import ErrorLogs from '../error-logs/error-logs'
import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
export default function PdfLogsViewer() {
const { showLogs } = useCompileContext()
const { loadingError } = usePdfPreviewContext()
return (
<div
className={classnames('new-logs-pane', {
hidden: !showLogs && !loadingError,
})}
>
<ErrorLogs includeActionButtons />
</div>
)
}
@@ -6,11 +6,11 @@ import PdfHybridDownloadButton from '@/features/pdf-preview/components/pdf-hybri
import { DetachedSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control'
import SwitchToEditorButton from '@/features/pdf-preview/components/switch-to-editor-button'
import PdfHybridLogsButton from '@/features/pdf-preview/components/pdf-hybrid-logs-button'
import { useAreNewErrorLogsEnabled } from '../../utils/new-editor-utils'
import { useIsNewErrorLogsPositionEnabled } from '../../utils/new-editor-utils'
function PdfPreviewHybridToolbar() {
const { t } = useTranslation()
const newErrorLogs = useAreNewErrorLogsEnabled()
const newErrorLogsPosition = useIsNewErrorLogsPositionEnabled()
// TODO: add detached pdf logic
return (
@@ -20,7 +20,7 @@ function PdfPreviewHybridToolbar() {
>
<div className="toolbar-pdf-left">
<PdfCompileButton />
{!newErrorLogs && <PdfHybridLogsButton />}
{!newErrorLogsPosition && <PdfHybridLogsButton />}
<PdfHybridDownloadButton />
</div>
<div className="toolbar-pdf-right">
@@ -29,7 +29,7 @@ import RailResizeHandle from './rail-resize-handle'
import RailModals from './rail-modals'
import RailOverflowDropdown from './rail-overflow-dropdown'
import useRailOverflow from '../../hooks/use-rail-overflow'
import { useAreNewErrorLogsEnabled } from '../../utils/new-editor-utils'
import { useIsNewErrorLogsPositionEnabled } from '../../utils/new-editor-utils'
export const RailLayout = () => {
const { sendEvent } = useEditorAnalytics()
@@ -45,7 +45,7 @@ export const RailLayout = () => {
const isHistoryView = view === 'history'
const newErrorlogs = useAreNewErrorLogsEnabled()
const newErrorlogsPosition = useIsNewErrorLogsPositionEnabled()
const railTabs: RailElement[] = useMemo(
() => [
@@ -94,10 +94,16 @@ export const RailLayout = () => {
component: <ErrorLogsPanel />,
indicator: <ErrorIndicator />,
disabled: errorLogsDisabled,
hide: !newErrorlogs,
hide: !newErrorlogsPosition,
},
],
[t, features.trackChangesVisible, newErrorlogs, errorLogsDisabled, view]
[
t,
features.trackChangesVisible,
newErrorlogsPosition,
errorLogsDisabled,
view,
]
)
const railActions: RailAction[] = useMemo(
@@ -25,7 +25,8 @@ export const canUseNewEditorViaNewUserFeatureFlag = () => {
!canUseNewEditorViaPrimaryFeatureFlag() &&
isNewUser() &&
(newUserTestVariant === 'new-editor' ||
newUserTestVariant === 'new-editor-old-logs')
newUserTestVariant === 'new-editor-old-logs' ||
newUserTestVariant === 'new-editor-new-logs-old-position')
)
}
@@ -36,7 +37,7 @@ export const canUseNewEditor = () => {
)
}
const canUseNewLogs = () => {
const canUseNewLogsPosition = () => {
const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users')
const canUseNewLogsViaNewUserFeatureFlag =
isNewUser() && newUserTestVariant === 'new-editor'
@@ -46,6 +47,18 @@ const canUseNewLogs = () => {
)
}
const canUseNewLogs = () => {
const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users')
const canUseNewLogsViaNewUserFeatureFlag =
isNewUser() &&
(newUserTestVariant === 'new-editor' ||
newUserTestVariant === 'new-editor-new-logs-old-position')
return (
canUseNewEditorViaPrimaryFeatureFlag() || canUseNewLogsViaNewUserFeatureFlag
)
}
export const useIsNewEditorEnabledViaPrimaryFeatureFlag = () => {
const { userSettings } = useUserSettingsContext()
const hasAccess = canUseNewEditorViaPrimaryFeatureFlag()
@@ -60,6 +73,11 @@ export const useIsNewEditorEnabled = () => {
return hasAccess && enabled
}
export const useIsNewErrorLogsPositionEnabled = () => {
const newEditorEnabled = useIsNewEditorEnabled()
return newEditorEnabled && canUseNewLogsPosition()
}
export const useAreNewErrorLogsEnabled = () => {
const newEditorEnabled = useIsNewEditorEnabled()
return newEditorEnabled && canUseNewLogs()
@@ -13,10 +13,12 @@ import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-er
import {
useAreNewErrorLogsEnabled,
useIsNewEditorEnabled,
useIsNewErrorLogsPositionEnabled,
} 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'
import NewPdfLogsViewer from '@/features/ide-redesign/components/pdf-preview/pdf-logs-viewer'
function PdfPreviewPane() {
const { pdfUrl } = useCompileContext()
@@ -26,6 +28,8 @@ function PdfPreviewPane() {
})
const newEditor = useIsNewEditorEnabled()
const newErrorLogs = useAreNewErrorLogsEnabled()
const newErrorLogsPosition = useIsNewErrorLogsPositionEnabled()
const pdfPromotions = importOverleafModules('pdfPreviewPromotions') as {
import: { default: ElementType }
path: string
@@ -48,7 +52,13 @@ function PdfPreviewPane() {
<PdfViewer />
</div>
</Suspense>
{newErrorLogs ? <PdfErrorState /> : <PdfLogsViewer />}
{newErrorLogsPosition ? (
<PdfErrorState />
) : newErrorLogs ? (
<NewPdfLogsViewer />
) : (
<PdfLogsViewer />
)}
{pdfPromotions.map(({ import: { default: Component }, path }) => (
<Component key={path} />
))}
@@ -1,79 +1,65 @@
import { useCallback, useEffect } from 'react'
import { useCallback } from 'react'
import { useLayoutContext } from '@/shared/context/layout-context'
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
import {
useAreNewErrorLogsEnabled,
useIsNewErrorLogsPositionEnabled,
} from '@/features/ide-redesign/utils/new-editor-utils'
import { useRailContext } from '@/features/ide-redesign/contexts/rail-context'
import { useEditorContext } from '@/shared/context/editor-context'
import useEventListener from '@/shared/hooks/use-event-listener'
/**
* This hook adds an event listener for events dispatched from the editor to the compile logs pane
*/
export const useLogEvents = (setShowLogs: (show: boolean) => void) => {
const { pdfLayout, setView } = useLayoutContext()
const newEditor = useIsNewEditorEnabled()
const newLogs = useAreNewErrorLogsEnabled()
const newLogsPosition = useIsNewErrorLogsPositionEnabled()
const { openTab: openRailTab } = useRailContext()
const { hasPremiumSuggestion } = useEditorContext()
const handleViewCompileLogEntryEventOldEditor = useCallback(
(event: Event) => {
const { id, suggestFix } = (
event as CustomEvent<{ id: string; suggestFix?: boolean }>
).detail
const selectLogOldLogs = useCallback((id: string, suggestFix: boolean) => {
window.setTimeout(() => {
const element = document.querySelector(
`.log-entry[data-log-entry-id="${id}"]`
)
setShowLogs(true)
if (element) {
element.scrollIntoView({
block: 'start',
inline: 'nearest',
})
if (pdfLayout === 'flat') {
setView('pdf')
}
if (suggestFix) {
// if they are paywalled, click that instead
const paywall = document.querySelector<HTMLButtonElement>(
'button[data-action="assistant-paywall-show"]'
)
window.setTimeout(() => {
const element = document.querySelector(
`.log-entry[data-log-entry-id="${id}"]`
)
if (element) {
element.scrollIntoView({
block: 'start',
inline: 'nearest',
})
if (suggestFix) {
// if they are paywalled, click that instead
const paywall = document.querySelector<HTMLButtonElement>(
'button[data-action="assistant-paywall-show"]'
)
if (paywall) {
paywall.scrollIntoView({
block: 'start',
inline: 'nearest',
})
paywall.click()
} else {
element
.querySelector<HTMLButtonElement>(
'button[data-action="suggest-fix"]'
)
?.click()
}
if (paywall) {
paywall.scrollIntoView({
block: 'start',
inline: 'nearest',
})
paywall.click()
} else {
element
.querySelector<HTMLButtonElement>(
'button[data-action="suggest-fix"]'
)
?.click()
}
}
})
},
[pdfLayout, setView, setShowLogs]
)
const handleViewCompileLogEntryEventNewEditor = useCallback(
(event: Event) => {
const { id, suggestFix, showPaywallIfOutOfSuggestions } = (
event as CustomEvent<{
id: string
suggestFix?: boolean
showPaywallIfOutOfSuggestions?: boolean
}>
).detail
openRailTab('errors')
}
})
}, [])
const selectLogNewLogs = useCallback(
(
id: string,
suggestFix: boolean,
showPaywallIfOutOfSuggestions: boolean
) => {
window.setTimeout(() => {
const logEntry = document.querySelector(
`.log-entry[data-log-entry-id="${id}"]`
@@ -114,26 +100,59 @@ export const useLogEvents = (setShowLogs: (show: boolean) => void) => {
}
})
},
[openRailTab, hasPremiumSuggestion]
[hasPremiumSuggestion]
)
useEffect(() => {
const listener = (event: Event) => {
if (newEditor) {
handleViewCompileLogEntryEventNewEditor(event)
const openNewLogsPosition = useCallback(() => {
openRailTab('errors')
}, [openRailTab])
const openOldLogsPosition = useCallback(() => {
setShowLogs(true)
if (pdfLayout === 'flat') {
setView('pdf')
}
}, [pdfLayout, setView, setShowLogs])
const handleViewCompileLogEntryEvent = useCallback(
(event: Event) => {
const { id, suggestFix, showPaywallIfOutOfSuggestions } = (
event as CustomEvent<{
id: string
suggestFix?: boolean
showPaywallIfOutOfSuggestions?: boolean
}>
).detail
if (newLogsPosition) {
openNewLogsPosition()
} else {
handleViewCompileLogEntryEventOldEditor(event)
openOldLogsPosition()
}
}
window.addEventListener('editor:view-compile-log-entry', listener)
if (newLogs) {
selectLogNewLogs(
id,
Boolean(suggestFix),
Boolean(showPaywallIfOutOfSuggestions)
)
} else {
selectLogOldLogs(id, Boolean(suggestFix))
}
},
[
openNewLogsPosition,
openOldLogsPosition,
selectLogNewLogs,
selectLogOldLogs,
newLogs,
newLogsPosition,
]
)
return () => {
window.removeEventListener('editor:view-compile-log-entry', listener)
}
}, [
handleViewCompileLogEntryEventNewEditor,
handleViewCompileLogEntryEventOldEditor,
newEditor,
])
useEventListener(
'editor:view-compile-log-entry',
handleViewCompileLogEntryEvent
)
}
@@ -5,7 +5,7 @@ import { useDetachCompileContext as useCompileContext } from '../../../shared/co
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
import MaterialIcon from '@/shared/components/material-icon'
import PdfLogEntry from '@/features/pdf-preview/components/pdf-log-entry'
import { useAreNewErrorLogsEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
import { useIsNewErrorLogsPositionEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
function PreviewLogsPaneMaxEntries({
totalEntries,
@@ -40,7 +40,7 @@ function PreviewLogsPaneMaxEntriesContent({
}: {
hasErrors?: boolean
}) {
const newLogs = useAreNewErrorLogsEnabled()
const newLogsPosition = useIsNewErrorLogsPositionEnabled()
const { t } = useTranslation()
const { startCompile, stoppedOnFirstError, setAnimateCompileDropdownArrow } =
useCompileContext()
@@ -79,7 +79,7 @@ function PreviewLogsPaneMaxEntriesContent({
/>{' '}
</p>
<p>
{newLogs
{newLogsPosition
? t('log_entry_maximum_entries_see_full_logs_new')
: t('log_entry_maximum_entries_see_full_logs')}
</p>
@@ -92,7 +92,7 @@ function PreviewLogsPaneMaxEntriesContent({
<MaterialIcon type="lightbulb" className="align-middle" />
&nbsp;
<strong>{t('tip')}: </strong>
{newLogs
{newLogsPosition
? t('log_entry_maximum_entries_see_full_logs_new')
: t('log_entry_maximum_entries_see_full_logs')}
</p>
@@ -177,6 +177,7 @@
.error-logs-tabs {
display: flex;
background-color: var(--bg-primary-themed);
}
.error-logs-tab-header {
@@ -204,6 +205,14 @@
}
}
.new-logs-pane {
position: absolute;
inset: 0;
z-index: 11; // above the PDF viewer + controls
top: var(--toolbar-small-height);
background-color: var(--bg-secondary-themed);
}
.logs-pane {
position: absolute;
inset: 0;
@@ -220,16 +229,6 @@
min-height: 100%;
}
.logs-pane-actions {
display: flex;
flex-wrap: wrap;
place-content: flex-end flex-end;
padding: var(--spacing-03) 0;
flex-grow: 1;
align-items: flex-end;
gap: var(--spacing-04);
}
.log-entry {
border-radius: var(--border-radius-base);
overflow: hidden;
@@ -340,6 +339,16 @@
}
}
.logs-pane-actions {
display: flex;
flex-wrap: wrap;
place-content: flex-end flex-end;
padding: var(--spacing-03) 0;
flex-grow: 1;
align-items: flex-end;
gap: var(--spacing-04);
}
.log-entry-content {
&:empty {
display: none;
+3
View File
@@ -149,6 +149,7 @@
"all": "All",
"all_borders": "All borders",
"all_features_in_group_standard_plus": "All features in Group Standard, plus:",
"all_logs": "All logs",
"all_premium_features": "All premium features",
"all_premium_features_including": "All premium features, including:",
"all_prices_displayed_are_in_currency": "All prices displayed are in __recommendedCurrency__.",
@@ -708,6 +709,7 @@
"error_opening_document_detail": "Sorry, something went wrong opening this document. Please try again.",
"error_performing_request": "An error has occurred while performing your request.",
"error_processing_file": "Sorry, something went wrong processing this file. Please try again.",
"errors": "Errors",
"es": "Spanish",
"essential_cookies_only": "Essential cookies only",
"estimated_number_of_overleaf_users": "Estimated number of __appName__ users",
@@ -2641,6 +2643,7 @@
"visual_editor_is_only_available_for_tex_files": "Visual Editor is only available for TeX files",
"want_access_to_overleaf_premium_features_through_your_university": "Want access to __appName__ premium features through your university?",
"want_change_to_apply_before_plan_end": "If you wish this change to apply before the end of your current billing period, please contact us.",
"warnings": "Warnings",
"we_are_unable_to_opt_you_into_this_experiment": "We are unable to opt you into this experiment at this time, please ensure your organization has allowed this feature, or try again later.",
"we_cant_confirm_this_email": "We can’t confirm this email",
"we_cant_find_any_sections_or_subsections_in_this_file": "We can’t find any sections or subsections in this file",