فهرست منبع

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

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

GitOrigin-RevId: 750f7ed91d5ce81beb520e23181a6a8dc22b6078
Mathias Jakobsen 2 سال پیش
والد
کامیت
2ce41e0ee6

+ 3 - 2
services/web/frontend/js/features/ide-react/components/unsaved-docs/unsaved-docs.tsx

@@ -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())
 

+ 26 - 2
services/web/frontend/js/features/ide-react/context/editor-manager-context.tsx

@@ -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,
     ]
   )
 

+ 79 - 0
services/web/frontend/js/features/ide-react/hooks/use-debug-diff-tracker.ts

@@ -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,
+  }
+}