Просмотр исходного кода

Merge pull request #6151 from overleaf/jpa-jel-ta-spelling-client-cache

[misc] filter out saved words from users dict client side

GitOrigin-RevId: 01b496c60d25954c8e65a71c06fd90a6c428a698
Jakob Ackermann 4 лет назад
Родитель
Сommit
17eb841b31

+ 8 - 1
services/spelling/app/js/LearnedWordsManager.js

@@ -54,6 +54,14 @@ const LearnedWordsManager = {
     metrics.inc('mongoCache', 0.1, { status: 'miss' })
     logger.info({ userToken }, 'mongoCache miss')
 
+    LearnedWordsManager.getLearnedWordsNoCache(userToken, (err, words) => {
+      if (err) return callback(err)
+      mongoCache.set(userToken, words)
+      callback(null, words)
+    })
+  },
+
+  getLearnedWordsNoCache(userToken, callback) {
     db.spellingPreferences.findOne(
       { token: userToken },
       function (error, preferences) {
@@ -68,7 +76,6 @@ const LearnedWordsManager = {
             (value, index, self) => self.indexOf(value) === index
           )
         }
-        mongoCache.set(userToken, words)
         callback(null, words)
       }
     )

+ 2 - 2
services/spelling/app/js/SpellingAPIManager.js

@@ -51,7 +51,7 @@ const SpellingAPIManager = {
   },
 
   getDic(token, callback) {
-    return LearnedWordsManager.getLearnedWords(token, callback)
+    return LearnedWordsManager.getLearnedWordsNoCache(token, callback)
   },
 }
 
