Merge pull request #18323 from overleaf/mj-editor-loss-debug

[web] DEBUG: Calculate diff size when detecting edit-loss

GitOrigin-RevId: 750f7ed91d5ce81beb520e23181a6a8dc22b6078
This commit is contained in:
Mathias Jakobsen 2024-05-15 15:24:29 +01:00 committed by Copybot
parent c1a4e4a873
commit 2ce41e0ee6
3 changed files with 108 additions and 4 deletions

View file

@ -11,7 +11,7 @@ import { useGlobalAlertsContainer } from '@/features/ide-react/context/global-al
const MAX_UNSAVED_SECONDS = 15 // lock the editor after this time if unsaved
export const UnsavedDocs: FC = () => {
const { openDocs } = useEditorManagerContext()
const { openDocs, debugTimers } = useEditorManagerContext()
const { permissionsLevel, setPermissionsLevel } = useEditorContext()
const [isLocked, setIsLocked] = useState(false)
const [unsavedDocs, setUnsavedDocs] = useState(new Map<string, number>())
@ -47,6 +47,7 @@ export const UnsavedDocs: FC = () => {
// NOTE: openDocs should never change, so it's safe to use as a dependency here
useEffect(() => {
const interval = window.setInterval(() => {
debugTimers.current.CheckUnsavedDocs = Date.now()
const unsavedDocs = new Map()
const unsavedDocIds = openDocs.unsavedDocIds()
@ -67,7 +68,7 @@ export const UnsavedDocs: FC = () => {
return () => {
window.clearInterval(interval)
}
}, [openDocs])
}, [openDocs, debugTimers])
const maxUnsavedSeconds = Math.max(0, ...unsavedDocs.values())

View file

@ -30,6 +30,7 @@ import useEventListener from '@/shared/hooks/use-event-listener'
import { EditorType } from '@/features/ide-react/editor/types/editor-type'
import { DocId } from '../../../../../types/project-settings'
import { Update } from '@/features/history/services/types/update'
import { useDebugDiffTracker } from '../hooks/use-debug-diff-tracker'
interface GotoOffsetOptions {
gotoOffset: number
@ -60,6 +61,7 @@ export type EditorManager = {
setWantTrackChanges: React.Dispatch<
React.SetStateAction<EditorManager['wantTrackChanges']>
>
debugTimers: React.MutableRefObject<Record<string, number>>
}
function hasGotoLine(options: OpenDocOptions): options is GotoLineOptions {
@ -126,12 +128,32 @@ export const EditorManagerProvider: FC = ({ children }) => {
const [ignoringExternalUpdates, setIgnoringExternalUpdates] = useState(false)
const { createDebugDiff, debugTimers } = useDebugDiffTracker(
projectId,
currentDocument
)
const [globalEditorWatchdogManager] = useState(
() =>
new EditorWatchdogManager({
onTimeoutHandler: (meta: Record<string, any>) => {
sendMB('losing-edits', meta)
reportError('losing-edits', meta)
let diffSize: number | null = null
createDebugDiff()
.then(calculatedDiffSize => {
diffSize = calculatedDiffSize
})
.finally(() => {
sendMB('losing-edits', {
...meta,
diffSize,
timers: debugTimers.current,
})
reportError('losing-edits', {
...meta,
diffSize,
timers: debugTimers.current,
})
})
},
})
)
@ -616,6 +638,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
jumpToLine,
wantTrackChanges,
setWantTrackChanges,
debugTimers,
}),
[
getEditorType,
@ -633,6 +656,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
jumpToLine,
wantTrackChanges,
setWantTrackChanges,
debugTimers,
]
)

View file

@ -0,0 +1,79 @@
import { useEffect, useMemo, useRef } from 'react'
import { DocumentContainer } from '../editor/document-container'
import { DocId } from '../../../../../types/project-settings'
import { debugConsole } from '@/utils/debugging'
import { diffChars } from 'diff'
const DIFF_TIMEOUT_MS = 5000
async function tryGetDiffSize(
currentContents: string | null | undefined,
projectId: string | null,
docId: DocId | null | undefined
): Promise<number | null> {
debugConsole.debug('tryGetDiffSize')
// If we don't know the current content or id, there's not much we can do
if (!projectId) {
debugConsole.debug('tryGetDiffSize: missing projectId')
return null
}
if (!currentContents) {
debugConsole.debug('tryGetDiffSize: missing currentContents')
return null
}
if (!docId) {
debugConsole.debug('tryGetDiffSize: missing docId')
return null
}
try {
const response = await fetch(
`/Project/${projectId}/doc/${docId}/download`,
{ signal: AbortSignal.timeout(DIFF_TIMEOUT_MS) }
)
const serverContent = await response.text()
const differences = diffChars(serverContent, currentContents)
let diffSize = 0
for (const diff of differences) {
if (diff.added || diff.removed) {
diffSize += diff.value.length
}
}
return diffSize
} catch {
// There's a good chance we're offline, so just return null
debugConsole.debug('tryGetDiffSize: fetch failed')
return null
}
}
export const useDebugDiffTracker = (
projectId: string,
currentDocument: DocumentContainer
) => {
const debugCurrentDocument = useRef<DocumentContainer | null>(null)
const debugProjectId = useRef<string | null>(null)
const debugTimers = useRef<Record<string, number>>({})
useEffect(() => {
debugCurrentDocument.current = currentDocument
}, [currentDocument])
useEffect(() => {
debugProjectId.current = projectId
}, [projectId])
const createDebugDiff = useMemo(
() => async () =>
await tryGetDiffSize(
debugCurrentDocument.current?.getSnapshot(),
debugProjectId.current,
debugCurrentDocument.current?.doc_id as DocId | undefined
),
[]
)
return {
createDebugDiff,
debugTimers,
}
}