[web] hide online collaborators when offline (#35403)

GitOrigin-RevId: bea77d919c63740e0049467327720036876f64ee
This commit is contained in:
Kristina
2026-09-17 11:26:19 +00:00
committed by Copybot
parent 235685aa61
commit 6f55a97262
5 changed files with 134 additions and 84 deletions
@@ -1,8 +1,6 @@
import { useTranslation } from 'react-i18next'
import MaterialIcon from '@/shared/components/material-icon'
import OLTooltip from '@/shared/components/ol/ol-tooltip'
import { useUnsavedDocsContext } from '@/features/ide-react/context/unsaved-docs-context'
import { useFeatureFlag } from '@/shared/context/split-test-context'
export function OfflineIndicatorContent() {
const { t } = useTranslation()
@@ -20,22 +18,12 @@ export function OfflineIndicatorContent() {
)
}
export default function OfflineIndicator() {
export default function OfflineIndicator({
isOffline,
}: {
isOffline: boolean
}) {
const { t } = useTranslation()
const { unsavedDocs } = useUnsavedDocsContext()
const improvedFlakyConnections = useFeatureFlag(
'intermittent-connection-improvements'
)
if (!improvedFlakyConnections) {
return null
}
// TODO: use shared hook for this when merged
const MAX_UNSAVED_ALERT_SECONDS = 15
const isOffline = [...unsavedDocs.values()].some(
seconds => seconds >= MAX_UNSAVED_ALERT_SECONDS
)
return (
<>
{isOffline && <OfflineIndicatorContent />}
@@ -16,6 +16,7 @@ import UpgradeButton from './upgrade-button'
import getMeta from '@/utils/meta'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import useIsNetworkStalled from '@/features/ide-react/hooks/use-is-network-stalled'
import OLIconButton from '@/shared/components/ol/ol-icon-button'
import OLTooltip from '@/shared/components/ol/ol-tooltip'
import SplitTestBadge from '@/shared/components/split-test-badge'
@@ -33,7 +34,12 @@ export const Toolbar = () => {
const upgradeButtonRelocation = useFeatureFlag(
'editor-upgrade-button-relocation'
)
const improvedFlakyConnections = useFeatureFlag(
'intermittent-connection-improvements'
)
const { t } = useTranslation()
const isOfflineDueToNetworkStall = useIsNetworkStalled()
const shouldDisplaySubmitButton =
(permissionsLevel === 'owner' || permissionsLevel === 'readAndWrite') &&
SubmitProjectButton
@@ -130,9 +136,11 @@ export const Toolbar = () => {
</div>
<ToolbarProjectTitle />
<div className="ide-redesign-toolbar-actions-wrapper">
<OfflineIndicator />
{improvedFlakyConnections && (
<OfflineIndicator isOffline={isOfflineDueToNetworkStall} />
)}
<div className="ide-redesign-toolbar-actions">
<OnlineUsers />
{!isOfflineDueToNetworkStall && <OnlineUsers />}
{!isRestrictedTokenMember && <ShowHistoryButton />}
<ChangeLayoutButton />
{shouldDisplaySubmitButton && cobranding && (
@@ -1,79 +1,19 @@
import OfflineIndicator from '@/features/ide-react/components/toolbar/offline-indicator'
import { UnsavedDocsContext } from '@/features/ide-react/context/unsaved-docs-context'
import { IdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
import { SplitTestProvider } from '@/shared/context/split-test-context'
const defaultIdeReactContextValue = {
projectId: 'test-project',
eventEmitter: new IdeEventEmitter(),
startedFreeTrial: false,
setStartedFreeTrial: () => {},
reportError: () => {},
projectJoined: true,
permissionsLevel: 'owner' as const,
setPermissionsLevel: () => {},
outOfSync: false,
setOutOfSync: () => {},
}
const defaultUnsavedDocsContextValue = {
unsavedDocs: new Map<string, number>(),
isLocked: false,
isSavingStalled: false,
}
const mount = ({
unsavedDocs = new Map<string, number>(),
}: {
unsavedDocs?: Map<string, number>
} = {}) => {
cy.mount(
<SplitTestProvider>
<IdeReactContext.Provider value={defaultIdeReactContextValue}>
<UnsavedDocsContext.Provider
value={{ ...defaultUnsavedDocsContextValue, unsavedDocs }}
>
<OfflineIndicator />
</UnsavedDocsContext.Provider>
</IdeReactContext.Provider>
</SplitTestProvider>
)
}
const enableFlag = () => {
cy.window().then(win => {
win.metaAttributesCache.set('ol-splitTestVariants', {
'intermittent-connection-improvements': 'enabled',
})
})
}
const unsavedDoc = () => new Map<string, number>([['doc-1', 20]])
describe('<OfflineIndicator />', function () {
it('is hidden when the feature flag is disabled', function () {
mount({ unsavedDocs: unsavedDoc() })
cy.findByText('You’re offline').should('not.exist')
cy.findByRole('status').should('not.exist')
})
it('is hidden when the flag is enabled but there are no unsaved docs', function () {
enableFlag()
mount()
it('renders an empty live region when online', function () {
cy.mount(<OfflineIndicator isOffline={false} />)
cy.findByText('You’re offline').should('not.exist')
cy.findByRole('status').should('be.empty')
})
it('is shown when the flag is enabled and there are unsaved docs', function () {
enableFlag()
mount({ unsavedDocs: unsavedDoc() })
it('shows the offline indicator when offline', function () {
cy.mount(<OfflineIndicator isOffline />)
cy.findAllByText('You’re offline').first().should('be.visible')
})
it('announces the offline status to screen readers via a live region', function () {
enableFlag()
mount({ unsavedDocs: unsavedDoc() })
cy.mount(<OfflineIndicator isOffline />)
cy.findByRole('status').within(() => {
cy.findByText('You’re offline').should('exist')
cy.findByText(
@@ -0,0 +1,47 @@
import { OnlineUsers } from '@/features/ide-react/components/toolbar/online-users'
import { EditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { OnlineUsersContext } from '@/features/ide-react/context/online-users-context'
import type { OnlineUser } from '@/features/ide-react/context/online-users-context'
const onlineUser: OnlineUser = {
id: 'client-1',
user_id: 'user-1',
email: 'alice@example.com',
name: 'alice',
initial: 'a',
}
const mount = ({
onlineUsersArray = [onlineUser],
}: {
onlineUsersArray?: OnlineUser[]
} = {}) => {
const mockOpenDoc = cy.stub().as('openDoc')
cy.mount(
<EditorManagerContext.Provider value={{ openDoc: mockOpenDoc } as any}>
<OnlineUsersContext.Provider
value={{
onlineUsers: {},
onlineUserCursorHighlights: {},
onlineUsersArray,
onlineUsersCount: onlineUsersArray.length,
}}
>
<OnlineUsers />
</OnlineUsersContext.Provider>
</EditorManagerContext.Provider>
)
}
describe('<OnlineUsers />', function () {
it('renders nothing when there are no online users', function () {
mount({ onlineUsersArray: [] })
cy.get('.ide-redesign-online-users').should('not.exist')
})
it('renders the widget when there are online users', function () {
mount()
cy.get('.ide-redesign-online-users').should('exist')
cy.findByText('a').should('be.visible')
})
})
@@ -4,6 +4,11 @@ import {
makeEditorProvider,
} from '../../../helpers/editor-providers'
import { Cobranding } from '@ol-types/cobranding'
import {
OnlineUsersContext,
type OnlineUser,
} from '@/features/ide-react/context/online-users-context'
import { UnsavedDocsContext } from '@/features/ide-react/context/unsaved-docs-context'
import partnerLogoUrl from './cobranding-logo.png'
describe('<Toolbar />', function () {
@@ -245,4 +250,66 @@ describe('<Toolbar />', function () {
// TODO: Test all the dynamic items
})
describe('offline gating', function () {
const onlineUser: OnlineUser = {
id: 'client-1',
user_id: 'user-1',
email: 'alice@example.com',
name: 'alice',
initial: 'a',
}
const mountToolbar = ({ offline }: { offline: boolean }) => {
const unsavedDocs = offline ? new Map([['doc-1', 20]]) : new Map()
cy.mount(
<EditorProviders>
<OnlineUsersContext.Provider
value={{
onlineUsers: {},
onlineUserCursorHighlights: {},
onlineUsersArray: [onlineUser],
onlineUsersCount: 1,
}}
>
<UnsavedDocsContext.Provider
value={{ unsavedDocs, isLocked: false, isSavingStalled: offline }}
>
<Toolbar />
</UnsavedDocsContext.Provider>
</OnlineUsersContext.Provider>
</EditorProviders>
)
}
describe('with intermittent-connection-improvements enabled', function () {
beforeEach(function () {
cy.window().then(win => {
win.metaAttributesCache.set('ol-splitTestVariants', {
'intermittent-connection-improvements': 'enabled',
})
})
})
it('shows online users and no offline indicator when online', function () {
mountToolbar({ offline: false })
cy.get('.ide-redesign-online-users').should('exist')
cy.findByText('You’re offline').should('not.exist')
})
it('hides online users and shows the offline indicator when offline', function () {
mountToolbar({ offline: true })
cy.get('.ide-redesign-online-users').should('not.exist')
cy.findAllByText('You’re offline').first().should('be.visible')
})
})
describe('with intermittent-connection-improvements disabled', function () {
it('always shows online users and never the offline indicator', function () {
mountToolbar({ offline: true })
cy.get('.ide-redesign-online-users').should('exist')
cy.findByText('You’re offline').should('not.exist')
})
})
})
})