Explorar o código

Merge pull request #18801 from overleaf/mj-revert-big-deploy

[web+chat] Revert big deploy

GitOrigin-RevId: f4d068cbea7e5f5d59a3d698e2def2f94c9b6322
Mathias Jakobsen %!s(int64=2) %!d(string=hai) anos
pai
achega
a26e3d3376

+ 0 - 46
services/chat/app/js/Features/Messages/MessageHttpController.js

@@ -82,14 +82,6 @@ export async function destroyProject(context) {
   return await callMessageHttpController(context, _destroyProject)
 }
 
-export async function duplicateCommentThreads(context) {
-  return await callMessageHttpController(context, _duplicateCommentThreads)
-}
-
-export async function generateThreadData(context) {
-  return await callMessageHttpController(context, _generateThreadData)
-}
-
 export async function getStatus(context) {
   const message = 'chat is alive'
   context.res.status(200).setBody(message)
@@ -128,18 +120,6 @@ const _getAllThreads = async (req, res) => {
   res.json(threads)
 }
 
-const _generateThreadData = async (req, res) => {
-  const { projectId } = req.params
-  const { threads } = req.body
-  logger.debug({ projectId }, 'getting all threads')
-  const rooms = await ThreadManager.findThreadsById(projectId, threads)
-  const roomIds = rooms.map(r => r._id)
-  const messages = await MessageManager.findAllMessagesInRooms(roomIds)
-  logger.debug({ rooms, messages }, 'looked up messages in the rooms')
-  const threadData = MessageFormatter.groupMessagesByThreads(rooms, messages)
-  res.json(threadData)
-}
-
 const _resolveThread = async (req, res) => {
   const { projectId, threadId } = req.params
   const { user_id: userId } = req.body
@@ -274,29 +254,3 @@ async function _getMessages(clientThreadId, req, res) {
   logger.debug({ projectId, messages }, 'got messages')
   res.status(200).setBody(messages)
 }
-
-async function _duplicateCommentThreads(req, res) {
-  const { projectId } = req.params
-  const { threads } = req.body
-  const result = {}
-  for (const id of threads) {
-    logger.debug({ projectId, thread: id }, 'duplicating thread')
-    try {
-      const { oldRoom, newRoom } = await ThreadManager.duplicateThread(
-        projectId,
-        id
-      )
-      await MessageManager.duplicateRoomToOtherRoom(oldRoom._id, newRoom._id)
-      result[id] = { duplicateId: newRoom.thread_id }
-    } catch (error) {
-      if (error instanceof ThreadManager.MissingThreadError) {
-        // Expected error when the comment has been deleted prior to duplication
-        result[id] = { error: 'not found' }
-      } else {
-        logger.err({ error }, 'error duplicating thread')
-        result[id] = { error: 'unknown' }
-      }
-    }
-  }
-  res.json({ newThreads: result })
-}

+ 0 - 13
services/chat/app/js/Features/Messages/MessageManager.js

@@ -89,16 +89,3 @@ function _ensureIdsAreObjectIds(query) {
   }
   return query
 }
-
-export async function duplicateRoomToOtherRoom(sourceRoomId, targetRoomId) {
-  const sourceMessages = await findAllMessagesInRooms([sourceRoomId])
-  const targetMessages = sourceMessages.map(comment => {
-    return _ensureIdsAreObjectIds({
-      room_id: targetRoomId,
-      content: comment.content,
-      timestamp: comment.timestamp,
-      user_id: comment.user_id,
-    })
-  })
-  await db.messages.insertMany(targetMessages)
-}

+ 0 - 31
services/chat/app/js/Features/Threads/ThreadManager.js

@@ -1,7 +1,5 @@
 import { db, ObjectId } from '../../mongodb.js'
 
-export class MissingThreadError extends Error {}
-
 export const GLOBAL_THREAD = 'GLOBAL'
 
 export async function findOrCreateThread(projectId, threadId) {
@@ -126,32 +124,3 @@ export async function getResolvedThreadIds(projectId) {
     .toArray()
   return resolvedThreadIds
 }
-
-export async function duplicateThread(projectId, threadId) {
-  const room = await db.rooms.findOne({
-    project_id: new ObjectId(projectId),
-    thread_id: new ObjectId(threadId),
-  })
-  if (!room) {
-    throw new MissingThreadError('Trying to duplicate a non-existent thread')
-  }
-  const newRoom = {
-    project_id: room.project_id,
-    thread_id: new ObjectId(),
-  }
-  if (room.resolved) {
-    newRoom.resolved = room.resolved
-  }
-  const confirmation = await db.rooms.insertOne(newRoom)
-  newRoom._id = confirmation.insertedId
-  return { oldRoom: room, newRoom }
-}
-
-export async function findThreadsById(projectId, threadIds) {
-  return await db.rooms
-    .find({
-      project_id: new ObjectId(projectId),
-      thread_id: { $in: threadIds.map(id => new ObjectId(id)) },
-    })
-    .toArray()
-}

+ 0 - 58
services/chat/chat.yaml

@@ -303,64 +303,6 @@ paths:
                 description: chat is alive
       operationId: getStatus
       description: Check that the Chat service is alive
-  '/project/{projectId}/duplicate-comment-threads':
-    parameters:
-      - schema:
-          type: string
-        name: projectId
-        in: path
-        required: true
-    post:
-      summary: Duplicate comment threads
-      operationId: duplicateCommentThreads
-      requestBody:
-        content:
-          application/json:
-            schema:
-              type: object
-              properties:
-                threads:
-                  type: array
-                  items:
-                    type: string
-      responses:
-        '200':
-          content:
-            application/json:
-              schema:
-                type: object
-                properties:
-                  newThreads:
-                    type: object
-                description: Mapping of old thread ids to their duplicated thread ids
-      description: Duplicate a list of comment threads
-  '/project/{projectId}/generate-thread-data':
-    parameters:
-      - schema:
-          type: string
-        name: projectId
-        in: path
-        required: true
-    post:
-      summary: Generate thread data to load into the frontend
-      operationId: generateThreadData
-      requestBody:
-        content:
-          application/json:
-            schema:
-              type: object
-              properties:
-                threads:
-                  type: array
-                  items:
-                    type: string
-      responses:
-        '200':
-          content:
-            application/json:
-              schema:
-                type: object
-      description: Load threads and generate a json blob containing all messages in all the threads
 components:
   schemas:
     Message:

+ 0 - 93
services/chat/test/acceptance/js/CloningCommentThreadsTests.js

@@ -1,93 +0,0 @@
-import { ObjectId } from '../../../app/js/mongodb.js'
-import { expect } from 'chai'
-
-import * as ChatClient from './helpers/ChatClient.js'
-import * as ChatApp from './helpers/ChatApp.js'
-
-const user1Id = new ObjectId().toString()
-const user2Id = new ObjectId().toString()
-
-async function createCommentThread(projectId, threadId = new ObjectId()) {
-  const { response: response1 } = await ChatClient.sendMessage(
-    projectId,
-    threadId.toString(),
-    user1Id,
-    'message 1'
-  )
-  expect(response1.statusCode).to.equal(201)
-  const { response: response2 } = await ChatClient.sendMessage(
-    projectId,
-    threadId,
-    user2Id,
-    'message 2'
-  )
-  expect(response2.statusCode).to.equal(201)
-  return threadId.toString()
-}
-
-describe('Cloning comment threads', async function () {
-  const projectId = new ObjectId().toString()
-
-  before(async function () {
-    await ChatApp.ensureRunning()
-    this.thread1Id = await createCommentThread(projectId)
-    this.thread2Id = await createCommentThread(projectId)
-    this.thread3Id = await createCommentThread(projectId)
-  })
-
-  describe('with non-orphaned threads', async function () {
-    before(async function () {
-      const {
-        response: { body: result, statusCode },
-      } = await ChatClient.duplicateCommentThreads(projectId, [this.thread3Id])
-      this.result = result
-      expect(statusCode).to.equal(200)
-      expect(this.result).to.have.property('newThreads')
-      this.newThreadId = this.result.newThreads[this.thread3Id].duplicateId
-    })
-
-    it('should duplicate threads', function () {
-      expect(this.result.newThreads).to.have.property(this.thread3Id)
-      expect(this.result.newThreads[this.thread3Id]).to.have.property(
-        'duplicateId'
-      )
-      expect(this.result.newThreads[this.thread3Id].duplicateId).to.not.equal(
-        this.thread3Id
-      )
-    })
-
-    it('should not duplicate other threads threads', function () {
-      expect(this.result.newThreads).to.not.have.property(this.thread1Id)
-      expect(this.result.newThreads).to.not.have.property(this.thread2Id)
-    })
-
-    it('should duplicate the messages in the thread', async function () {
-      const {
-        response: { body: threads },
-      } = await ChatClient.getThreads(projectId)
-      function ignoreId(comment) {
-        return {
-          ...comment,
-          id: undefined,
-        }
-      }
-      expect(threads[this.thread3Id].messages.map(ignoreId)).to.deep.equal(
-        threads[this.newThreadId].messages.map(ignoreId)
-      )
-    })
-
-    it('should have two separate unlinked threads', async function () {
-      await ChatClient.sendMessage(
-        projectId,
-        this.newThreadId,
-        user1Id,
-        'third message'
-      )
-      const {
-        response: { body: threads },
-      } = await ChatClient.getThreads(projectId)
-      expect(threads[this.thread3Id].messages.length).to.equal(2)
-      expect(threads[this.newThreadId].messages.length).to.equal(3)
-    })
-  })
-})

+ 0 - 10
services/chat/test/acceptance/js/helpers/ChatClient.js

@@ -144,13 +144,3 @@ export async function destroyProject(projectId) {
     url: `/project/${projectId}`,
   })
 }
