From c4b3cd2a77ee5288663da23be057843de8881d09 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Wed, 12 Nov 2025 10:34:18 +0000 Subject: [PATCH] Merge pull request #29511 from overleaf/dp-new-users-to-new-editor Move all new users to use the new editor GitOrigin-RevId: e3611e5853da4b96db9f4cc37114ededb8632aed --- .../settings/settings-new-editor.tsx | 4 +- .../try-new-editor-button.tsx | 4 +- .../components/switcher-modal/beta-modal.tsx | 4 +- .../components/toolbar/beta-actions.tsx | 4 +- .../components/toolbar/menu-bar.tsx | 4 +- .../contexts/settings-modal-context.tsx | 4 +- .../ide-redesign/utils/new-editor-utils.ts | 56 +++++++----- .../components/timeout-upgrade-prompt-new.tsx | 22 ++--- .../pdf-preview/new-editor-utils.test.tsx | 86 ------------------- 9 files changed, 50 insertions(+), 138 deletions(-) delete mode 100644 services/web/test/frontend/components/pdf-preview/new-editor-utils.test.tsx diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-new-editor.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-new-editor.tsx index c57ec12924..59a377f03c 100644 --- a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-new-editor.tsx +++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-new-editor.tsx @@ -4,7 +4,7 @@ import { useSwitchEnableNewEditorState } from '@/features/ide-redesign/hooks/use import { useLayoutContext } from '@/shared/context/layout-context' import { useCallback } from 'react' import { - canUseNewEditorViaNewUserFeatureFlag, + canUseNewEditorAsNewUser, useIsNewEditorEnabled, } from '@/features/ide-redesign/utils/new-editor-utils' @@ -13,7 +13,7 @@ export default function SettingsNewEditor() { const { setEditorRedesignStatus } = useSwitchEnableNewEditorState() const { setLeftMenuShown } = useLayoutContext() const enabled = useIsNewEditorEnabled() - const show = canUseNewEditorViaNewUserFeatureFlag() + const show = canUseNewEditorAsNewUser() const onChange = useCallback( (newValue: boolean) => { diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx b/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx index cedb4d3e51..7bfdc034de 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx @@ -2,13 +2,13 @@ import { useCallback } from 'react' import OLButton from '../../shared/components/ol/ol-button' import { useIdeRedesignSwitcherContext } from '../ide-react/context/ide-redesign-switcher-context' import { useTranslation } from 'react-i18next' -import { canUseNewEditorViaPrimaryFeatureFlag } from '../ide-redesign/utils/new-editor-utils' +import { canUseNewEditorAsExistingUser } from '../ide-redesign/utils/new-editor-utils' import { useSwitchEnableNewEditorState } from '../ide-redesign/hooks/use-switch-enable-new-editor-state' const TryNewEditorButton = () => { const { t } = useTranslation() const { setShowSwitcherModal } = useIdeRedesignSwitcherContext() - const showModal = canUseNewEditorViaPrimaryFeatureFlag() + const showModal = canUseNewEditorAsExistingUser() const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState() const onClick = useCallback(() => { diff --git a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/beta-modal.tsx b/services/web/frontend/js/features/ide-redesign/components/switcher-modal/beta-modal.tsx index b571c03aa8..43dad33c8b 100644 --- a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/beta-modal.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/switcher-modal/beta-modal.tsx @@ -11,7 +11,7 @@ import { FC, useCallback, useEffect } from 'react' import { canUseNewEditor, useIsNewEditorEnabled, - useIsNewEditorEnabledViaPrimaryFeatureFlag, + useIsNewEditorEnabledAsExistingUser, } from '../../utils/new-editor-utils' import Notification from '@/shared/components/notification' import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state' @@ -32,7 +32,7 @@ export const IdeRedesignIntroModal: FC = () => { name: TUTORIAL_KEY, } ) - const hasAccess = useIsNewEditorEnabledViaPrimaryFeatureFlag() + const hasAccess = useIsNewEditorEnabledAsExistingUser() useEffect(() => { if (!hasAccess) return diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx index 9071960efa..a45f33ad92 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx @@ -4,7 +4,7 @@ import OLTooltip from '@/shared/components/ol/ol-tooltip' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { GiveFeedbackLink } from './give-feedback-link' -import { useIsNewEditorEnabledViaPrimaryFeatureFlag } from '../../utils/new-editor-utils' +import { useIsNewEditorEnabledAsExistingUser } from '../../utils/new-editor-utils' export const BetaActions = () => { const { t } = useTranslation() @@ -12,7 +12,7 @@ export const BetaActions = () => { const openEditorRedesignSwitcherModal = useCallback(() => { setShowSwitcherModal(true) }, [setShowSwitcherModal]) - const showBetaActions = useIsNewEditorEnabledViaPrimaryFeatureFlag() + const showBetaActions = useIsNewEditorEnabledAsExistingUser() if (!showBetaActions) { return null diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx index 1561a1f1b3..7669230745 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx @@ -30,7 +30,7 @@ import { useSurveyUrl } from '../../hooks/use-survey-url' import getMeta from '@/utils/meta' import EditorCloneProjectModalWrapper from '@/features/clone-project-modal/components/editor-clone-project-modal-wrapper' import useOpenProject from '@/shared/hooks/use-open-project' -import { canUseNewEditorViaPrimaryFeatureFlag } from '../../utils/new-editor-utils' +import { canUseNewEditorAsExistingUser } from '../../utils/new-editor-utils' export const ToolbarMenuBar = () => { const { t } = useTranslation() @@ -44,7 +44,7 @@ export const ToolbarMenuBar = () => { const [showWordCountModal, setShowWordCountModal] = useState(false) const [showCloneProjectModal, setShowCloneProjectModal] = useState(false) const openProject = useOpenProject() - const showEditorSwitchMenuOption = canUseNewEditorViaPrimaryFeatureFlag() + const showEditorSwitchMenuOption = canUseNewEditorAsExistingUser() const anonymous = getMeta('ol-anonymous') diff --git a/services/web/frontend/js/features/ide-redesign/contexts/settings-modal-context.tsx b/services/web/frontend/js/features/ide-redesign/contexts/settings-modal-context.tsx index 4a271afd9a..832d2ade5a 100644 --- a/services/web/frontend/js/features/ide-redesign/contexts/settings-modal-context.tsx +++ b/services/web/frontend/js/features/ide-redesign/contexts/settings-modal-context.tsx @@ -25,7 +25,7 @@ import FontFamilySetting from '../components/settings/appearance-settings/font-f import { AvailableUnfilledIcon } from '@/shared/components/material-icon' import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context' import NewEditorSetting from '../components/settings/editor-settings/new-editor-setting' -import { canUseNewEditorViaNewUserFeatureFlag } from '../utils/new-editor-utils' +import { canUseNewEditorAsNewUser } from '../utils/new-editor-utils' const [referenceSearchSettingModule] = importOverleafModules( 'referenceSearchSetting' @@ -77,7 +77,7 @@ export const SettingsModalProvider: FC = ({ children, }) => { const { t } = useTranslation() - const showEditorSwitch = canUseNewEditorViaNewUserFeatureFlag() + const showEditorSwitch = canUseNewEditorAsNewUser() // TODO ide-redesign-cleanup: Rename this field and move it directly into this context const { leftMenuShown, setLeftMenuShown } = useLayoutContext() diff --git a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts index fa6d0d9f1e..c2b8c06235 100644 --- a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts +++ b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts @@ -2,13 +2,24 @@ import { useUserSettingsContext } from '@/shared/context/user-settings-context' import getMeta from '@/utils/meta' import { isSplitTestEnabled, getSplitTestVariant } from '@/utils/splitTestUtils' -export const ignoringUserCutoffDate = +const ignoringUserCutoffDate = new URLSearchParams(window.location.search).get('skip-new-user-check') === 'true' -const NEW_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 8, 23, 13, 0, 0)) // 2pm British Summer Time on September 23, 2025 +// For E2E tests, allow forcing a user to be treated as an existing user +const existingUserOverride = + new URLSearchParams(window.location.search).get('existing-user-override') === + 'true' + +// We don't want to enable the new editor on server-pro/CE until we have fully rolled it out on SaaS +const { isOverleaf } = getMeta('ol-ExposedSettings') + +const SPLIT_TEST_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 8, 23, 13, 0, 0)) // 2pm British Summer Time on September 23, 2025 +const NEW_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 10, 12, 12, 0, 0)) // 12pm GMT on November 12, 2025 export const isNewUser = () => { + if (existingUserOverride) return false + if (ignoringUserCutoffDate) return true const user = getMeta('ol-user') @@ -18,32 +29,37 @@ export const isNewUser = () => { return createdAt > NEW_USER_CUTOFF_DATE } -export const canUseNewEditorViaPrimaryFeatureFlag = () => { - return isSplitTestEnabled('editor-redesign') +export const isSplitTestUser = () => { + if (existingUserOverride) return false + + const user = getMeta('ol-user') + if (!user.signUpDate) return false + + const createdAt = new Date(user.signUpDate) + return ( + createdAt > SPLIT_TEST_USER_CUTOFF_DATE && createdAt <= NEW_USER_CUTOFF_DATE + ) } -export const canUseNewEditorViaNewUserFeatureFlag = () => { - const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users') +export const canUseNewEditorAsExistingUser = () => { + return !canUseNewEditorAsNewUser() && isSplitTestEnabled('editor-redesign') +} +export const canUseNewEditorAsNewUser = () => { + const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users') return ( - !canUseNewEditorViaPrimaryFeatureFlag() && - isNewUser() && - (newUserTestVariant === 'new-editor' || - newUserTestVariant === 'new-editor-old-logs' || - newUserTestVariant === 'new-editor-new-logs-old-position') + isOverleaf && + (isNewUser() || (isSplitTestUser() && newUserTestVariant !== 'default')) ) } export const canUseNewEditor = () => { - return ( - canUseNewEditorViaPrimaryFeatureFlag() || - canUseNewEditorViaNewUserFeatureFlag() - ) + return canUseNewEditorAsExistingUser() || canUseNewEditorAsNewUser() } -export const useIsNewEditorEnabledViaPrimaryFeatureFlag = () => { +export const useIsNewEditorEnabledAsExistingUser = () => { const { userSettings } = useUserSettingsContext() - const hasAccess = canUseNewEditorViaPrimaryFeatureFlag() + const hasAccess = canUseNewEditorAsExistingUser() const enabled = userSettings.enableNewEditor return hasAccess && enabled } @@ -54,9 +70,3 @@ export const useIsNewEditorEnabled = () => { const enabled = userSettings.enableNewEditor return hasAccess && enabled } - -export function useNewEditorVariant() { - const newEditor = useIsNewEditorEnabled() - if (!newEditor) return 'default' - return 'new-editor-new-logs-old-position' -} diff --git a/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx b/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx index b279f1ca84..8c0dc7c41f 100644 --- a/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx @@ -8,12 +8,9 @@ import OLButton from '@/shared/components/ol/ol-button' import * as eventTracking from '../../../infrastructure/event-tracking' import getMeta from '@/utils/meta' import { populateEditorRedesignSegmentation } from '@/shared/hooks/use-editor-analytics' -import { - isNewUser, - useIsNewEditorEnabled, -} from '@/features/ide-redesign/utils/new-editor-utils' -import { getSplitTestVariant, isSplitTestEnabled } from '@/utils/splitTestUtils' import CompileTimeoutPaywallModal from '@/features/pdf-preview/components/compile-timeout-paywall-modal' +import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils' +import { isSplitTestEnabled } from '@/utils/splitTestUtils' function TimeoutUpgradePromptNew() { const { @@ -98,21 +95,12 @@ const CompileTimeout = memo(function CompileTimeout({ isCompileTimeoutTargetPlansEnabled, }: CompileTimeoutProps) { const { t } = useTranslation() + const newEditor = useIsNewEditorEnabled() const extraSearchParams = useMemo(() => { - if (!isNewUser()) { - return undefined - } - - const variant = getSplitTestVariant('editor-redesign-new-users') - - if (!variant) { - return undefined - } - return { - itm_content: variant, + itm_content: newEditor ? 'new-editor' : 'old-editor', } - }, []) + }, [newEditor]) const handleFreeTrialClick = useCallback( (event: React.MouseEvent) => { diff --git a/services/web/test/frontend/components/pdf-preview/new-editor-utils.test.tsx b/services/web/test/frontend/components/pdf-preview/new-editor-utils.test.tsx deleted file mode 100644 index bcacd7898b..0000000000 --- a/services/web/test/frontend/components/pdf-preview/new-editor-utils.test.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { expect } from 'chai' -import { mockScope } from './scope' -import { EditorProviders } from '../../helpers/editor-providers' -import { renderHook } from '@testing-library/react' -import { useNewEditorVariant } from '@/features/ide-redesign/utils/new-editor-utils' - -describe('new-editor-utils', function () { - describe('useNewEditorVariant', function () { - const newEditorVariants = [ - { splitTestVariant: 'default', uiVariant: 'default' }, - { - splitTestVariant: 'new-editor', - uiVariant: 'new-editor-new-logs-old-position', - }, - { - splitTestVariant: 'new-editor-old-logs', - uiVariant: 'new-editor-new-logs-old-position', - }, - { - splitTestVariant: 'new-editor-new-logs-old-position', - uiVariant: 'new-editor-new-logs-old-position', - }, - ] - for (const variant of newEditorVariants) { - it(`forwards ?editor-redesign-new-users=${variant}`, function () { - window.metaAttributesCache.set('ol-splitTestVariants', { - 'editor-redesign-new-users': variant.splitTestVariant, - }) - - const scope = mockScope() - - const { result } = renderHook(() => useNewEditorVariant(), { - wrapper: ({ children }) => ( - - {children} - - ), - }) - expect(result.current).to.equal(variant.uiVariant) - }) - } - for (const variant of newEditorVariants) { - it(`ignores ?editor-redesign-new-users=${variant} when disabled by user`, function () { - window.metaAttributesCache.set('ol-splitTestVariants', { - 'editor-redesign-new-users': variant.splitTestVariant, - }) - - const scope = mockScope() - - const { result } = renderHook(() => useNewEditorVariant(), { - wrapper: ({ children }) => ( - - {children} - - ), - }) - expect(result.current).to.equal('default') - }) - } - it(`handles ?editor-redesign=enabled`, function () { - window.metaAttributesCache.set('ol-splitTestVariants', { - 'editor-redesign': 'enabled', - }) - - const scope = mockScope() - - const { result } = renderHook(() => useNewEditorVariant(), { - wrapper: ({ children }) => ( - - {children} - - ), - }) - expect(result.current).to.equal('new-editor-new-logs-old-position') - }) - }) -})