diff --git a/services/web/frontend/js/features/ide-react/components/alerts/alerts.tsx b/services/web/frontend/js/features/ide-react/components/alerts/alerts.tsx index 6f80e85cdb..e8ad0e60c0 100644 --- a/services/web/frontend/js/features/ide-react/components/alerts/alerts.tsx +++ b/services/web/frontend/js/features/ide-react/components/alerts/alerts.tsx @@ -7,6 +7,7 @@ import { createPortal } from 'react-dom' import { useGlobalAlertsContainer } from '@/features/ide-react/context/global-alerts-context' import Notification from '@/shared/components/notification' import importOverleafModules from '../../../../../macros/import-overleaf-module.macro' +import { useFeatureFlag } from '@/shared/context/split-test-context' const rollingBuildsUpdatedAlert: Array<{ import: { default: ElementType } @@ -23,6 +24,9 @@ export function Alerts() { secondsUntilReconnect, } = useConnectionContext() const globalAlertsContainer = useGlobalAlertsContainer() + const improvedFlakyConnections = useFeatureFlag( + 'intermittent-connection-improvements' + ) if (!globalAlertsContainer) { return null @@ -36,56 +40,60 @@ export function Alerts() { ) )} - {connectionState.forceDisconnected && - // hide "disconnected" banner when displaying out of sync modal - connectionState.error !== 'out-of-sync' ? ( -
- {t('disconnected')}} - /> -
- ) : null} + {!improvedFlakyConnections && ( + <> + {connectionState.forceDisconnected && + // hide "disconnected" banner when displaying out of sync modal + connectionState.error !== 'out-of-sync' ? ( +
+ {t('disconnected')}} + /> +
+ ) : null} - {connectionState.reconnectAt ? ( - - ) : null} + {connectionState.reconnectAt ? ( + + ) : null} - {isStillReconnecting ? ( -
- {t('reconnecting')}…} - /> -
- ) : null} + {isStillReconnecting ? ( +
+ {t('reconnecting')}…} + /> +
+ ) : null} - {connectionState.inactiveDisconnect || - (connectionState.readyState === WebSocket.CLOSED && - (connectionState.error === 'rate-limited' || - connectionState.error === 'unable-to-connect') && - !secondsUntilReconnect()) ? ( -
- {t('editor_disconected_click_to_reconnect')} - } - /> -
- ) : null} + {connectionState.inactiveDisconnect || + (connectionState.readyState === WebSocket.CLOSED && + (connectionState.error === 'rate-limited' || + connectionState.error === 'unable-to-connect') && + !secondsUntilReconnect()) ? ( +
+ {t('editor_disconected_click_to_reconnect')} + } + /> +
+ ) : null} - {debugging ? ( -
- Connected: {isConnected.toString()}} - /> -
- ) : null} + {debugging ? ( +
+ Connected: {isConnected.toString()}} + /> +
+ ) : null} + + )} , globalAlertsContainer ) diff --git a/services/web/frontend/js/features/ide-react/components/unsaved-docs/unsaved-docs-locked-alert.tsx b/services/web/frontend/js/features/ide-react/components/unsaved-docs/unsaved-docs-locked-alert.tsx index 88dab5f33e..499c02bdbd 100644 --- a/services/web/frontend/js/features/ide-react/components/unsaved-docs/unsaved-docs-locked-alert.tsx +++ b/services/web/frontend/js/features/ide-react/components/unsaved-docs/unsaved-docs-locked-alert.tsx @@ -3,11 +3,15 @@ import { useTranslation } from 'react-i18next' import Notification from '@/shared/components/notification' import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context' import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context' +import { useFeatureFlag } from '@/shared/context/split-test-context' export const UnsavedDocsLockedAlert: FC = () => { const { t } = useTranslation() const { openDocs } = useEditorManagerContext() const { reportError } = useIdeReactContext() + const improvedFlakyConnections = useFeatureFlag( + 'intermittent-connection-improvements' + ) useEffect(() => { const { pendingOpsLength, inflightOpsLength } = openDocs.getUnsavedOpsSize() @@ -17,6 +21,10 @@ export const UnsavedDocsLockedAlert: FC = () => { }) }, [reportError, openDocs]) + if (improvedFlakyConnections) { + return null + } + return (
0, + tryReconnectNow: () => {}, + registerUserActivity: () => {}, + closeConnection: () => {}, + getSocketDebuggingInfo: () => ({ id: '' }), +} as any + +const projectContextValue = { + project: { imageName: 'texlive-full:2023.1' }, +} as any + +const mount = () => { + cy.mount( + + + + + + + + + + + + ) +} + +const enableFlag = () => { + cy.window().then(win => { + win.metaAttributesCache.set('ol-splitTestVariants', { + 'intermittent-connection-improvements': 'enabled', + }) + }) +} + +describe('', function () { + it('shows the connection alert when flag is disabled', function () { + mount() + cy.findByRole('alert').should('exist') + }) + + it('suppresses the connection alert when flag is enabled', function () { + enableFlag() + mount() + cy.findByRole('alert').should('not.exist') + }) +}) diff --git a/services/web/test/frontend/features/ide-react/components/unsaved-docs/unsaved-docs.spec.tsx b/services/web/test/frontend/features/ide-react/components/unsaved-docs/unsaved-docs.spec.tsx index a01bec2a01..1fc485a42a 100644 --- a/services/web/test/frontend/features/ide-react/components/unsaved-docs/unsaved-docs.spec.tsx +++ b/services/web/test/frontend/features/ide-react/components/unsaved-docs/unsaved-docs.spec.tsx @@ -4,6 +4,8 @@ import { GlobalAlertsProvider } from '@/features/ide-react/context/global-alerts import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path' import { ConnectionContext } from '@/features/ide-react/context/connection-context' import { SplitTestProvider } from '@/shared/context/split-test-context' +import { EditorManagerContext } from '@/features/ide-react/context/editor-manager-context' +import { IdeReactContext } from '@/features/ide-react/context/ide-react-context' const defaultUnsavedDocsContextValue = { unsavedDocs: new Map([['doc1', 16]]), @@ -37,16 +39,37 @@ const mockFileTreePathContextValue = { previewByPath: () => null, } -const mount = () => { +const mockEditorManagerContextValue = { + openDocs: { + getUnsavedOpsSize: () => ({ pendingOpsLength: 2, inflightOpsLength: 1 }), + }, +} as any + +const mount = ({ + unsavedDocsContextValue = defaultUnsavedDocsContextValue, + reportError = cy.stub(), +}: { + unsavedDocsContextValue?: typeof defaultUnsavedDocsContextValue + reportError?: any +} = {}) => { + const ideReactContextValue = { reportError } as any cy.mount( - - - - - + + + + + + + + + @@ -72,4 +95,34 @@ describe('', function () { mount() cy.findByRole('alert').should('not.exist') }) + + describe('when locked', function () { + const lockedContextValue = { + unsavedDocs: new Map(), + isLocked: true, + } + + it('shows the locked alert and reports the error when flag is disabled', function () { + const reportError = cy.stub().as('reportError') + mount({ unsavedDocsContextValue: lockedContextValue, reportError }) + cy.findByRole('alert').should('exist') + cy.get('@reportError').should( + 'have.been.calledOnceWith', + 'connection-lost-with-unsaved-changes', + { pendingOpsLength: 2, inflightOpsLength: 1 } + ) + }) + + it('hides the locked alert but still reports the error when flag is enabled', function () { + const reportError = cy.stub().as('reportError') + enableFlag() + mount({ unsavedDocsContextValue: lockedContextValue, reportError }) + cy.findByRole('alert').should('not.exist') + cy.get('@reportError').should( + 'have.been.calledOnceWith', + 'connection-lost-with-unsaved-changes', + { pendingOpsLength: 2, inflightOpsLength: 1 } + ) + }) + }) })