-
-export async function duplicateCommentThreads(projectId, threads) {
-  return await asyncRequest({
-    method: 'post',
-    url: `/project/${projectId}/duplicate-comment-threads`,
-    json: {
-      threads,
-    },
-  })
-}

+ 0 - 26
services/web/app/src/Features/Chat/ChatApiHandler.js

@@ -97,28 +97,6 @@ async function getResolvedThreadIds(projectId) {
   return body.resolvedThreadIds
 }
 
-async function duplicateCommentThreads(projectId, threads) {
-  return await fetchJson(
-    chatApiUrl(`/project/${projectId}/duplicate-comment-threads`),
-    {
-      method: 'POST',
-      json: {
-        threads,
-      },
-    }
-  )
-}
-
-async function generateThreadData(projectId, threads) {
-  return await fetchJson(
-    chatApiUrl(`/project/${projectId}/generate-thread-data`),
-    {
-      method: 'POST',
-      json: { threads },
-    }
-  )
-}
-
 function chatApiUrl(path) {
   return new URL(path, settings.apis.chat.internal_url)
 }
@@ -135,8 +113,6 @@ module.exports = {
   editMessage: callbackify(editMessage),
   deleteMessage: callbackify(deleteMessage),
   getResolvedThreadIds: callbackify(getResolvedThreadIds),
-  duplicateCommentThreads: callbackify(duplicateCommentThreads),
-  generateThreadData: callbackify(generateThreadData),
   promises: {
     getThreads,
     destroyProject,
@@ -149,7 +125,5 @@ module.exports = {
     editMessage,
     deleteMessage,
     getResolvedThreadIds,
-    duplicateCommentThreads,
-    generateThreadData,
   },
 }

+ 53 - 3
services/web/app/src/Features/Chat/ChatController.js

@@ -18,8 +18,7 @@ const EditorRealTimeController = require('../Editor/EditorRealTimeController')
 const SessionManager = require('../Authentication/SessionManager')
 const UserInfoManager = require('../User/UserInfoManager')
 const UserInfoController = require('../User/UserInfoController')
-const ChatManager = require('./ChatManager')
-const logger = require('@overleaf/logger')
+const async = require('async')
 
 module.exports = ChatController = {
   sendMessage(req, res, next) {
@@ -69,7 +68,7 @@ module.exports = ChatController = {
         if (err != null) {
           return next(err)
         }
-        return ChatManager.injectUserInfoIntoThreads(
+        return ChatController._injectUserInfoIntoThreads(
           { global: { messages } },
           function (err) {
             if (err != null) {
@@ -81,4 +80,55 @@ module.exports = ChatController = {
       }
     )
   },
+
+  _injectUserInfoIntoThreads(threads, callback) {
+    // There will be a lot of repitition of user_ids, so first build a list
+    // of unique ones to perform db look ups on, then use these to populate the
+    // user fields
+    let message, thread, threadId, userId
+    if (callback == null) {
+      callback = function () {}
+    }
+    const userIds = {}
+    for (threadId in threads) {
+      thread = threads[threadId]
+      if (thread.resolved) {
+        userIds[thread.resolved_by_user_id] = true
+      }
+      for (message of Array.from(thread.messages)) {
+        userIds[message.user_id] = true
+      }
+    }
+
+    const jobs = []
+    const users = {}
+    for (userId in userIds) {
+      const _ = userIds[userId]
+      ;(userId =>
+        jobs.push(cb =>
+          UserInfoManager.getPersonalInfo(userId, function (error, user) {
+            if (error != null) return cb(error)
+            user = UserInfoController.formatPersonalInfo(user)
+            users[userId] = user
+            cb()
+          })
+        ))(userId)
+    }
+
+    return async.series(jobs, function (error) {
+      if (error != null) {
+        return callback(error)
+      }
+      for (threadId in threads) {
+        thread = threads[threadId]
+        if (thread.resolved) {
+          thread.resolved_by_user = users[thread.resolved_by_user_id]
+        }
+        for (message of Array.from(thread.messages)) {
+          message.user = users[message.user_id]
+        }
+      }
+      return callback(null, threads)
+    })
+  },
 }

+ 0 - 61
services/web/app/src/Features/Chat/ChatManager.js

@@ -1,61 +0,0 @@
-const async = require('async')
-const UserInfoManager = require('../User/UserInfoManager')
-const UserInfoController = require('../User/UserInfoController')
-const { promisify } = require('@overleaf/promise-utils')
-
-function injectUserInfoIntoThreads(threads, callback) {
-  // There will be a lot of repitition of user_ids, so first build a list
-  // of unique ones to perform db look ups on, then use these to populate the
-  // user fields
-  let message, thread, threadId, userId
-  if (callback == null) {
-    callback = function () {}
-  }
-  const userIds = {}
-  for (threadId in threads) {
-    thread = threads[threadId]
-    if (thread.resolved) {
-      userIds[thread.resolved_by_user_id] = true
-    }
-    for (message of Array.from(thread.messages)) {
-      userIds[message.user_id] = true
-    }
-  }
-
-  const jobs = []
-  const users = {}
-  for (userId in userIds) {
-    ;(userId =>
-      jobs.push(cb =>
-        UserInfoManager.getPersonalInfo(userId, function (error, user) {
-          if (error != null) return cb(error)
-          user = UserInfoController.formatPersonalInfo(user)
-          users[userId] = user
-          cb()
-        })
-      ))(userId)
-  }
-
-  return async.series(jobs, function (error) {
-    if (error != null) {
-      return callback(error)
-    }
-    for (threadId in threads) {
-      thread = threads[threadId]
-      if (thread.resolved) {
-        thread.resolved_by_user = users[thread.resolved_by_user_id]
-      }
-      for (message of Array.from(thread.messages)) {
-        message.user = users[message.user_id]
-      }
-    }
-    return callback(null, threads)
-  })
-}
-
-module.exports = {
-  injectUserInfoIntoThreads,
-  promises: {
-    injectUserInfoIntoThreads: promisify(injectUserInfoIntoThreads),
-  },
-}

+ 9 - 75
services/web/app/src/Features/History/RestoreManager.js

@@ -9,11 +9,6 @@ const { callbackifyAll } = require('@overleaf/promise-utils')
 const { fetchJson } = require('@overleaf/fetch-utils')
 const ProjectLocator = require('../Project/ProjectLocator')
 const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
-const ChatApiHandler = require('../Chat/ChatApiHandler')
-const DocstoreManager = require('../Docstore/DocstoreManager')
-const logger = require('@overleaf/logger')
-const EditorRealTimeController = require('../Editor/EditorRealTimeController')
-const ChatManager = require('../Chat/ChatManager')
 
 const RestoreManager = {
   async restoreFileFromV2(userId, projectId, version, pathname) {
@@ -93,17 +88,17 @@ const RestoreManager = {
     }
 
     if (file) {
-      logger.debug(
-        { projectId, fileId: file.element._id, type: importInfo.type },
-        'deleting entity before reverting it'
-      )
-      await EditorController.promises.deleteEntity(
+      await DocumentUpdaterHandler.promises.setDocument(
         projectId,
         file.element._id,
-        importInfo.type,
-        'revert',
-        userId
+        userId,
+        importInfo.lines,
+        source
       )
+      return {
+        _id: file.element._id,
+        type: importInfo.type,
+      }
     }
 
     const ranges = await RestoreManager._getRangesFromHistory(
@@ -112,73 +107,12 @@ const RestoreManager = {
       pathname
     )
 
-    const documentCommentIds = new Set(
-      ranges.comments?.map(({ op: { t } }) => t)
-    )
-
-    await DocumentUpdaterHandler.promises.flushProjectToMongo(projectId)
-
-    const docsWithRanges =
-      await DocstoreManager.promises.getAllRanges(projectId)
-
-    const nonOrphanedThreadIds = new Set()
-    for (const { ranges } of docsWithRanges) {
-      for (const comment of ranges.comments ?? []) {
-        nonOrphanedThreadIds.add(comment.op.t)
-      }
-    }
-
-    const commentIdsToDuplicate = Array.from(documentCommentIds).filter(id =>
-      nonOrphanedThreadIds.has(id)
-    )
-
-    const newRanges = { changes: ranges.changes, comments: [] }
-
-    if (commentIdsToDuplicate.length > 0) {
-      const { newThreads: newCommentIds } =
-        await ChatApiHandler.promises.duplicateCommentThreads(
-          projectId,
-          commentIdsToDuplicate
-        )
-
-      logger.debug({ mapping: newCommentIds }, 'replacing comment threads')
-
-      for (const comment of ranges.comments ?? []) {
-        if (Object.prototype.hasOwnProperty.call(newCommentIds, comment.op.t)) {
-          const result = newCommentIds[comment.op.t]
-          if (result.error) {
-            // We couldn't duplicate the thread, so we need to delete it from
-            // the resulting ranges.
-            continue
-          }
-          // We have a new id for this comment thread
-          comment.op.t = result.duplicateId
-          newRanges.comments.push(comment)
-        }
-      }
-    } else {
-      newRanges.comments = ranges.comments
-    }
-
-    const newCommentThreadData =
-      await ChatApiHandler.promises.generateThreadData(
-        projectId,
-        newRanges.comments.map(({ op: { t } }) => t)
-      )
-    await ChatManager.promises.injectUserInfoIntoThreads(newCommentThreadData)
-    logger.debug({ newCommentThreadData }, 'emitting new comment threads')
-    EditorRealTimeController.emitToRoom(
-      projectId,
-      'new-comment-threads',
-      newCommentThreadData
-    )
-
     return await EditorController.promises.addDocWithRanges(
       projectId,
       parentFolderId,
       basename,
       importInfo.lines,
-      newRanges,
+      ranges,
       'revert',
       userId
     )

+ 0 - 21
services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts

@@ -1452,27 +1452,6 @@ function useReviewPanelState(): ReviewPanel.ReviewPanelState {
       [getThread]
     )
   )
-  useSocketListener(
-    socket,
-    'new-comment-threads',
-    useCallback(
-      (threads: ReviewPanelCommentThreadsApi) => {
-        setCommentThreads(prevState => {
-          const newThreads = { ...prevState }
-          for (const threadIdString of Object.keys(threads)) {
-            const threadId = threadIdString as ThreadId
-            const { submitting: _, ...thread } = getThread(threadId)
-            // Replace already loaded messages with the server provided ones
-            thread.messages = threads[threadId].messages.map(formatComment)
-            newThreads[threadId] = thread
-          }
-          return newThreads
-        })
-        handleLayoutChange({ async: true })
-      },
-      [getThread]
-    )
-  )
 
   const openSubView = useRef<typeof subView>('cur_file')
   useEffect(() => {

+ 2 - 1
services/web/frontend/js/features/source-editor/languages/latex/latex-language.ts

@@ -152,7 +152,8 @@ export const LaTeXLanguage = LRLanguage.define({
         'HrefCommand/ShortTextArgument/ShortArg/...': t.link,
         'HrefCommand/UrlArgument/...': t.monospace,
         'CtrlSeq Csname': t.tagName,
-        'DocumentClass/OptionalArgument/ShortOptionalArg/...': t.attributeValue,
+        'DocumentClass/OptionalArgument/ShortOptionalArg/Normal':
+          t.attributeValue,
         'DocumentClass/ShortTextArgument/ShortArg/Normal': t.typeName,
         'ListEnvironment/BeginEnv/OptionalArgument/...': t.monospace,
         Number: t.number,

+ 2 - 28
services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.js

@@ -322,7 +322,7 @@ const read1filename = function (TokeniseResult, k) {
   }
 }
 
-const readOptionalArgumentWithUnderscores = function (TokeniseResult, k) {
+const readOptionalLabel = function (TokeniseResult, k) {
   // read a label my_label:text..
   const Tokens = TokeniseResult.tokens
   const text = TokeniseResult.text
@@ -348,7 +348,6 @@ const readOptionalArgumentWithUnderscores = function (TokeniseResult, k) {
       label = label + str
       if (str.match(/\]/)) {
         // breaking due to ]
-        j++
         break
       }
     } else if (tok[1] === '_') {
@@ -801,31 +800,6 @@ const InterpretTokens = function (TokeniseResult, ErrorReporter) {
       ) {
         // Environments.push({command: "end", name: "user-defined-equation", token: token});
         seenUserDefinedEndEquation = true
-      } else if (seq === 'documentclass') {
-        // try to read any optional params [LABEL].... allowing for
-        // underscores, advance if found
-        let newPos = readOptionalArgumentWithUnderscores(TokeniseResult, i)
-        if (newPos instanceof Error) {
-          TokenErrorFromTo(
-            Tokens[i + 1],
-            Tokens[Math.min(newPos.pos, len - 1)],
-            'invalid documentclass option'
-          )
-          i = newPos.pos
-        } else if (newPos == null) {
-          /* do nothing */
-        } else {
-          i = newPos
-        }
-        // Read parameter {....}, ignore if missing
-        newPos = readDefinition(TokeniseResult, i)
-        if (newPos === null) {
-          // NOTE: We could choose to throw an error here, as the argument is
-          // required. However, that would show errors as you are typing. So
-          // maybe it's better to be lenient.
-        } else {
-          i = newPos
-        }
       } else if (
         seq === 'newcommand' ||
         seq === 'renewcommand' ||
@@ -1064,7 +1038,7 @@ const InterpretTokens = function (TokeniseResult, ErrorReporter) {
       } else if (seq === 'hyperref') {
         // try to read any optional params [LABEL].... allowing for
         // underscores, advance if found
-        let newPos = readOptionalArgumentWithUnderscores(TokeniseResult, i)
+        let newPos = readOptionalLabel(TokeniseResult, i)
         if (newPos instanceof Error) {
           TokenErrorFromTo(
             Tokens[i + 1],

+ 0 - 1
services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar

@@ -484,7 +484,6 @@ ShortOptionalArg {
     ( textBase
     | NonEmptyGroup<ShortOptionalArg>
     | "#" // macro character
-    | "_" // underscore is used in some parameter names
     )*
 }
 

+ 0 - 8
services/web/frontend/stylesheets/app/editor/left-menu.less

@@ -191,18 +191,11 @@
   transition: opacity 0.5s;
 }
 
-// We want to be able to stack modals on top of the left-side menu. So we make
-// it a little lower than the normal modal backdrops. We don't want to go too
-// low, to avoid conflicting with dropdowns etc.
-@left-menu-z-index-backdrop: @zindex-modal-background - 5;
-@left-menu-z-index: @zindex-modal-background - 2;
-
 // Make the Bootstrap Modal behavior as a left sidebar
 #left-menu-modal {
   opacity: 1;
   overflow-y: hidden;
   padding-left: 0 !important; // bootstrap modal may randomly give padding-left when zooming in / out in chrome
-  z-index: @left-menu-z-index;
   .modal-dialog {
     height: 100%;
     margin: 0;
@@ -226,6 +219,5 @@
 // Don't disable backdrop that allows closing the Modal when clicking outside of it,
 // But match its background color with the original mask background color.
 .left-menu-modal-backdrop {
-  z-index: @left-menu-z-index-backdrop;
   background-color: transparent;
 }

+ 0 - 17
services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts

@@ -494,23 +494,6 @@ describe('LatexLinter', function () {
     assert.equal(errors[0].text, 'unclosed group {')
   })
 
-  it('should accept documentclass with no options', function () {
-    const { errors } = Parse('\\documentclass{article}')
-    assert.equal(errors.length, 0)
-  })
-
-  it('should accept documentclass with options', function () {
-    const { errors } = Parse('\\documentclass[a4paper]{article}')
-    assert.equal(errors.length, 0)
-  })
-
-  it('should accept documentclass with underscore in options', function () {
-    const { errors } = Parse(
-      '\\documentclass[my_custom_document_class_option]{my-custom-class}'
-    )
-    assert.equal(errors.length, 0)
-  })
-
   // %novalidate
   // %begin novalidate
   // %end novalidate

+ 105 - 3
services/web/test/unit/src/Chat/ChatControllerTests.js

@@ -19,13 +19,13 @@ const modulePath = path.join(
   __dirname,
   '../../../../app/src/Features/Chat/ChatController'
 )
+const { expect } = require('chai')
 
 describe('ChatController', function () {
   beforeEach(function () {
     this.user_id = 'mock-user-id'
     this.settings = {}
     this.ChatApiHandler = {}
-    this.ChatManager = {}
     this.EditorRealTimeController = { emitToRoom: sinon.stub() }
     this.SessionManager = {
       getLoggedInUserId: sinon.stub().returns(this.user_id),
@@ -34,7 +34,6 @@ describe('ChatController', function () {
       requires: {
         '@overleaf/settings': this.settings,
         './ChatApiHandler': this.ChatApiHandler,
-        './ChatManager': this.ChatManager,
         '../Editor/EditorRealTimeController': this.EditorRealTimeController,
         '../Authentication/SessionManager': this.SessionManager,
         '../User/UserInfoManager': (this.UserInfoManager = {}),
@@ -107,7 +106,7 @@ describe('ChatController', function () {
         limit: (this.limit = '30'),
         before: (this.before = '12345'),
       }
-      this.ChatManager.injectUserInfoIntoThreads = sinon.stub().yields()
+      this.ChatController._injectUserInfoIntoThreads = sinon.stub().yields()
       this.ChatApiHandler.getGlobalMessages = sinon
         .stub()
         .yields(null, (this.messages = ['mock', 'messages']))
@@ -124,4 +123,107 @@ describe('ChatController', function () {
       return this.res.json.calledWith(this.messages).should.equal(true)
     })
   })
+
+  describe('_injectUserInfoIntoThreads', function () {
+    beforeEach(function () {
+      this.users = {
+        user_id_1: {
+          mock: 'user_1',
+        },
+        user_id_2: {
+          mock: 'user_2',
+        },
+      }
+      this.UserInfoManager.getPersonalInfo = (userId, callback) => {
+        return callback(null, this.users[userId])
+      }
+      sinon.spy(this.UserInfoManager, 'getPersonalInfo')
+      return (this.UserInfoController.formatPersonalInfo = user => ({
+        formatted: user.mock,
+      }))
+    })
+
+    it('should inject a user object into messaged and resolved data', function (done) {
+      return this.ChatController._injectUserInfoIntoThreads(
+        {
+          thread1: {
+            resolved: true,
+            resolved_by_user_id: 'user_id_1',
+            messages: [
+              {
+                user_id: 'user_id_1',
+                content: 'foo',
+              },
+              {
+                user_id: 'user_id_2',
+                content: 'bar',
+              },
+            ],
+          },
+          thread2: {
+            messages: [
+              {
+                user_id: 'user_id_1',
+                content: 'baz',
+              },
+            ],
+          },
+        },
+        (error, threads) => {
+          expect(threads).to.deep.equal({
+            thread1: {
+              resolved: true,
+              resolved_by_user_id: 'user_id_1',
+              resolved_by_user: { formatted: 'user_1' },
+              messages: [
+                {
+                  user_id: 'user_id_1',
+                  user: { formatted: 'user_1' },
+                  content: 'foo',
+                },
+                {
+                  user_id: 'user_id_2',
+                  user: { formatted: 'user_2' },
+                  content: 'bar',
+                },
+              ],
+            },
+            thread2: {
+              messages: [
+                {
+                  user_id: 'user_id_1',
+                  user: { formatted: 'user_1' },
+                  content: 'baz',
+                },
+              ],
+            },
+          })
+          return done()
+        }
+      )
+    })
+
+    it('should only need to look up each user once', function (done) {
+      return this.ChatController._injectUserInfoIntoThreads(
+        [
+          {
+            messages: [
+              {
+                user_id: 'user_id_1',
+                content: 'foo',
+              },
+              {
+                user_id: 'user_id_1',
+                content: 'bar',
+              },
+            ],
+          },
+        ],
+        (error, threads) => {
+          this.UserInfoManager.getPersonalInfo.calledOnce.should.equal(true)
+          return done()
+        }
+      )
+    })
+  })
 })

+ 0 - 135
services/web/test/unit/src/Chat/ChatManagerTests.js

@@ -1,135 +0,0 @@
-const SandboxedModule = require('sandboxed-module')
-const path = require('path')
-const sinon = require('sinon')
-const modulePath = path.join(
-  __dirname,
-  '../../../../app/src/Features/Chat/ChatManager'
-)
-const { expect } = require('chai')
-
-describe('ChatManager', function () {
-  beforeEach(function () {
-    this.user_id = 'mock-user-id'
-    this.ChatManager = SandboxedModule.require(modulePath, {
-      requires: {
-        '../User/UserInfoManager': (this.UserInfoManager = {}),
-        '../User/UserInfoController': (this.UserInfoController = {}),
-      },
-    })
-    this.req = {
-      params: {
-        project_id: this.project_id,
-      },
-    }
-    this.res = {
-      json: sinon.stub(),
-      send: sinon.stub(),
-      sendStatus: sinon.stub(),
-    }
-  })
-
-  describe('injectUserInfoIntoThreads', function () {
-    beforeEach(function () {
-      this.users = {
-        user_id_1: {
-          mock: 'user_1',
-        },
-        user_id_2: {
-          mock: 'user_2',
-        },
-      }
-      this.UserInfoManager.getPersonalInfo = (userId, callback) => {
-        return callback(null, this.users[userId])
-      }
-      sinon.spy(this.UserInfoManager, 'getPersonalInfo')
-      return (this.UserInfoController.formatPersonalInfo = user => ({
-        formatted: user.mock,
-      }))
-    })
-
-    it('should inject a user object into messaged and resolved data', function (done) {
-      return this.ChatManager.injectUserInfoIntoThreads(
-        {
-          thread1: {
-            resolved: true,
-            resolved_by_user_id: 'user_id_1',
-            messages: [
-              {
-                user_id: 'user_id_1',
-                content: 'foo',
-              },
-              {
-                user_id: 'user_id_2',
-                content: 'bar',
-              },
-            ],
-          },
-          thread2: {
-            messages: [
-              {
-                user_id: 'user_id_1',
-                content: 'baz',
-              },
-            ],
-          },
-        },
-        (error, threads) => {
-          expect(error).to.be.null
-          expect(threads).to.deep.equal({
-            thread1: {
-              resolved: true,
-              resolved_by_user_id: 'user_id_1',
-              resolved_by_user: { formatted: 'user_1' },
-              messages: [
-                {
-                  user_id: 'user_id_1',
-                  user: { formatted: 'user_1' },
-                  content: 'foo',
-                },
-                {
-                  user_id: 'user_id_2',
-                  user: { formatted: 'user_2' },
-                  content: 'bar',
-                },
-              ],
-            },
-            thread2: {
-              messages: [
-                {
-                  user_id: 'user_id_1',
-                  user: { formatted: 'user_1' },
-                  content: 'baz',
-                },
-              ],
-            },
-          })
-          return done()
-        }
-      )
-    })
-
-    it('should only need to look up each user once', function (done) {
-      return this.ChatManager.injectUserInfoIntoThreads(
-        [
-          {
-            messages: [
-              {
-                user_id: 'user_id_1',
-                content: 'foo',
-              },
-              {
-                user_id: 'user_id_1',
-                content: 'bar',
-              },
-            ],
-          },
-        ],
-        (error, threads) => {
-          expect(error).to.be.null
-          this.UserInfoManager.getPersonalInfo.calledOnce.should.equal(true)
-          return done()
-        }
-      )
-    })
-  })
-})

+ 11 - 60
services/web/test/unit/src/History/RestoreManagerTests.js

@@ -24,16 +24,7 @@ describe('RestoreManager', function () {
         }),
         '../Project/ProjectLocator': (this.ProjectLocator = { promises: {} }),
         '../DocumentUpdater/DocumentUpdaterHandler':
-          (this.DocumentUpdaterHandler = {
-            promises: { flushProjectToMongo: sinon.stub().resolves() },
-          }),
-        '../Docstore/DocstoreManager': (this.DocstoreManager = {
-          promises: {},
-        }),
-        '../Chat/ChatApiHandler': (this.ChatApiHandler = { promises: {} }),
-        '../Chat/ChatManager': (this.ChatManager = { promises: {} }),
-        '../Editor/EditorRealTimeController': (this.EditorRealTimeController =
-          {}),
+          (this.DocumentUpdaterHandler = { promises: {} }),
       },
     })
     this.user_id = 'mock-user-id'
@@ -240,25 +231,10 @@ describe('RestoreManager', function () {
         this.DocumentUpdaterHandler.promises.setDocument = sinon
           .stub()
           .resolves()
-        this.EditorController.promises.deleteEntity = sinon.stub().resolves()
-        this.RestoreManager.promises._getRangesFromHistory = sinon
-          .stub()
-          .resolves({ changes: [], comments: [] })
-        this.DocstoreManager.promises.getAllRanges = sinon.stub().resolves([])
-        this.ChatApiHandler.promises.generateThreadData = sinon
-          .stub()
-          .resolves({})
-        this.ChatManager.promises.injectUserInfoIntoThreads = sinon
-          .stub()
-          .resolves()
-        this.EditorRealTimeController.emitToRoom = sinon.stub()
-        this.EditorController.promises.addDocWithRanges = sinon
-          .stub()
-          .resolves()
       })
 
-      it('should delete the existing document', async function () {
-        await this.RestoreManager.promises.revertFile(
+      it('should call setDocument in document updater and revert file', async function () {
+        const revertRes = await this.RestoreManager.promises.revertFile(
           this.user_id,
           this.project_id,
           this.version,
@@ -266,14 +242,15 @@ describe('RestoreManager', function () {
         )
 
         expect(
-          this.EditorController.promises.deleteEntity
+          this.DocumentUpdaterHandler.promises.setDocument
         ).to.have.been.calledWith(
           this.project_id,
           'mock-file-id',
-          'doc',
-          'revert',
-          this.user_id
+          this.user_id,
+          ['foo', 'bar', 'baz'],
+          'file-revert'
         )
+        expect(revertRes).to.deep.equal({ _id: 'mock-file-id', type: 'doc' })
       })
     })
 
@@ -320,34 +297,7 @@ describe('RestoreManager', function () {
     describe("when reverting a file that doesn't current exist", function () {
       beforeEach(async function () {
         this.pathname = 'foo.tex'
-        this.comments = [
-          (this.comment = { op: { t: 'comment-1', p: 0, c: 'foo' } }),
-        ]
-        this.remappedComments = [{ op: { t: 'comment-2', p: 0, c: 'foo' } }]
         this.ProjectLocator.promises.findElementByPath = sinon.stub().rejects()
-        this.DocstoreManager.promises.getAllRanges = sinon.stub().resolves([
-          {
-            ranges: {
-              comments: [this.comment],
-            },
-          },
-        ])
-        this.ChatApiHandler.promises.duplicateCommentThreads = sinon
-          .stub()
-          .resolves({
-            newThreads: {
-              'comment-1': {
-                duplicateId: 'comment-2',
-              },
-            },
-          })
-        this.ChatApiHandler.promises.generateThreadData = sinon
-          .stub()
-          .resolves({})
-        this.ChatManager.promises.injectUserInfoIntoThreads = sinon
-          .stub()
-          .resolves()
-        this.EditorRealTimeController.emitToRoom = sinon.stub()
         this.tracked_changes = [
           {
             op: { pos: 4, i: 'bar' },
@@ -358,12 +308,13 @@ describe('RestoreManager', function () {
             metadata: { ts: '2024-01-01T00:00:00.000Z', user_id: 'user-2' },
           },
         ]
+        this.comments = [{ op: { t: 'comment-1', p: 0, c: 'foo' } }]
         this.FileSystemImportManager.promises.importFile = sinon
           .stub()
           .resolves({ type: 'doc', lines: ['foo', 'bar', 'baz'] })
         this.RestoreManager.promises._getRangesFromHistory = sinon
           .stub()
-          .resolves({ changes: this.tracked_changes, comments: this.comments })
+          .resolves({ changes: this.tracked_changes, comment: this.comments })
         this.EditorController.promises.addDocWithRanges = sinon
           .stub()
           .resolves(
@@ -385,7 +336,7 @@ describe('RestoreManager', function () {
           this.folder_id,
           'foo.tex',
           ['foo', 'bar', 'baz'],
-          { changes: this.tracked_changes, comments: this.remappedComments }
+          { changes: this.tracked_changes, comment: this.comments }
         )
       })