@@ -67,7 +67,7 @@ const promises = {
 
     const misspellings = await ASpell.promises.checkWords(lang, wordSlice)
 
-    if (token) {
+    if (token && !request.skipLearnedWords) {
       const learnedWords = await LearnedWordsManager.promises.getLearnedWords(
         token
       )

+ 9 - 0
services/web/app/src/Features/Project/ProjectController.js

@@ -40,6 +40,7 @@ const Modules = require('../../infrastructure/Modules')
 const SplitTestV2Handler = require('../SplitTests/SplitTestV2Handler')
 const { getNewLogsUIVariantForUser } = require('../Helpers/NewLogsUI')
 const FeaturesUpdater = require('../Subscription/FeaturesUpdater')
+const SpellingHandler = require('../Spelling/SpellingHandler')
 
 const _ssoAvailable = (affiliation, session, linkedInstitutionIds) => {
   if (!affiliation.institution) return false
@@ -691,6 +692,12 @@ const ProjectController = {
             )
           }
         },
+        learnedWords(cb) {
+          if (!userId) {
+            return cb(null, [])
+          }
+          SpellingHandler.getUserDictionaryWithRetries(userId, cb)
+        },
         subscription(cb) {
           if (userId == null) {
             return cb()
@@ -776,6 +783,7 @@ const ProjectController = {
         {
           project,
           user,
+          learnedWords,
           subscription,
           isTokenMember,
           brandVariation,
@@ -927,6 +935,7 @@ const ProjectController = {
                 isTokenMember
               ),
               languages: Settings.languages,
+              learnedWords,
               editorThemes: THEME_LIST,
               maxDocLength: Settings.max_doc_length,
               useV2History:

+ 29 - 0
services/web/app/src/Features/Spelling/SpellingHandler.js

@@ -1,10 +1,39 @@
 const request = require('request')
+const requestRetry = require('requestretry')
 const Settings = require('@overleaf/settings')
 const OError = require('@overleaf/o-error')
 
 const TIMEOUT = 10 * 1000
 
 module.exports = {
+  getUserDictionaryWithRetries(userId, callback) {
+    const options = {
+      url: `${Settings.apis.spelling.url}/user/${userId}`,
+      timeout: 3 * 1000,
+      json: true,
+      retryDelay: 1,
+      maxAttempts: 3,
+    }
+    requestRetry(options, (error, response, body) => {
+      if (error) {
+        return callback(
+          OError.tag(error, 'error getting user dictionary', { error, userId })
+        )
+      }
+
+      if (response.statusCode !== 200) {
+        return callback(
+          new OError(
+            'Non-success code from spelling API when getting user dictionary',
+            { userId, statusCode: response.statusCode }
+          )
+        )
+      }
+
+      callback(null, body)
+    })
+  },
+
   getUserDictionary(userId, callback) {
     const url = `${Settings.apis.spelling.url}/user/${userId}`
     request.get({ url: url, timeout: TIMEOUT }, (error, response) => {

+ 1 - 0
services/web/app/views/project/editor.pug

@@ -123,6 +123,7 @@ block append meta
 	meta(name="ol-project_id" content=project_id)
 	meta(name="ol-userSettings" data-type="json" content=userSettings)
 	meta(name="ol-user" data-type="json" content=user)
+	meta(name="ol-learnedWords" data-type="json" content=learnedWords)
 	meta(name="ol-anonymous" data-type="boolean" content=anonymous)
 	meta(name="ol-brandVariation" data-type="json" content=brandVariation)
 	meta(name="ol-anonymousAccessToken" content=anonymousAccessToken)

+ 10 - 3
services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.js

@@ -1,3 +1,5 @@
+import getMeta from '../../../../../utils/meta'
+
 // eslint-disable-next-line prefer-regex-literals
 const BLACKLISTED_COMMAND_REGEX = new RegExp(
   `\
@@ -58,6 +60,8 @@ class SpellCheckManager {
 
     this.selectedHighlightContents = null
 
+    this.learnedWords = new Set(getMeta('ol-learnedWords'))
+
     $(document).on('click', e => {
       // There is a bug (?) in Safari when ctrl-clicking an element, and the
       // the contextmenu event is preventDefault-ed. In this case, the
@@ -191,6 +195,7 @@ class SpellCheckManager {
     this.adapter.highlightedWordManager.removeWord(highlight.word)
     const language = this.$scope.spellCheckLanguage
     this.cache.put(`${language}:${highlight.word}`, true)
+    this.learnedWords.add(highlight.word)
   }
 
   markLinesAsUpdated(change) {
@@ -291,7 +296,7 @@ class SpellCheckManager {
     } else {
       this.inProgressRequest = this.apiRequest(
         '/check',
-        { language, words },
+        { language, words, skipLearnedWords: true },
         (error, result) => {
           delete this.inProgressRequest
           if (error != null || result == null || result.misspellings == null) {
@@ -372,8 +377,10 @@ class SpellCheckManager {
         if (word[word.length - 1] === "'") {
           word = word.slice(0, -1)
         }
-        positions.push({ row: rowIdx, column: result.index })
-        words.push(word)
+        if (!this.learnedWords.has(word)) {
+          positions.push({ row: rowIdx, column: result.index })
+          words.push(word)
+        }
       }
     }
     return { words, positions }

+ 4 - 0
services/web/test/karma/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.js

@@ -223,6 +223,7 @@ export default describe('SpellCheckManager', function () {
         .expect('POST', '/spelling/check', {
           language: this.scope.spellCheckLanguage,
           words: ['Lorem', 'ipsum', 'dolor'],
+          skipLearnedWords: true,
           token: window.user.id,
           _csrf: window.csrfToken,
         })
@@ -244,6 +245,7 @@ export default describe('SpellCheckManager', function () {
         .expect('POST', '/spelling/check', {
           language: this.scope.spellCheckLanguage,
           words: ['Lorem', 'ipsum', 'dolor'],
+          skipLearnedWords: true,
           token: window.user.id,
           _csrf: window.csrfToken,
         })
@@ -267,6 +269,7 @@ export default describe('SpellCheckManager', function () {
         .expect('POST', '/spelling/check', {
           language: this.scope.spellCheckLanguage,
           words: ['Lorem', 'ipsum', 'dolor'],
+          skipLearnedWords: true,
           token: window.user.id,
           _csrf: window.csrfToken,
         })
@@ -287,6 +290,7 @@ export default describe('SpellCheckManager', function () {
         .expect('POST', '/spelling/check', {
           language: this.scope.spellCheckLanguage,
           words: ['sit', 'amet'],
+          skipLearnedWords: true,
           token: window.user.id,
           _csrf: window.csrfToken,
         })

+ 3 - 0
services/web/test/unit/src/Project/ProjectControllerTests.js

@@ -193,6 +193,9 @@ describe('ProjectController', function () {
           hooks: { fire: sinon.stub().yields(null, []) },
         },
         '../Helpers/NewLogsUI': this.NewLogsUIHelper,
+        '../Spelling/SpellingHandler': {
+          getUserDictionaryWithRetries: sinon.stub().yields(null, []),
+        },
       },
     })