Compare commits

..
232 Commits
Author SHA1 Message Date
David Rotermund 07687f132b Readme updated, usage added, bug hunt done 2026-08-23 12:41:49 +00:00
David Rotermund 6d1703d79f Remodeled + BringYourOwn Api Key 2026-08-22 23:24:42 +00:00
yu-i-i 967abed4f9 LLM: provider-agnostic code (not finished); styles have to be in sylesheets (not finished) 2026-08-03 02:51:40 +02:00
yu-i-i e7ddd0ea61 LLM: additions by @alelotti96
- Made the three hardcoded qwen3-32b fallbacks env-configurable.
 - AES-256-GCM encryption of per-user API keys.
 - "Ask AI" on a selection follows the model picked in the chat dropdown.
 - Per-feature enable/disable toggles (chat / completion / review) enforced backend-side, not just hidden in the UI
 - All prompts editable from the admin page: the chat system prompt, the Ask AI behaviour prompt, the compile-error help prompt, the review system prompt, and the ten Ask AI action templates (paraphrase, academic, concise, summarize, ...), each using a {{selection}} placeholder. An empty field falls back to the shipped default.
 - A document compliance review: it sends the whole project to the model and checks it against admin-defined rubrics (thesis or internship guidelines), returning a per-requirement report.
2026-08-03 02:51:36 +02:00
David Rotermund f628081f90 LLM: Initial files 2026-08-03 02:49:45 +02:00
yu-i-i cf70bc751e Reference picker: fix show existing key as unexistent in Insert mode 2026-07-23 17:42:51 +02:00
yu-i-i 2e275a7489 Update README.md for 6.2.0 2026-07-17 18:07:23 +02:00
yu-i-i 27728c9283 Add syscalls to seccomp profile, fixes #194 2026-07-17 18:07:22 +02:00
yu-i-i 6c10182c60 Admin tools: improve Active Projects UI
- do not show the project owner, email and connections number columns
 - add mailto: all active users of a project when clicking the project title
 - fix error handling
2026-07-17 18:07:22 +02:00
yu-i-i c7f50c7de6 Fix: hide Git Bridge, GitHub Sync and Zotero UI elements when the corresponding feature is disabled 2026-07-17 18:07:22 +02:00
yu-i-i a547af1de3 Reference picker: improve usability
- show tags for nonexistent references
- click a badge to find the corresponding reference
2026-07-17 18:07:22 +02:00
yu-i-i febe214af6 Add variables to enable experimental features: pandoc conversion and editor tabs 2026-07-17 18:07:22 +02:00
yu-i-i aaea099e48 Registration page: the initial commit 2026-07-17 18:07:19 +02:00
yu-i-i fdeff3c174 Git bridge, Template gallery: remove integration with the old editor code 2026-07-17 18:03:42 +02:00
yu-i-i 652acede70 Admin tools: handle bad SMTP configuration error on account creation 2026-07-17 18:03:42 +02:00
yu-i-i c9eff55f05 Admin tools: remove themed pages env var; style admin header
- themed pages now is enabled by default upstream
  - make the header background red on admin manage urers/projects pages
  - fix minor bug
2026-07-17 18:03:42 +02:00
yu-i-i 9554d5b910 Github Sync: enhance token handling and post-sync indexing
- support file-based token cipher
  - reindex references after sync
  - fix typo and clean up code
2026-07-17 18:03:41 +02:00
yu-i-i 441d275948 Zotero: improve integration and reference import
- use OAuth instead of tokens
  - add an integration card
  - improve error handling
  - add BibLaTeX format support
  - fix indexing of linked .bib files on creation
2026-07-17 18:03:41 +02:00
yu-i-i c2f191b43c Linked URL: adapt for yarn; bump node to 24.14.1 2026-07-17 18:03:41 +02:00
David Rotermund 7f1c095039 Fix admin socket metadata handling and footer HTML rendering in admin-tools 2026-07-17 18:03:41 +02:00
Pierpaolo Vittorini 39971e0c30 Admin tools, Authentication: Fix: set analyticsId when creating a new user 2026-07-17 18:03:40 +02:00
yu-i-i 6478184ead Zotero: allow collaborators to refresh files imported from Zotero; show importer's name in the header 2026-07-17 18:03:40 +02:00
yu-i-i 35b6bdc582 Admin tools: move styles to modules directory; fix form for dark theme 2026-07-17 18:03:40 +02:00
yu-i-i fd583d5bf0 Github Sync: initial commit 2026-07-17 18:03:40 +02:00
yu-i-i 25765f6a7d Template Gallery: fix for 6.2.0 2026-07-17 18:03:39 +02:00
yu-i-i c133c1fdb8 Git integration: use named export for settings page widget
remove stray console.log
2026-07-17 18:03:39 +02:00
Synésio Neto 26683c3ba3 Fix a greyed out create file button after the Zotero Integration was merged. 2026-07-17 18:03:39 +02:00
yu-i-i 57a060c793 Sandboxed compiles: cwd argument was added to CommandRunner
see upstream commit ce6f9b8
2026-07-17 18:03:39 +02:00
yu-i-i cae6088557 Patches handled by yarn 2026-07-17 18:03:39 +02:00
yu-i-i bb025b29b2 Revert "[clsi] remove locking from docker actions (#32373)"
This reverts commit d66a856baa.
2026-07-17 18:03:39 +02:00
yu-i-i 6269f8e4ba Reference picker: normal search instead of fuzzy; overleafying modal 2026-07-17 18:03:39 +02:00
David Rotermund a83e54034f Reference picker 2026-07-17 18:03:36 +02:00
yu-i-i 5b4db4121b Authentication: fix ldap searchAttributes for passport-ldapauth 3.0.1 2026-07-17 18:01:58 +02:00
David Rotermund 463e4d5034 Zotero Integration (#164) 2026-07-17 18:01:56 +02:00
David Rotermund 629217a128 Logo Tools (#158)
* Logo Tools

* Moved to /tools/logo; Checks dependencies first and stops if they are not there; Added information about 3_remove_branding_from_projectpage.sh in README ; reordered the commands of 1_convert.sh to avoid the logo-horizontal.png problem
2026-07-17 18:00:24 +02:00
yu-i-i 621c554523 Git Bridge: fix incorrect token creation date display 2026-07-17 18:00:24 +02:00
David Rotermund 7b107886b4 Admin tools: introduce Activ Projects tab on the Manage Site page
- code moved to modules/admin-tools (yu-i-i)
2026-07-17 18:00:24 +02:00
yu-i-i 0f334b7932 Git Bridge: route PAT validation endpoint to correct router 2026-07-17 18:00:24 +02:00
yu-i-i d18da4336d Sandboxed compiles: avoid destroying containers of recently accessed projects
see upstream commit a9c4138
  here is an appropriate DockerRunner.mjs patch
2026-07-17 18:00:24 +02:00
yu-i-i dbdd5d4640 Admin tools: themed dashboard 2026-07-17 18:00:24 +02:00
Tao Chen 42f5226e09 Git Bridge: support git-bridge configuration via env in nginx
- generate overleaf.conf dynamically
- support GIT_BRIDGE_HOST and GIT_BRIDGE_PORT
- configure client_max_body_size for /git using GIT_BRIDGE_REPOSTORE_MAX_FILE_SIZE
2026-07-17 18:00:24 +02:00
yu-i-i 24d6518898 Linked URL: bump node to 24.13.0 2026-07-17 18:00:23 +02:00
yu-i-i f7b0755011 Git Bridge: integrations-panel code was moved 2026-07-17 18:00:23 +02:00
yu-i-i cee91bec80 Authentication: fix uid ldap login after email parsing was added to rate limiter 2026-07-17 18:00:23 +02:00
yu-i-i 9c409be818 Sandboxed compiles: convert DockerRunner to ESM 2026-07-17 18:00:23 +02:00
yu-i-i fdbeeeba5d Update README.md 2026-07-17 18:00:23 +02:00
yu-i-i 7317bd3e0c Sandboxed compiles: add scripts to add/strip repo prefix from texlive images 2026-07-17 18:00:23 +02:00
yu-i-i 9e0bc1e5a6 Add env variable to enable restoring project/file from history snapshot 2026-07-17 18:00:23 +02:00
yu-i-i a9b79d4a34 Admin tools: minor improvements
- enable Projects button in navbar for admin projects page
- enable copy of user ID in user info modal
- add null check in UserListController (thanks @moschlar)
2026-07-17 18:00:22 +02:00
yu-i-i 57fd623f64 Git Bridge: Add git integration 2026-07-17 18:00:22 +02:00
yu-i-i 7f1db9fdaf Sandboxed compiles: set imageName to default if undefined in project 2026-07-17 18:00:22 +02:00
yu-i-i 950cbe7ff1 Authentication: update passport-ldapauth to 3.0.1; fix minor issues 2026-07-17 18:00:22 +02:00
yu-i-i 5b6922d4bc Linked URL: prevent SSRF via DNS rebinding; minor fixes 2026-07-17 18:00:22 +02:00
yu-i-i 363e8b8f61 Template gallery: add template management authorization middleware
- Middleware to enforce template access rules
- Introduce template manager role
- Fix minor issues
2026-07-17 18:00:22 +02:00
yu-i-i 5142417020 Admin tools: optimize user/project list performance
Add frontend pagination
Add search debouncing
Render modals conditionally
Remove unnecessary sorting
2026-07-17 18:00:19 +02:00
yu-i-i 85859805e0 Update README.md 2026-07-17 17:59:21 +02:00
yu-i-i 1dab731bd8 Sandboxed Compiles: pass imageName in Copy project 2026-07-17 17:59:21 +02:00
yu-i-i 5c56d03656 Admin tools: fix minor bugs 2026-07-17 17:59:20 +02:00
yu-i-i 845bccfec5 Sandboxed compiles: add arm64 support to clsi seccomp profile 2026-07-17 17:59:20 +02:00
yu-i-i 412a1a539f Sandboxed Compiles: support configurable texlive image root via env var 2026-07-17 17:59:20 +02:00
yu-i-i e3175676c1 Admin Tools: Fix EXTERNAL_AUTH is 'none' 2026-07-17 17:59:20 +02:00
yu-i-i 76dfd21949 Admin Tools: Fix sorting 2026-07-17 17:59:20 +02:00
yu-i-i c03df586d6 Template Gallery: migration to 6.1.0
enable menu for editor redisign
move frontend code to modules
2026-07-17 17:59:20 +02:00
yu-i-i afaab641b1 Symbol Palette: fix for 6.1.0 2026-07-17 17:59:20 +02:00
yu-i-i 51454a08d7 Track changes: Fix import after upstream converted several files to ESM 2026-07-17 17:59:20 +02:00
yu-i-i 550272502e Authentication: Fix import after upstream converted some files to ESM 2026-07-17 17:59:19 +02:00
yu-i-i 4a87f36ef3 Admin Tools: Fix for 6.1.0 migraton 2026-07-17 17:59:19 +02:00
Guilhem Poy a25f82a606 Add support for EMAIL_SMTP_PASS_FILE
Small feature to provide the email password from a file rather than directly from the env variable OVERLEAF_EMAIL_SMTP_PASS. Useful when used in conjunction with Docker Compose secrets.
2026-07-17 17:59:19 +02:00
yu-i-i 5f6d248502 Admin Tools: Fix user name
fix user name is not updated

fix first_name/last_name is undefined
2026-07-17 17:59:19 +02:00
yu-i-i c6aab19393 Admin Tools: Manage users and Manage projects pages 2026-07-17 17:59:12 +02:00
yu-i-i b760ab7be1 Hotfix 5.5.6-ext-3.3 2026-07-17 17:56:50 +02:00
yu-i-i 42e7969f42 Linked URL: add sanitize and normalize 2026-07-17 17:56:49 +02:00
yu-i-i 7f1c0fdd4d README for extended CE 2026-07-17 17:56:49 +02:00
yu-i-i 18be9f31d2 Add service for Linked URL feature 2026-07-17 17:56:49 +02:00
yu-i-i ed65373e74 Sandboxed compiles: adjust clsi-profile 2026-07-17 17:56:49 +02:00
yu-i-i 15f99aa8c5 Track changes: Fix import after upstream converted DocumentUpdaterHandler and CollaboratorsGetter to ESM 2026-07-17 17:56:49 +02:00
yu-i-i 59984b59c0 Authentication: Fix import after upstream converted AuthenticationController to ESM 2026-07-17 17:56:49 +02:00
yu-i-i eeb61a3a2f Template gallery: Fix import after upstream converted AuthenticationController, ProjectGetter, ProjectLocator and DocumentUpdaterHandler` to ESM 2026-07-17 17:56:49 +02:00
yu-i-i 68bf77cbad Template gallery: Fix import after upstream converted CompileManager, ClsiManager and RateLimiterMiddleware to ESM 2026-07-17 17:56:48 +02:00
yu-i-i 9a511efdd8 Template gallery: Fix OLModal usage (see upstream commit 19b3834) 2026-07-17 17:56:48 +02:00
yu-i-i 41b78a87e0 Authentication: Fix import after upstream converted PermissionsManager, RateLimiterMiddleware, UserCreator and SAMLIdentityManager to ESM 2026-07-17 17:56:48 +02:00
yu-i-i 502eedffbd Track changes: Fix import after upstream converted ChatManager, UserInfoManager and UserInfoController to ESM 2026-07-17 17:56:48 +02:00
Rudy ERCEK a06480439b Add MAX_UPLOAD_SIZE environment variable 2026-07-17 17:56:48 +02:00
yu-i-i 7fce14beb7 Fix style of Templates button in navbar 2026-07-17 17:56:48 +02:00
yu-i-i f99a364148 Fix: comments thread does not necessarily belong to current document 2026-07-17 17:56:48 +02:00
yu-i-i 1bd06b6c4b Fix import after upstream converted AuthorizationMiddleware to ESM 2026-07-17 17:56:48 +02:00
yu-i-i 9f3236cc92 Track changes: Use getProjectRanges introduced in the upstream 2026-07-17 17:56:48 +02:00
yu-i-i 69b5ba751d Rename @node-saml/node-saml patch for v5.1.0 2026-07-17 17:56:48 +02:00
yu-i-i ad45c80c3a Fix imports after upstream converted UserController and CaptchaMiddleware to ESM 2026-07-17 17:56:47 +02:00
yu-i-i 43b195baa9 Fix import after upstream converted ErrorController to ESM 2026-07-17 17:56:47 +02:00
yu-i-i e3ebaca4b6 Bring back deleteFile removed upstream 2026-07-17 17:56:47 +02:00
yu-i-i 236ceb60de Symbol palette: ui components were moved to shared folder 2026-07-17 17:56:47 +02:00
yu-i-i 6fe766097b Move Sympol Palette code to modules, closes #63 2026-07-17 17:56:47 +02:00
yu-i-i acd3c256cd Template gallery: ui components were moved to shared folder 2026-07-17 17:56:47 +02:00
yu-i-i 6547ffbb46 passport-saml was updated to v5.1.0 2026-07-17 17:56:47 +02:00
yu-i-i 3fbae278e6 Fix: always include repository prefix in sandboxed compiles image name 2026-07-17 17:56:46 +02:00
yu-i-i d8fe1bd10c Fix template publishing to align with upstream project context changes 2026-07-17 17:56:46 +02:00
yu-i-i c704b9e04a Remove LDAP admin check via group search in ldap-passport 2026-07-17 17:56:46 +02:00
yu-i-i 637f66a91c toggleSymbolPalette now in useEditorPropertiesContext 2026-07-17 17:56:46 +02:00
yu-i-i 8a71e33174 Set use-ai capability to false 2026-07-17 17:56:46 +02:00
yu-i-i 7f9358f358 Track changes: convert to mjs 2026-07-17 17:56:46 +02:00
yu-i-i 00fa3a2082 Track changes: use getTrackedChangesUserIds introduced in the mainstream 2026-07-17 17:56:46 +02:00
yu-i-i 47fe923883 Remove OVERLEAF_OIDC_DISABLE_JIT_ACCOUNT_CREATION in favor of OVERLEAF_OIDC_ALLOWED_EMAIL_DOMAINS 2026-07-17 17:56:46 +02:00
Synésio Neto cc562f6314 Introduce an environment variable to allow JIT OIDC users creation based on their email address domain. 2026-07-17 17:56:46 +02:00
yu-i-i 71c76e10d6 Introduce an environment variable to control user creation in OIDC authentication, closes #47 2026-07-17 17:56:46 +02:00
David Rotermund fdaed06adc I need the --network=host settings for building the container 2026-07-17 17:56:45 +02:00
yu-i-i 371a736408 Avoid DEP0174 by removing async from callback-based getGroupPolicyForUser 2026-07-17 17:56:45 +02:00
yu-i-i b7c5088603 Fix login page 2026-07-17 17:56:45 +02:00
yu-i-i 07cf12a3ec Symbol Palette: make close button visible 2026-07-17 17:56:45 +02:00
yu-i-i 61591cd2e7 Template Gallery: replace markdown-it with marked 2026-07-17 17:56:45 +02:00
yu-i-i db1c746cc2 Refactor Template Gallery; resolves #38 and #39
- Replace free-text license input with a select box
- Improve visual presentation of modals and enhance keyboard interaction
2026-07-17 17:56:42 +02:00
yu-i-i 55e24c3158 Refactor Sandboxed Compiles 2026-07-17 17:56:08 +02:00
yu-i-i 90ca6f6666 Symbol Palette: improve keyboard input experience 2026-07-17 17:56:08 +02:00
yu-i-i 3c031dc7a0 Symbol Palette: get rid of @reach/tabs 2026-07-17 17:56:08 +02:00
yu-i-i baad2fabd4 Add 'poll' to clsi seccomp profile, fixes minted. Thanks, David. 2026-07-17 17:56:07 +02:00
yu-i-i bfd93d1071 Make Template Gallery optional; rename environment variables 2026-07-17 17:56:07 +02:00
yu-i-i 2a8b0bef67 Add Template Gallery support 2026-07-17 17:56:04 +02:00
yu-i-i e0050414d6 Track changes / comments: update backend to support frontend changes 2026-07-17 17:55:16 +02:00
yu-i-i 35dac5f76e Allow EXTERNAL_AUTH to be undefined, fixes #26 2026-07-17 17:55:16 +02:00
yu-i-i c9f28472fe Symbol palette: switch to 'OL' UI components and apply minor cosmetic changes 2026-07-17 17:55:16 +02:00
yu-i-i cc6ec7eb6e Make OVERLEAF_OIDC_USER_ID_FIELD support 'email' as a value 2026-07-17 17:55:15 +02:00
yu-i-i 72c739283e See upstream commit 42ee56e 2026-07-17 17:55:15 +02:00
yu-i-i edc66f8722 Fix glitches in symbol palette after switching to Bootstrap 5 2026-07-17 17:55:15 +02:00
yu-i-i e1c746aa68 Whitelist /oidc/login endpoint, fixes #21 2026-07-17 17:55:15 +02:00
yu-i-i 32ee132dd9 Add ENV variables to control SAML signature validation 2026-07-17 17:55:15 +02:00
yu-i-i 0325f4c519 Re-export doLogout (was removed from exports in commit b9fb636). 2026-07-17 17:55:15 +02:00
yu-i-i fae9cef1e4 Refactor authentication code; add OIDC support 2026-07-17 17:55:08 +02:00
yu-i-i 2b78561654 Allow adding extra flags to LaTeX compiler through environment variable 2026-07-17 17:54:27 +02:00
yu-i-i 5b8db1ddef Enable LDAP and SAML authentication support 2026-07-17 17:54:27 +02:00
yu-i-i d0f96f6587 Enable Symbol Palette 2026-07-17 17:54:19 +02:00
yu-i-i a6a32d6d58 Allow selecting a TeX Live image for a project 2026-07-17 17:52:29 +02:00
Sam Van den Vonder c8bb029509 Enable Sandboxed Compiles feature 2026-07-17 17:52:28 +02:00
yu-i-i c0c5bbd960 Enable track changes and comments feature 2026-07-17 17:52:28 +02:00
yu-i-i 1f6f83f5c3 Redirect non-existing links to Overleaf page 2026-07-17 17:52:28 +02:00
Kristina 28ad3b03b7 [web] add email notifications dry run split test (#34653)
GitOrigin-RevId: 62f73ce3850b994bf2a4a054edd6fa62134213a9
2026-06-26 08:08:18 +00:00
Alexandre Bourdin 33b8f27097 Merge pull request #34489 from overleaf/ab-keydown-event-logging
[web] Trigger full project search / focus mode on lowercase key matching for keyboard shortcuts

GitOrigin-RevId: a1786050c205ed46229b939c6325a3130c6fbcd3
2026-06-26 08:08:14 +00:00
Jakob Ackermann a6c98365d9 [web] use client-side filtering for split tests in admin panel (#34975)
* [web] use client-side filtering for split tests in admin panel

Filtering on non-indexed fields is not allowed with notablescan.

* [web] add initial value for DEFAULT_FILTERS.active

* [web] unset error before fetching split tests

GitOrigin-RevId: 0813873c5a90d57c4fdbfd0043fcc325f00408aa
2026-06-26 08:08:10 +00:00
Jakob Ackermann 1b553c0455 [migrations] only apply non-blocking migrations in CI (#34665)
* [migrations] only apply non-blocking migrations in CI

* [migrations] use "yarn run migrations" instead of "npm run migrations"

GitOrigin-RevId: d9f96db398adf2febcd82333295fb8d5eab741bb
2026-06-26 08:08:05 +00:00
Jakob Ackermann 9fd0d644cf [agents] refine guidance for running tests (#34977)
* [monorepo] fix handling of quoted MOCHA_GREP

* [agents] refine guidance for running tests

- nudge it towards scoped module targets
- nudge it to really run e2e tests when changed
- nudge it towards a11y cypress matchers
- recommend quoting for MOCHA_GREP

* [web] fix forwarding of mocha args from module Makefile

* [monorepo] use BRANCH_NAME_TAG_SAFE for building the ci image name

* [web] also make BRANCH_NAME_TAG_SAFE available in module Makefile

(It's not really needed, but will make copilot happy.)

GitOrigin-RevId: bfda3c8fe2b4f857ef111ac63f94b35a82ad75f8
2026-06-26 08:07:37 +00:00
Eric Mc Sween c645215afc Merge pull request #34907 from overleaf/em-promisify-github-sync
Promisify github-sync

GitOrigin-RevId: ac68f3f18ad211e204631d80bd5d4354ea23aaff
2026-06-26 08:07:30 +00:00
Eric Mc Sween 2c54574377 Merge pull request #34922 from overleaf/lg-vite-security-upgrade-v2
[Security upgrade] bump vite to 7.3.5 in root workspace

GitOrigin-RevId: 278475d98e23f0679ec45993e2a6816e9b93eb64
2026-06-26 08:07:25 +00:00
Eric Mc Sween 4fb8608908 Merge pull request #34884 from overleaf/em-already-in-your-library
Library import: flag entries that are already in the user's library

GitOrigin-RevId: f3c92fb59283631e1a9e78d2e08a3a365981df45
2026-06-26 08:07:21 +00:00
Mathias Jakobsen 75f7f82a3f Merge pull request #34923 from overleaf/mj-left-menu-field
[web] Clean up "left menu" concepts in new editor

GitOrigin-RevId: 9597c1c53af15f3a1587e62176bca36cd7c9069c
2026-06-26 08:07:10 +00:00
Mathias Jakobsen 5dd2c425ac Merge pull request #34912 from overleaf/mj-file-tree-style-cleanup
[web] Clean up file tree scss post editor redesign

GitOrigin-RevId: de2c5f3bd2979600897ffbbdcaead7ef72fa6ae1
2026-06-26 08:07:05 +00:00
Kate Crichton 9034e81c9b Merge pull request #34508 from overleaf/kc-email-admin-domain-reverification-failed
[web] email group admin when domain reverification failed

GitOrigin-RevId: d885f26b1b39184c25133f70024a72af66fa2d69
2026-06-26 08:07:01 +00:00
Kate Crichton 907f5a969a Merge pull request #34841 from overleaf/kc-grace-period-failedat-anchor
[web] Anchor domain-capture grace period on failedAt

GitOrigin-RevId: 5a31e5189512b22952c387722c7ad67f5c6397e3
2026-06-26 08:06:57 +00:00
Andrew Rumble bbf5586935 Merge pull request #34919 from overleaf/lg-undici-security-upgrade
[Security upgrade] bump undici to 7.28.0 in root workspace

GitOrigin-RevId: 39a6fb92235c6453fbd6106c05af66fb1d089607
2026-06-26 08:06:49 +00:00
Maria Florencia Besteiro Gonzalez 1e03046d68 Merge pull request #34587 from overleaf/lg-security-netty-codec-http2
[Security upgrade] bump io.netty:netty-codec-http2 to 4.1.135.Final in services/git-bridge

GitOrigin-RevId: e6b6b110bd4ce9c70c91f701cc3cb4f0868f870a
2026-06-25 08:06:26 +00:00
Jessica Lawshe 6c9b4aa498 Merge pull request #34768 from overleaf/jel-email-when-domain-verified
[web] Email group admin when domain is verified

GitOrigin-RevId: 5f8bc6187c561067d16863d3586a94ef498f3bdf
2026-06-25 08:06:22 +00:00
Jessica Lawshe 02d9c344f2 Merge pull request #34821 from overleaf/jel-clear-domain-notifications
[web] Make domain notifications dismissible

GitOrigin-RevId: fa1648efd0aa73213e5352b347a1b0f4c07dfc04
2026-06-25 08:06:18 +00:00
Jessica Lawshe 4a635d7599 Merge pull request #34819 from overleaf/jel-domain-table-spacing
[web] Update spacing in groups domain table

GitOrigin-RevId: 6e908b79e45212cf060068271ec56c3146fe20d7
2026-06-25 08:06:13 +00:00
Mathias Jakobsen a038e4b3a2 Merge pull request #34903 from overleaf/mj-settings-tabs-hover
[web] Add hover states to settings tabs

GitOrigin-RevId: c41c200231d0308267fc346d2949ef659a1950bc
2026-06-25 08:05:59 +00:00
Tim Down 3e29ce839f Merge pull request #34602 from overleaf/td-stripe-3ds1-redirect
Fix for Stripe redirecting to incorrect page

GitOrigin-RevId: ac90b2e501c4bf5f2738d61866ea72ba861e5539
2026-06-25 08:05:54 +00:00
ilkin-overleaf 13bc31cf25 Merge pull request #34870 from overleaf/ii-share-modal-group-sharing
[web] Make group project sharing available to Group Professional plans only

GitOrigin-RevId: ced66529818d573cfd20cef016ed4b3aafdd8d51
2026-06-25 08:05:50 +00:00
ilkin-overleaf f536b39b03 Merge pull request #34627 from overleaf/ii-share-modal-improvements
[web] New share modal improvements

GitOrigin-RevId: d8b47a6294f8d7e4833130fd2bb1720bfe2357d8
2026-06-25 08:05:45 +00:00
roo hutton 0fab15c58e Merge pull request #34730 from overleaf/rh-tax-id-verify
Validate tax ids and send emails on failed verification

GitOrigin-RevId: 84d53fdaf00d7b23ced22dd540a071e3028ced93
2026-06-25 08:05:34 +00:00
Brian Gough 29c8717b60 Merge pull request #33845 from overleaf/lg-ws-security-upgrade
[Security upgrade] pin ws@^8 to 8.21.0

GitOrigin-RevId: 40b839757122c1116debe8b7ebfa4eba9187bfa8
2026-06-24 08:06:51 +00:00
Andrew Rumble 255a6242cb Merge pull request #34340 from overleaf/ar-things-that-dont-need-to-be-pinned
[dependency-management] find unnecessary pins script

GitOrigin-RevId: b935ee7ae72c0339ea58f14d5f0a991ed4ae350c
2026-06-24 08:06:36 +00:00
Jakob Ackermann a8b4f2dc55 [web] reduce mongo lookups for compile requests (#34890)
* [web] _getUserCompileLimits: use owner.analyticsId directly

* [web] deduplicate project lookups for compile requests

* [web] add test case for prefetched project

GitOrigin-RevId: 0e140619c874accf8673153ed90af2ce70cf9234
2026-06-24 08:06:18 +00:00
Mathias Jakobsen 795ba4cd41 Merge pull request #34655 from overleaf/mj-ai-error-assistant-ide-redesign-cleanup
Clean up ai error assistant  style after editor redesign

GitOrigin-RevId: 8ca0747a567fe8f46b4d76886cd0723e1721ced1
2026-06-24 08:05:58 +00:00
Mathias Jakobsen fe52c3df12 Merge pull request #34872 from overleaf/mj-bulk-review-permissions
[web] Hide bulk track change actions for reviewers

GitOrigin-RevId: f636f4c82fdfb43a6dedf1abbb3cc53805eab7f8
2026-06-24 08:05:53 +00:00
Mathias Jakobsen c625ce428e Merge pull request #34875 from overleaf/mj-logs-link-colour
[web] Avoid default blue link colours in log headers

GitOrigin-RevId: 56359ededf5ba8c1a615bf1bf84ba6502f9c0078
2026-06-24 08:05:49 +00:00
David 7ce1e0bd48 Merge pull request #34797 from overleaf/dp-cleanup-reference-settings-migrated-field
[web] Add migration to unset reference settings migrated flag

GitOrigin-RevId: 570621d7a78f225a4bbf98f7046b096ffa735048
2026-06-24 08:05:38 +00:00
Brian Gough c6a46fbdad Merge pull request #33961 from overleaf/lg-qs-security-upgrade
[Security upgrade] pin qs to 6.15.2 in root yarn.lock

GitOrigin-RevId: 4819f0a1f9b41821ccda354a309d190d8395ef56
2026-06-24 08:05:31 +00:00
Brian Gough 81f85293f2 Merge pull request #33656 from overleaf/lg-protobufjs-security-upgrade
[Security upgrade] Pin protobufjs to 7.6.3 and @protobufjs/utf8 to 1.1.1 via resolutions

GitOrigin-RevId: 7d71d564d7296af76f04fda8b35f178c5014a9b9
2026-06-24 08:05:26 +00:00
Jakob Ackermann 1cc18dd0f0 [web] avoid UserAnalyticsIdCache for split-test assignments (#34803)
* [web] split-tests: add helpers to using an already fetched mongo user

* [web] split-tests: add ignoreOverrides option to getAssignment

* [web] split-tests: reuse already fetched mongo users where possible

* [web] notifications: fetch users in bulk for split-test assignment

* [web] split-tests: use session for assignment where possible

* [web] split-tests: pass req/res down into domain-capture hook

* [web] split-tests: explicitly throw early

* [web] split-tests: refine jsdoc types

* [web] mark analyticsId and labsProgram as required in user model

* [web] split-tests: refine jsdoc types

* Revert "[web] split-tests: pass req/res down into domain-capture hook"

This reverts commit 897a158384cd2b3e416fc34495b20d1e2a0e68f0.

GitOrigin-RevId: ea4afd15c2506f369827b5193fd16d220d3c7d0e
2026-06-23 08:06:42 +00:00
Jakob Ackermann 6b084424b5 [monorepo] fix running make lint from root (#33559)
* [monorepo] remove spurious package-name field from buildscript.txt

* [monorepo] align variables/paths across build script variants

* [monorepo] align linting/test setup for jobs/libraries

* [monorepo] fix "make lint/lint_fix" from root

* [wiki-algolia] integrate with yarn workspace

GitOrigin-RevId: a0954f2936d03cd4a69b3f2c8098d9ee2e361993
2026-06-23 08:06:37 +00:00
Eric Mc Sween 006b30c441 Merge pull request #34817 from overleaf/em-library-backend-search-2
Library search: make backend search tokens consistent with frontend (re-apply #34643)

GitOrigin-RevId: e5a143edf3226c12eaff77b2bc4cdb68e139bb10
2026-06-23 08:06:33 +00:00
Jimmy Domagala-Tang 5cd4447cbf Adding 2026 TeXLive behind admin (#34291)
* feat: adding 2026 behind admin

* feat: bumping packer and clsi memory to accomodate new image

* align packer build and packer verify disk sizes

* feat: adding more space for packer build

GitOrigin-RevId: 1961ea7937e2c751137a4b9564a34d4e371ce0f9
2026-06-23 08:06:14 +00:00
Jakob Ackermann 88267716a3 [web] adopt free/premium compile backend class (#34846)
* [web] adopt free/premium compile backend class

* [rails] adopt free compile backend class for pdf conversion

GitOrigin-RevId: e12c99a27363f5654132c2fcdc6c5d8ccc591868
2026-06-23 08:05:59 +00:00
Brian Gough 23dca72cd3 Merge pull request #34843 from overleaf/bg-add-compile-timer
add timer for compile with checkpoints

GitOrigin-RevId: ec758d34594f242a5cac8192233bfe2db783e42a
2026-06-23 08:05:55 +00:00
Brian Gough b1ed38c2d0 Merge pull request #34761 from overleaf/bg-enable-clsi-checkpoints
adds enableCheckpoint to compile requests from web to clsi

GitOrigin-RevId: 431b28602091cf8f7252ad836f083c7b092351d4
2026-06-23 08:05:50 +00:00
Jakob Ackermann 3be2f320f8 [web] fix preference of new/old clsiServerId in double-compiles (#34862)
GitOrigin-RevId: 81cbbcec9fef79ebd0c5e7d90048fc4bda302108
2026-06-23 08:05:42 +00:00
Brian Gough d75ef19bbf Merge pull request #34746 from overleaf/bg-clsi-apparmor
add new clsi apparmor profile for checkpoints

GitOrigin-RevId: 3a003263326613d3166343d54adb7050fe1e9847
2026-06-23 08:05:38 +00:00
Mathias Jakobsen e0cfb07f60 Merge pull request #34698 from overleaf/mj-context-menu-tracked-changes
[web] Add tracked changes options to context menu

GitOrigin-RevId: b49198c6c2c63063d96ace69f10b617ac819bb04
2026-06-23 08:05:33 +00:00
Kristina a53b164b45 [web] email content previews for notification emails (#34576)
* add content previews to notification emails
* add fetcher for doc lines
* gate content previews behind a split test
* make composable blocks for rendering

GitOrigin-RevId: c8df384d55a301084b98df76fd3edd6dde6ea50c
2026-06-22 08:06:25 +00:00
Domagoj Kriskovic 63a605484c Add support for rendering math in Markdown with MathJax
GitOrigin-RevId: dd1c1c72237a5364c642e46c66e08ea7a6977662
2026-06-22 08:05:40 +00:00
Mathias Jakobsen ef05288f81 Merge pull request #34714 from overleaf/mj-tracked-change-merges
[overleaf-editor-core+ranges-tracker] Pick lower timestamp when merging tracking info

GitOrigin-RevId: 5b1c8d7c719003df4506014c381f2a525d0a7ff4
2026-06-19 08:07:27 +00:00
Eric Mc Sween 7ee385fe45 Merge pull request #34816 from overleaf/revert-34643-em-library-backend-search
Revert "Library search: make backend search tokens consistent with frontend (follow-up to #34386)"

GitOrigin-RevId: 83328f2ee4a9c3f668325fa4de1ca652e7c718bf
2026-06-19 08:07:19 +00:00
Eric Mc Sween 80218df85e Merge pull request #34643 from overleaf/em-library-backend-search
Library search: make backend search tokens consistent with frontend (follow-up to #34386)

GitOrigin-RevId: 91ef5b5dc81ba8ebda43e746c9b747b7636f3e80
2026-06-19 08:07:12 +00:00
Eric Mc Sween 0371ba46f6 Merge pull request #34693 from overleaf/em-upload-bib
[web] Add "Upload .bib file" option to library

GitOrigin-RevId: 7067a4cb10f0973c0423507836ef13555329942c
2026-06-19 08:07:08 +00:00
Kate Crichton 0883e0fec0 Merge pull request #34513 from overleaf/kc-verify-domain-from-table
[web] verify domain from table

GitOrigin-RevId: 2e41f76be26b81f204208fab714f2e3399071499
2026-06-19 08:06:34 +00:00
Kate Crichton f7b1ccae9a Merge pull request #33852 from overleaf/kc-schedule-domain-verification
[web] schedule domain reverification

GitOrigin-RevId: 08217692960379d0a6f7ce457032b1182fb2bf4e
2026-06-19 08:06:30 +00:00
Liam O'BrienandClaude Opus 4.8 bc9c85019f Lo zotero initial library sync (#34456)
* Migration and index setup script

* Helper script to clear linked library

* Pull users collections

* Implementing get items from users zotero library

* Initial zotero sync client

* Pulling users overleaf collection

* Pulling users overleaf collection

* Initial sync pull from Zotero to Library

* Sync and import workflow from Zotero into Library

* Using user getter and setter for updating user library info

* Using new collection for library sync states

* Adding tests for new sync functionality

* Harden Zotero sync route

* Simplify Zotero sync state read and skip accounting

Fold the ensure+get double round-trip in sync-state discovery into one
findOneAndUpdate, and derive itemsSkipped from items.length - itemsInserted
instead of a running counter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Redact URL-encoded token secret from OAuth1 request logs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fixing type failure

* Rename upsertZoteroReferences to importZoteroReferences

The method is insert-only; the bulkWrite upsert just makes re-runs
idempotent rather than updating, so the name now reflects that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Validate librarySyncStates reads with zod

Replace the `as` / `as unknown as` casts in getLibrarySyncState and
ensureLibrarySyncState with a zod schema parse, matching
LibraryReferenceRepository. Types are now derived from the schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Defer Zotero sync watermark until pagination exists

Don't record lastSyncedVersion after a single-page sync: committing the
page-1 version before pagination (#34286) lands would let incremental
sync (#34282) skip items beyond the first page. Drops the commitSyncState
call and toVersion from SyncResult; the version envelope is left for the
follow-ups. commitSyncState stays in the repository for #34282.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Extract OAuth1Transport from OAuth1Handler

Lift the OAuth1 client/config registry, getAccessToken, redactSecret and
classifyOAuth1Error into oauth1/OAuth1Transport.js, plus a signedGet
helper for provider modules to reuse. OAuth1Handler now imports them; its
method bodies are unchanged. Test mocks move to esmock's global scope so
they reach the transport, with Errors pinned for instanceof checks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Move Zotero collections/items into a per-provider ZoteroProvider

Carve the Zotero-only operations out of the generic OAuth1 handler into
providers/ZoteroProvider.js, dispatched via providers/ProviderRegistry.js.
ReferenceProviderHandler now resolves the provider from the registry and
throws UnsupportedOperationError (mapped to 501) for providers without the
operation, instead of bolting Zotero-only methods onto OAuth1Handler.

Zotero's collectionsUri/itemsTopUri are read from settings by ZoteroProvider
rather than copied into the shared OAuth1 registry. Removes getCollections/
getUsersItemsChanges (and their tests) from OAuth1Handler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add ZoteroProvider unit tests and unsupported-provider coverage

Cover ZoteroProvider collections/items parsing, header envelope and
malformed-payload rejection, and assert ReferenceProviderHandler throws
UnsupportedOperationError for providers without a sync implementation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Make addSyncedCollection idempotent

Only push a synced collection when its key isn't already recorded, so a
repeat sync can't create duplicate syncedCollections entries regardless of
the caller-side guard. Adds a sequential test asserting no duplicate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Hardcode Zotero library endpoint paths, derive host from site

Move the collections/items-top endpoints out of settings and into
ZoteroProvider as path constants, taking only the host from the
provider's `site`. Aligns Zotero `site` with the other providers
(API host) and avoids adding per-endpoint config points.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Make Zotero sync routes explicit, 404 unknown providers

Hardcode the collections/items routes to /zotero rather than a generic
/:ref_provider, so a non-Zotero request 404s on the missing route
instead of returning a misleading 501. Drop the UnsupportedOperationError
501 branch, reverting OAuth2 unsupported ops to their prior 500.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
GitOrigin-RevId: 50e16a9c46f995c2e9db6eed50ef81c3a71ea71d
2026-06-19 08:06:07 +00:00
Antoine ClausseandClaude Opus 4.8 e5f712a857 [web] Cleanup plans-2026-phase-1 - phase 2: pug/backend pricing (#34623)
* Remove `plans2026` from pug files in subscriptions module

* Remove `plans2026` param from `makeGroupPlans`

* Remove `plans2026` param from `makeUISettings` in interstitial-payment.mjs

* Remove `plans2026` param from `makeInterstitialPaymentConfig` in interstitial-payment.mjs

* Remove `plans2026` param from `makeIndividualPlans`

* Remove `plans2026` param from `makeStudentPlans`

* Remove `plans2026` from try-premium page

* Remove `plans2026` from controllers

* Remove unused files

* Update translations

* Update tests

* Update tests

* Remove unused translations

* Rename the collaborator upgrade-mode test to read explicitly as the
Standard-plan replacement for the removed redirect test, and drop the
stale "when plans 2026 is enabled" framing now the controller no longer
branches on the flag.

* Surface controller errors in InterstitialPayment tests

Move assertions out of the res.render callback into render/redirect
helpers that resolve with the captured args and reject on the wrong
path or a controller rejection, so failures show the real error
instead of timing out. Restore the ai-limit-upgrade-copy split-test
assignment for the AI copy tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* format

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
GitOrigin-RevId: f0b055b4d92b494b531766a13b057336d9c2c9af
2026-06-19 08:06:03 +00:00
David d6e55f81ff Merge pull request #34750 from overleaf/dp-cleanup-reference-settings-migration
[web] Remove reference manager settings migration code

GitOrigin-RevId: 1062e1f08a6e6d4547f48c965c342d2ac15e9a4f
2026-06-19 08:05:56 +00:00
Copilot ea1871b0df Use Ctrl+Shift+C for the add-comment shortcut while keeping Cmd+Shift+C active (#32624)
GitOrigin-RevId: 5fdf9ae7dd0fa194c3ac6acf9abb70ac9721fd2e
2026-06-19 08:05:48 +00:00
Mathias Jakobsen b570078223 Merge pull request #34774 from overleaf/mj-fix-cleanup-locales
[web] Ignore cache files in cleanupUnusedLocales

GitOrigin-RevId: 72ad8fb265a6fe905671dd379b3ca29468c9f759
2026-06-19 08:05:10 +00:00
Mathias Jakobsen 5f79c2dd10 Merge pull request #34512 from overleaf/mj-floating-toolbar-option
[web] Add setting for floating toolbar menu

GitOrigin-RevId: bd28f1f287f2011535c7fd530f1217f01e0cfbbc
2026-06-18 08:07:33 +00:00
Jessica Lawshe 106dd1b07b Merge pull request #34702 from overleaf/jel-grace-period-ui
[web] Update domain verification failed grace period UI

GitOrigin-RevId: 267dc4e4cc60b959faaacecc5a13b662d670e313
2026-06-18 08:07:09 +00:00
MoxAmber a3036730b4 Merge pull request #34644 from overleaf/as-link-sharing-analytics
[web] Add sharing link analytics events

GitOrigin-RevId: b206732397065c399d38a8e42dbdebe60222fb24
2026-06-18 08:06:52 +00:00
MoxAmber ad9b28af18 Merge pull request #34537 from overleaf/ii-share-modal-invited-people
Show "No one invited yet" instead of "1 person invited" for solo projects

GitOrigin-RevId: 0423b507e0574dc5a381cbe4044912a25af7a3f0
2026-06-18 08:06:48 +00:00
MoxAmber a7a3ccdb71 Merge pull request #34639 from overleaf/as-link-sharing-editinvitecount
[web] Ignore reuable invites (aka sharing links) when checking edit invite count

GitOrigin-RevId: 355ec6e6b2d5d497392fe1bdc04e68ad80349518
2026-06-18 08:06:44 +00:00
MoxAmber bd8a156908 Merge pull request #34394 from overleaf/as-link-sharing-groups-split-test
[web] Move group link sharing behind a separate split test

GitOrigin-RevId: c973a9bccb88f427a9f51d121b58a8799993e7c9
2026-06-18 08:06:39 +00:00
Domagoj Kriskovic 62c2523aec Add table support for visual markdown editor
GitOrigin-RevId: b36f575d40b19fdf32622c9923cecd5b47658d5d
2026-06-18 08:06:26 +00:00
Eric Mc Sween 95e0a88bbb Merge pull request #34519 from overleaf/em-github-sync-octokit-migration
Replace octonode with octokit in github-sync

GitOrigin-RevId: 05886b87ed8625dbbcb77dc2d6ccbeda7b426e79
2026-06-18 08:06:22 +00:00
roo hutton 1dfe6e60b3 Merge pull request #34534 from overleaf/rh-rm-recurly
[web] Remove conditional Recurly checkout page

GitOrigin-RevId: 5bb773080bba682628f0f5428c72b7dff75b6015
2026-06-18 08:06:17 +00:00
Davinder SinghandCopilot Autofix powered by AI 6613ef2f18 [WEB] Move Review Toggle into the toolbar - 2 (#34578)
* wip of adding Review Toggle into the toolbar

* renaming to isToolbarMigration

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* removing extra comments

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: 2c0b54ae983a32c083def8e9e64ddd0fb7ddf334
2026-06-18 08:06:10 +00:00
Malik Glossop b04fea4726 Merge pull request #34559 from overleaf/mg-writefull-workbench
Allow opening workbench and hide texgpt for ai menu behind flag

GitOrigin-RevId: f4de64ce9b68f4e337772e164e46e27d2a41a0e7
2026-06-18 08:06:06 +00:00
Antoine Clausse 0d004926e6 [web] Cleanup plans-2026-phase-1 - phase 1: frontend (#34619)
* Remove `plans-2026-phase-1` from frontend in subscriptions module

* Remove `plans-2026-phase-1` from general frontend

* Remove empty `AiErrorAssistantPaywall`

* Simplify `UpgradeNotification`

* Update translations

* Fix tests

* Remove `OverleafAssistPaywall`

* Remove unused locales

GitOrigin-RevId: 4cf04bbde134a3f7b778a0695d388f31d9765343
2026-06-18 08:06:01 +00:00
Mathias Jakobsen fb9ebfcc10 Merge pull request #34459 from overleaf/mj-review-panel-style-cleanup
[web] Clean up review styles post-editor redesign

GitOrigin-RevId: b8b535f765f446770e62e31519f7f1e98dc7e3d2
2026-06-18 08:05:54 +00:00
Mathias Jakobsen a14971b790 Merge pull request #34600 from overleaf/mj-collapsible-file-header-cleanup
[web] Clean up collapsible file header styles post editor redesign

GitOrigin-RevId: a5c8749360433213d6602d029ea6b66312c9032b
2026-06-18 08:05:49 +00:00
Rebeka Dekany 51fc266bc2 Redirect users without an active subscription to the subscription page instead of the error page (#34637)
* Redirect freemium users from billing portal routes to subscription page with banner

* Handle null management link and unknown service in subscription redirects

* Move deleted-subscription invoices link inside `getCustomerManagementLink` instead

GitOrigin-RevId: 7122f69e9b8bda6238111f76c15a35135ce5f0ac
2026-06-18 08:05:22 +00:00
Jakob AckermannandAndrew Rumble 6323fddbd8 [web] avoid UserAnalyticsIdCache (#33121)
* [web] always provide an analyticsId when registering new users

* [web] add recordEventForMongoUser and use it during login

* [web] use recordEventForMongoUser/recordEventForSession where possible

* [web] use session cache for analyticsId when refreshing features

* [web] add setUserPropertyForMongoUser and use it where possible

* [web] use setUserPropertyForSessionInBackground where possible

* [web] use mongo user for analyticsId when setting utm user properties

* [web] admin-panel: user updated user when sending events

* [web] enforce labsProgram being populated on user object

Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>

* [web] revert FeaturesUpdater.refreshFeatures change

* [web] labs: move session maintenance into handler

* [web] analyticsMiddleware: ensure that all users have labsProgram field

* [web] migrate a few more events to recordEventForSession

* [migrations] mark back-fill of db.users.analyticsId as required

* [web] setUserPropertyForSessionInBackground: throw early

---------

Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
GitOrigin-RevId: 19c26bc36abd8c11f0a8f458d25abfb7cbda0bb7
2026-06-17 08:08:32 +00:00
Anna Claire Fields 7035a2e025 Revert "Acf pnp migration master" (#34709)
GitOrigin-RevId: ec7d63e1212e05dd6dfc7a0ddf785c11eca76ad6
2026-06-17 08:07:56 +00:00
Anna Claire Fields e1226515f6 PNP deployment: clsi-lb, web, latexqc fixes (#34696)
GitOrigin-RevId: fe1f5a7670d6fcf2128c5e2b50cc2a544afb1ebc
2026-06-17 08:07:36 +00:00
Anna Claire Fields 8f07308342 Acf pnp migration master (#34040)
GitOrigin-RevId: 418eab9bc733f27a6e0f8f04a2a8e21c2f5a491f
2026-06-17 08:07:16 +00:00
Jakob Ackermann d44e862a0c [web] throw early in UserCreator.createNewUser with missing analyticsId (#34664)
* [web] throw early in UserCreator.createNewUser with missing analyticsId

* [migrations] rerun back-fill of db.users.analyticsId

GitOrigin-RevId: eabf16326192fca4dbea00ad643cee4315c426c6
2026-06-17 08:06:59 +00:00
Jakob Ackermann 6bc9f1dae4 [server-ce] test: the first startWith must resetData when using dataDir (#34671)
* [server-ce] test: the first startWith must resetData when using dataDir

* [server-ce] test: fix order of creating user and resetting DB

GitOrigin-RevId: 2a6e6344919fe48d41704e1a3cb39c6e22b5a924
2026-06-17 08:06:54 +00:00
Malik Glossop bf22bf9eed Merge pull request #34487 from overleaf/mg-fix-nested-context-menu
Block native context menu on editor context menu and fix focus styling

GitOrigin-RevId: c2d59e7acc59019b5e2777cc1bf3ad24a7d8e54e
2026-06-17 08:06:49 +00:00
Malik Glossop 8ef463f8b0 Merge pull request #34357 from overleaf/mg-combine-floating-menus
Add combined floating menu for comment, tracked changes, writefull

GitOrigin-RevId: 4de5b9d3b1717fc76394f7c0edfe4d7230394c8d
2026-06-17 08:06:44 +00:00
David 9fae908602 Merge pull request #34585 from overleaf/lg-security-hapi-wreck-joi
[Security upgrade] bump @hapi/wreck from 18.1.0 to 18.1.2 in services/third-party-references

GitOrigin-RevId: a6efbbb9b7c8ddf995c247f8b787c16fa8b6d6f3
2026-06-17 08:06:34 +00:00
Davinder Singh 8e8804a318 [WEB] Add TeXGPT button to the main toolbar (#34219)
* feat: add writefull-toolbar-migration split test and
  sourceEditorToolbarStartButtons slot

* feat: add TeXGPT button to main editor toolbar (writefull-toolbar-migration)

* removing wrapper div

* hide toolbar start buttons from readonly users

GitOrigin-RevId: 6bafe3ad3fade98c318ea0d0b2714957c1d2d9b4
2026-06-17 08:06:16 +00:00
Davinder Singh e1881a7645 [WEB] Toolbar overflow: drop one button at a time (#34171)
* adding individual hiding logic for the toolbar icons

* using css for logic

GitOrigin-RevId: 87e9f72c1dab888e2701ef108ecaa0b190e366ff
2026-06-17 08:06:11 +00:00
CopilotandJakob Ackermann f40cebf60b [docstore] replace remaining Joi usage with zod (#34603)
Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: 41ca0c35d59bf6accd619f6b5149be14a69e1078
2026-06-16 08:07:22 +00:00
Brian Gough 6f9b7031f4 pass through checkpoint setting (#34631)
GitOrigin-RevId: 50e9791b468253ee966b0eac822b2a01321a0ece
2026-06-16 08:07:14 +00:00
Brian Gough be4108a573 add glob matching to isExtraneousFile (#34633)
GitOrigin-RevId: f12aedb52195cfc9e3254936f1db6189f49c3f37
2026-06-16 08:07:09 +00:00
Domagoj Kriskovic 0778a0e4d3 Revert "Add markdown link tooltip support to the visual editor"
This reverts commit 7ab4fd0793f057c86e4fc0307d81df376efe4d34.

GitOrigin-RevId: 625a5cbbb39832f6a32a26d2ad454669d0ed1846
2026-06-16 08:07:01 +00:00
Rebeka DekanyandM Fahru 1f28eaecdb [web] Add Stripe acceptance tests (#34577)
* Add StripeClient mock-target support and Stripe test settings

Co-Authored-By: M Fahru <m.fahru@overleaf.com>

* Add Stripe payment page acceptance tests

Co-Authored-By: M Fahru <m.fahru@overleaf.com>

* Add MockStripeApi for stateful Stripe acceptance tests

Co-Authored-By: M Fahru <m.fahru@overleaf.com>

* Add Stripe subscription acceptance tests

* Add Stripe webhook acceptance tests

* Use a helper for setting the subscription status

---------

Co-authored-by: M Fahru <m.fahru@overleaf.com>
GitOrigin-RevId: b9bc50c914369da646ddb42ade1a02fd7baeb96b
2026-06-16 08:06:57 +00:00
Jakob AckermannandAndrew Rumble f2ebe46506 [web] add migration for back-filling db.users.labsProgram=false (#34632)
* [web] add migration for back-filling db.users.labsProgram=false

Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>

* [web] include user.labsProgram=false in session

---------

Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
GitOrigin-RevId: 0b3322007f6a36d2999d646869dc84f2c6ad4d2d
2026-06-16 08:06:43 +00:00
Jakob Ackermann cb9e834a1f [project-history] return status "423 Locked" when redis lock is taken (#34429)
GitOrigin-RevId: f40230bc98e8ed8a5358e6db5c515cb2a58f9b85
2026-06-16 08:06:38 +00:00
Jakob Ackermann b11027d58f [monorepo] add clsi-sharded mode (#34614)
* [monorepo] provide clsi-sharded dev-env setup

* [monorepo] provide sharded clsi-nginx when using clsi-sharded

* [clsi-perf] run on all instances again

* [monorepo] switch default TL to 2025.1 in clsi (same as web)

* [monorepo] avoid starting base clsi when running clsi-perf with clsi-sharded

* [clsi-lb] use 3s check interval in clsi-sharded mode

GitOrigin-RevId: 3cae8f0c1db69dbd76b94b45dc45b3abb2fa0a83
2026-06-16 08:06:34 +00:00
Andrew Rumble d22cea01ce Merge pull request #33404 from overleaf/copilot/fix-accepts-language-deprecation
Fix deprecated req.acceptsLanguage → req.acceptsLanguages in Translations.mjs

GitOrigin-RevId: db9f09d999158ba5f9f084bc1a8334954a55cd79
2026-06-16 08:06:29 +00:00
Mathias Jakobsen b7946791f4 Merge pull request #34553 from overleaf/mj-pandoc-html-mathml
[clsi] Add mathml option to HTML export

GitOrigin-RevId: c0736f0ae78020c2c554e8aaed9472f050323fd4
2026-06-16 08:06:25 +00:00
Mathias Jakobsen 481a0b1da9 Merge pull request #34479 from overleaf/mj-toolbar-border
[web] Hide toolbar border when tabs are enabled

GitOrigin-RevId: 58a33980006d1bc148db16d864fc0547d9c5d421
2026-06-16 08:06:21 +00:00
Mathias Jakobsen 7096b0f8a3 Merge pull request #34496 from overleaf/mj-history-style-cleanup
[web] Clean up history styles post-editor redesign

GitOrigin-RevId: fffdc0074000f6076984b35e3f84467e4d881c09
2026-06-16 08:06:16 +00:00
Mathias Jakobsen b85702f169 Merge pull request #34592 from overleaf/mj-full-project-search-redesign-cleanup
[web] Clean up full-project-search styles post-editor redesign

GitOrigin-RevId: a93660b48f8b088244f5d99db41752402032953f
2026-06-16 08:06:12 +00:00
Mathias Jakobsen 69ec7cdeb6 Merge pull request #34575 from overleaf/mj-file-view-cleanup
[web] Clean up file view styles post editor redesign

GitOrigin-RevId: 702200b1dc0ff6dee44e9163cf9ee3540ad1f621
2026-06-16 08:06:07 +00:00
Mathias Jakobsen b7f984fc8d [web] View menu tweaks (#34548)
* [web] Add missing translation

* [web] Group PDF zoom options in view menu

* [web] Add review panel toggle to view menu

* [web] Address review comments

* [web] Show disabled buttons to anonymous users

* [web] Add e2e tests for editing mode in view menu

GitOrigin-RevId: d13dc307f1bcb7c50c76afbaa397c3aca8b96029
2026-06-16 08:06:03 +00:00
roo hutton efd12c8feb Merge pull request #34484 from overleaf/rh-ol-stripe-id
Sync updated customer email to Stripe/Recurly

GitOrigin-RevId: b2becc50cc63739bdf56e45c37939f84c6fcd7a8
2026-06-16 08:05:58 +00:00
Lucie Germain ed166a7291 [Security upgrade] upgrade react-router to 6.30.4 in latexqc (#34324)
* Upgrade react-router to 6.30.4 (GHSA-2j2x-hqr9-3h42)

GitOrigin-RevId: 1224dc24bac65a8d2ee2d7c3f83d900a22aae5a0
2026-06-16 08:05:54 +00:00
Domagoj Kriskovic 8ccdc0fbc4 Add markdown link tooltip support to the visual editor
GitOrigin-RevId: 7ab4fd0793f057c86e4fc0307d81df376efe4d34
2026-06-16 08:05:46 +00:00
393 changed files with 24448 additions and 2564 deletions
+5
View File
@@ -4,3 +4,8 @@
# user defined files # user defined files
.env .env
docker-compose.override.yml docker-compose.override.yml
# local-only agent docs + plan notes (never track: they hold credentials/creds refs)
references/
AGENTS.md
todo.md
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/access-token-encryptor monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/access-token-encryptor
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/access-token-encryptor
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/access-token-encryptor
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
@@ -5,6 +5,5 @@ access-token-encryptor
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/access-token-encryptor
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/fetch-utils monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/fetch-utils
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/fetch-utils
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/fetch-utils
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,6 +5,5 @@ fetch-utils
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/fetch-utils
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/logger monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/logger
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/logger
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/logger
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,6 +5,5 @@ logger
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/logger
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/metrics monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/metrics
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/metrics
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/metrics
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,6 +5,5 @@ metrics
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/metrics
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/mongo-utils monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/mongo-utils
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/mongo-utils
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/mongo-utils
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,7 +5,6 @@ mongo-utils
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/mongo-utils
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
--tsconfig-no-implicit-any=True --tsconfig-no-implicit-any=True
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/o-error monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/o-error
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/o-error
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/o-error
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,6 +5,5 @@ o-error
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/o-error
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/object-persistor monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/object-persistor
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/object-persistor
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/object-persistor
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
@@ -5,7 +5,6 @@ object-persistor
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/object-persistor
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
--tsconfig-no-implicit-any=True --tsconfig-no-implicit-any=True
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/overleaf-editor-core monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/overleaf-editor-core
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/overleaf-editor-core
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/overleaf-editor-core
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
@@ -5,6 +5,5 @@ overleaf-editor-core
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=overleaf-editor-core
--pipeline-owner=44 --pipeline-owner=44
--public-repo=False --public-repo=False
@@ -56,8 +56,7 @@ class TrackedChange {
return false return false
} }
return ( return (
this.tracking.type === other.tracking.type && this.tracking.canMergeWith(other.tracking) &&
this.tracking.userId === other.tracking.userId &&
this.range.touches(other.range) && this.range.touches(other.range) &&
this.range.canMerge(other.range) this.range.canMerge(other.range)
) )
@@ -75,13 +74,7 @@ class TrackedChange {
} }
return new TrackedChange( return new TrackedChange(
this.range.merge(other.range), this.range.merge(other.range),
new TrackingProps( this.tracking.mergeWith(other.tracking)
this.tracking.type,
this.tracking.userId,
this.tracking.ts.getTime() > other.tracking.ts.getTime()
? this.tracking.ts
: other.tracking.ts
)
) )
} }
@@ -405,6 +405,49 @@ describe('TextOperation', function () {
}) })
) )
it(
'compose associativity (randomised)',
random.test(numTrials, () => {
const str = random.string(20)
const comments = random.comments(6)
const a = randomOperation(str, comments.ids)
const afterA = new StringFileData(str, comments.comments)
a.apply(afterA)
const b = randomOperation(afterA.getContent(), comments.ids)
const afterB = new StringFileData(
afterA.getContent(),
comments.comments
)
b.apply(afterB)
const c = randomOperation(afterB.getContent(), comments.ids)
const ab = a.compose(b)
const ab_c = ab.compose(c)
const bc = b.compose(c)
const a_bc = a.compose(bc)
const ab_c_file = new StringFileData(str, comments.comments)
ab_c.apply(ab_c_file)
const a_bc_file = new StringFileData(str, comments.comments)
a_bc.apply(a_bc_file)
const fuzzingError = fuzzingErrorMessage({
str,
comments,
a: a.toJSON(),
b: b.toJSON(),
c: c.toJSON(),
})
expect(ab_c_file.toRaw()).to.deep.equal(a_bc_file.toRaw(), fuzzingError)
})
)
it('composes two operations with comments', function () { it('composes two operations with comments', function () {
expect( expect(
compose( compose(
@@ -625,11 +668,54 @@ describe('TextOperation', function () {
a: a.toJSON(), a: a.toJSON(),
b: b.toJSON(), b: b.toJSON(),
}) })
expect(abPrime.equals(baPrime)).to.be.equal(true, fuzzingError) // The composition of ab' and ba' is not guaranteed to be equal, but
// should converge to the same file contents + ranges.
expect(abFile.toRaw()).to.deep.equal(baFile.toRaw(), fuzzingError) expect(abFile.toRaw()).to.deep.equal(baFile.toRaw(), fuzzingError)
}) })
) )
it('chooses lower tracked change timestamp', function () {
const ts1 = '2024-01-01T01:00:00.000Z'
const ts2 = '2024-01-01T02:00:00.000Z'
const str = 'abcde'
const comments = []
const a = new TextOperation()
.retain(2, {
tracking: TrackingProps.fromRaw({
ts: ts1,
type: 'insert',
userId: 'user1',
}),
})
.retain(1)
.retain(2, {
tracking: TrackingProps.fromRaw({
ts: ts2,
type: 'insert',
userId: 'user1',
}),
})
const b = new TextOperation().retain(1).remove(3).retain(1)
const [aPrime, bPrime] = TextOperation.transform(a, b)
const aComposeBPrime = a.compose(bPrime)
const bComposeAPrime = b.compose(aPrime)
const aBPFile = new StringFileData(str, comments)
aComposeBPrime.apply(aBPFile)
const bAPFile = new StringFileData(str, comments)
bComposeAPrime.apply(bAPFile)
expect(aBPFile.toRaw()).to.deep.equal(bAPFile.toRaw())
expect(aBPFile.trackedChanges.length).to.equal(1)
expect(
aBPFile.trackedChanges.asSorted()[0].tracking.ts.toISOString()
).to.equal(ts1)
})
it('adds a tracked change from operation 1', function () { it('adds a tracked change from operation 1', function () {
expect( expect(
transform( transform(
@@ -8,7 +8,7 @@ const { expect } = require('chai')
describe('TrackedChangeList', function () { describe('TrackedChangeList', function () {
describe('applyInsert', function () { describe('applyInsert', function () {
describe('with same author', function () { describe('with same author', function () {
it('should merge consecutive tracked changes and use the latest timestamp', function () { it('should merge consecutive tracked changes and use the earliest timestamp', function () {
const trackedChanges = TrackedChangeList.fromRaw([ const trackedChanges = TrackedChangeList.fromRaw([
{ {
range: { pos: 0, length: 3 }, range: { pos: 0, length: 3 },
@@ -33,7 +33,7 @@ describe('TrackedChangeList', function () {
tracking: { tracking: {
type: 'insert', type: 'insert',
userId: 'user1', userId: 'user1',
ts: '2024-01-01T00:00:00.000Z', ts: '2023-01-01T00:00:00.000Z',
}, },
}, },
]) ])
@@ -699,7 +699,7 @@ describe('TrackedChangeList', function () {
tracking: { tracking: {
type: 'insert', type: 'insert',
userId: 'user1', userId: 'user1',
ts: '2024-01-01T00:00:00.000Z', ts: '2023-01-01T00:00:00.000Z',
}, },
}, },
]) ])
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/promise-utils monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/promise-utils
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/promise-utils
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/promise-utils
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,6 +5,5 @@ promise-utils
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/promise-utils
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/ranges-tracker monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/ranges-tracker
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/ranges-tracker
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/ranges-tracker
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,6 +5,5 @@ ranges-tracker
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/ranges-tracker
--pipeline-owner=44 --pipeline-owner=44
--public-repo=False --public-repo=False
+16 -1
View File
@@ -420,7 +420,7 @@ class RangesTracker {
offset = opStart - changeStart offset = opStart - changeStart
change.op.i = change.op.i =
change.op.i.slice(0, offset) + op.i + change.op.i.slice(offset) change.op.i.slice(0, offset) + op.i + change.op.i.slice(offset)
change.metadata.ts = metadata.ts change.metadata.ts = pickTimestamp(change.metadata, metadata)
alreadyMerged = true alreadyMerged = true
movedChanges.push(change) movedChanges.push(change)
} else if (opStart <= changeStart) { } else if (opStart <= changeStart) {
@@ -728,6 +728,10 @@ class RangesTracker {
) { ) {
removeChanges.push(change) removeChanges.push(change)
previousChange.op.i += change.op.i previousChange.op.i += change.op.i
previousChange.metadata.ts = pickTimestamp(
previousChange.metadata,
change.metadata
)
movedChanges.push(previousChange) movedChanges.push(previousChange)
} }
} else if ( } else if (
@@ -790,4 +794,15 @@ class RangesTracker {
} }
} }
function pickTimestamp(oldMetadata, newMetadata) {
// Make sure null values don't get treated as 1970-01-01 dates in the
// comparison below.
if (oldMetadata.ts == null) return newMetadata.ts
if (newMetadata.ts == null) return oldMetadata.ts
return new Date(oldMetadata.ts) < new Date(newMetadata.ts)
? oldMetadata.ts
: newMetadata.ts
}
module.exports = RangesTracker module.exports = RangesTracker
@@ -222,4 +222,114 @@ describe('RangesTracker', function () {
) )
}) })
}) })
describe('merging tracked inserts from the same user', function () {
beforeEach(function () {
this.comments = []
})
it('keeps the earlier timestamp when the existing insert is older', function () {
this.rangesTracker = new RangesTracker(
[
{
id: 'id1',
op: { p: 10, i: 'foo' },
metadata: { user_id: 'user-1', ts: '2024-01-01T00:00:00.000Z' },
},
],
this.comments
)
this.rangesTracker.track_changes = true
this.rangesTracker.applyOp(
{ p: 13, i: 'bar' },
{ user_id: 'user-1', ts: new Date('2024-06-01T00:00:00.000Z') }
)
expect(this.rangesTracker.changes).to.have.length(1)
const [change] = this.rangesTracker.changes
expect(change.op).to.deep.equal({ p: 10, i: 'foobar' })
expect(change.metadata.user_id).to.equal('user-1')
expect(new Date(change.metadata.ts).toISOString()).to.equal(
'2024-01-01T00:00:00.000Z'
)
})
it('keeps the earlier timestamp when the incoming insert is older', function () {
this.rangesTracker = new RangesTracker(
[
{
id: 'id1',
op: { p: 10, i: 'foo' },
metadata: { user_id: 'user-1', ts: '2024-06-01T00:00:00.000Z' },
},
],
this.comments
)
this.rangesTracker.track_changes = true
this.rangesTracker.applyOp(
{ p: 13, i: 'bar' },
{ user_id: 'user-1', ts: new Date('2024-01-01T00:00:00.000Z') }
)
expect(this.rangesTracker.changes).to.have.length(1)
const [change] = this.rangesTracker.changes
expect(change.op).to.deep.equal({ p: 10, i: 'foobar' })
expect(change.metadata.user_id).to.equal('user-1')
expect(new Date(change.metadata.ts).toISOString()).to.equal(
'2024-01-01T00:00:00.000Z'
)
})
it('uses the defined timestamp when the existing insert has a null timestamp', function () {
this.rangesTracker = new RangesTracker(
[
{
id: 'id1',
op: { p: 10, i: 'foo' },
metadata: { user_id: 'user-1', ts: null },
},
],
this.comments
)
this.rangesTracker.track_changes = true
this.rangesTracker.applyOp(
{ p: 13, i: 'bar' },
{ user_id: 'user-1', ts: new Date('2024-01-01T00:00:00.000Z') }
)
expect(this.rangesTracker.changes).to.have.length(1)
const [change] = this.rangesTracker.changes
expect(new Date(change.metadata.ts).toISOString()).to.equal(
'2024-01-01T00:00:00.000Z'
)
})
it('keeps the earlier timestamp when re-merging inserts after a deletion', function () {
this.rangesTracker = new RangesTracker(
[
{
id: 'id1',
op: { p: 10, i: 'aaa' },
metadata: { user_id: 'user-1', ts: '2024-03-01T00:00:00.000Z' },
},
{
id: 'id2',
op: { p: 13, i: 'bb' },
metadata: { user_id: 'user-2', ts: '2024-02-01T00:00:00.000Z' },
},
{
id: 'id3',
op: { p: 15, i: 'ccc' },
metadata: { user_id: 'user-1', ts: '2024-01-01T00:00:00.000Z' },
},
],
this.comments
)
this.rangesTracker.applyOp({ p: 13, d: 'bb' })
expect(this.rangesTracker.changes).to.have.length(1)
const [change] = this.rangesTracker.changes
expect(change.op).to.deep.equal({ p: 10, i: 'aaaccc' })
expect(change.metadata.user_id).to.equal('user-1')
expect(new Date(change.metadata.ts).toISOString()).to.equal(
'2024-01-01T00:00:00.000Z'
)
})
})
}) })
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/redis-wrapper monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/redis-wrapper
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/redis-wrapper
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/redis-wrapper
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,6 +5,5 @@ redis-wrapper
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/redis-wrapper
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/settings monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/settings
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/settings
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/settings
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,6 +5,5 @@ settings
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/settings
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/stream-utils monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/stream-utils
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/stream-utils
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/stream-utils
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
-1
View File
@@ -5,6 +5,5 @@ stream-utils
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/stream-utils
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
+59
View File
@@ -0,0 +1,59 @@
# Run the test commands in the scope of the monorepo.
# Eslint and prettier (plus some configs) are on the root.
RUN_LINTING = ../../bin/run -w /overleaf/libraries/validation-tools monorepo yarn run --silent
RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
WITH_CACHE_FOLDER = \
lint \
lint_ci \
lint_fix \
format \
format_ci \
format_fix \
typecheck \
typecheck_ci \
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
../../node_modules/.cache/prettier/:
mkdir -p $@
$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
../../node_modules/.cache/eslint/:
mkdir -p $@
format:
$(RUN_LINTING_MONOREPO) format libraries/validation-tools
format_ci:
$(RUN_LINTING_MONOREPO) format libraries/validation-tools
format_fix:
$(RUN_LINTING_MONOREPO) format:fix libraries/validation-tools
lint:
$(RUN_LINTING) lint
lint_ci:
-$(RUN_LINTING) lint --format json --output-file reports/eslint.json
sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
lint_fix:
$(RUN_LINTING) lint:fix
typecheck:
$(RUN_LINTING) types:check
typecheck_ci:
$(RUN_LINTING) types:check
test: test_unit
test_unit:
ifneq (,$(wildcard test/unit))
$(RUN_LINTING) test:unit
endif
test: test_acceptance
test_acceptance:
ifneq (,$(wildcard test/acceptance))
$(RUN_LINTING) test:acceptance
endif
@@ -5,7 +5,6 @@ validation-tools
--esmock-loader=False --esmock-loader=False
--is-library=True --is-library=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/validation-tools
--public-repo=False --public-repo=False
--test-acceptance-vitest=True --test-acceptance-vitest=True
--test-unit-vitest=True --test-unit-vitest=True
+8 -5
View File
@@ -41,12 +41,13 @@
"sandboxed-module": "patch:sandboxed-module@npm%3A2.0.4#~/.yarn/patches/sandboxed-module-npm-2.0.4-f8b45aacc9.patch", "sandboxed-module": "patch:sandboxed-module@npm%3A2.0.4#~/.yarn/patches/sandboxed-module-npm-2.0.4-f8b45aacc9.patch",
"request/tough-cookie": "5.1.2", "request/tough-cookie": "5.1.2",
"request/form-data": "2.5.5", "request/form-data": "2.5.5",
"request/qs": "6.14.1", "request/qs": "6.15.2",
"@cypress/request/qs": "6.14.1", "@cypress/request/qs": "6.15.2",
"qs": "6.15.2",
"@opentelemetry/api": "1.9.0", "@opentelemetry/api": "1.9.0",
"mocha/serialize-javascript": "7.0.5", "mocha/serialize-javascript": "7.0.5",
"pprof/protobufjs": "7.5.5", "protobufjs": "7.6.3",
"@google-cloud/profiler/protobufjs": "7.5.5", "@protobufjs/utf8": "1.1.1",
"mocha-multi-reporters": "patch:mocha-multi-reporters@npm%3A1.5.1#~/.yarn/patches/mocha-multi-reporters-npm-1.5.1-0a1088aed5.patch", "mocha-multi-reporters": "patch:mocha-multi-reporters@npm%3A1.5.1#~/.yarn/patches/mocha-multi-reporters-npm-1.5.1-0a1088aed5.patch",
"pdfjs-dist": "patch:pdfjs-dist@npm%3A5.1.91#~/.yarn/patches/pdfjs-dist-npm-5.1.91.patch", "pdfjs-dist": "patch:pdfjs-dist@npm%3A5.1.91#~/.yarn/patches/pdfjs-dist-npm-5.1.91.patch",
"referer-parser": "patch:referer-parser@npm%3A0.0.3#~/.yarn/patches/referer-parser-npm-0.0.3.patch", "referer-parser": "patch:referer-parser@npm%3A0.0.3#~/.yarn/patches/referer-parser-npm-0.0.3.patch",
@@ -101,11 +102,12 @@
"cluster-key-slot": "1.1.0", "cluster-key-slot": "1.1.0",
"@octokit/request": "9.2.2", "@octokit/request": "9.2.2",
"randomstring": "1.2.2", "randomstring": "1.2.2",
"vite": "7.3.1", "vite": "7.3.5",
"isomorphic-git": "1.33.1", "isomorphic-git": "1.33.1",
"http-status": "1.5.0", "http-status": "1.5.0",
"knex": "2.4.0", "knex": "2.4.0",
"utf-8-validate": "5.0.8", "utf-8-validate": "5.0.8",
"ws@npm:^8": "8.21.0",
"samlp": "7.0.2", "samlp": "7.0.2",
"compression": "1.7.4", "compression": "1.7.4",
"cookie-parser": "1.4.6", "cookie-parser": "1.4.6",
@@ -198,6 +200,7 @@
}, },
"workspaces": [ "workspaces": [
"jobs/mirror-documentation", "jobs/mirror-documentation",
"jobs/wiki-algolia",
"libraries/*", "libraries/*",
"server-ce/test", "server-ce/test",
"services/analytics", "services/analytics",
+10
View File
@@ -8,3 +8,13 @@ server-ce/test
services/web/scripts/plan-prices services/web/scripts/plan-prices
services/web/scripts/translations services/web/scripts/translations
services/web/scripts/ukamf services/web/scripts/ukamf
# dev-environment node_modules symlinks must never enter the image:
# BuildKit cannot replace a directory already present in the base/cache
# layer (overleaf/services/web/node_modules) with a file/symlink
node_modules
services/web/node_modules
libraries/*/node_modules
services/*/node_modules
services/web/modules/*/node_modules
tools/migrations/node_modules
+120
View File
@@ -2,6 +2,8 @@ import {
createNewFile, createNewFile,
createProjectAndOpenInNewEditor, createProjectAndOpenInNewEditor,
openProjectById, openProjectById,
openProjectByName,
shareProjectByEmailAndAcceptInviteViaDash,
testNewFileUpload, testNewFileUpload,
} from './helpers/project' } from './helpers/project'
import { isExcludedBySharding, startWith } from './helpers/config' import { isExcludedBySharding, startWith } from './helpers/config'
@@ -321,6 +323,124 @@ describe('editor', function () {
}) })
}) })
describe('editing mode', function () {
if (isExcludedBySharding('PRO_DEFAULT_1')) return
startWith({ pro: true })
const OWNER = USER
const EDITOR = COLLABORATOR
const REVIEWER = 'reviewer@example.com'
const VIEWER = 'viewer@example.com'
ensureUserExists({ email: OWNER })
ensureUserExists({ email: EDITOR })
ensureUserExists({ email: REVIEWER })
ensureUserExists({ email: VIEWER })
let projectName: string
function openEditingModeMenu() {
cy.findByRole('button', { name: 'View' }).click()
cy.findByRole('menuitem', { name: 'Editing mode' }).click()
}
beforeWithReRunOnTestRetry(() => {
projectName = `project-${uuid()}`
login(OWNER)
createProjectAndOpenInNewEditor(projectName, { type: 'Example project' })
shareProjectByEmailAndAcceptInviteViaDash(
projectName,
EDITOR,
'Editor',
true
)
login(OWNER)
shareProjectByEmailAndAcceptInviteViaDash(
projectName,
REVIEWER,
'Reviewer',
true
)
login(OWNER)
shareProjectByEmailAndAcceptInviteViaDash(
projectName,
VIEWER,
'Viewer',
true
)
})
it('lets an editor switch between editing and reviewing', function () {
login(EDITOR)
openProjectByName(projectName, true)
openEditingModeMenu()
cy.findByRole('menuitem', { name: 'Editing' })
.should('have.attr', 'aria-selected', 'true')
.and('not.have.attr', 'aria-disabled')
cy.findByRole('menuitem', { name: 'Reviewing' })
.should('have.attr', 'aria-selected', 'false')
.and('not.have.attr', 'aria-disabled')
cy.findByRole('menuitem', { name: 'Viewing' }).should('not.exist')
cy.findByRole('menuitem', { name: 'Reviewing' }).click()
cy.findByRole('button', { name: 'Reviewing' }).should('exist')
openEditingModeMenu()
cy.findByRole('menuitem', { name: 'Reviewing' }).should(
'have.attr',
'aria-selected',
'true'
)
cy.findByRole('menuitem', { name: 'Editing' }).should(
'have.attr',
'aria-selected',
'false'
)
cy.findByRole('menuitem', { name: 'Editing' }).click()
cy.findByRole('button', { name: 'Editing' }).should('exist')
})
it('shows a reviewer in reviewing mode with editing disabled', function () {
login(REVIEWER)
openProjectByName(projectName, true)
openEditingModeMenu()
cy.findByRole('menuitem', { name: 'Reviewing' })
.should('have.attr', 'aria-selected', 'true')
.and('not.have.attr', 'aria-disabled')
cy.findByRole('menuitem', { name: 'Editing' }).should(
'have.attr',
'aria-disabled',
'true'
)
cy.findByRole('menuitem', { name: 'Viewing' }).should('not.exist')
})
it('shows a viewer in viewing mode with editing and reviewing disabled', function () {
login(VIEWER)
openProjectByName(projectName, true)
openEditingModeMenu()
cy.findByRole('menuitem', { name: 'Viewing' }).should(
'have.attr',
'aria-selected',
'true'
)
cy.findByRole('menuitem', { name: 'Editing' }).should(
'have.attr',
'aria-disabled',
'true'
)
cy.findByRole('menuitem', { name: 'Reviewing' }).should(
'have.attr',
'aria-disabled',
'true'
)
})
})
function createRandomLetterString() { function createRandomLetterString() {
const chars = 'abcdefghijklmnopqrstuvwxyz' const chars = 'abcdefghijklmnopqrstuvwxyz'
let result = '' let result = ''
+6 -1
View File
@@ -256,7 +256,12 @@ describe('filestore migration', function () {
}) })
} else { } else {
// 5.x // 5.x
startWith({ version: '5.5.5', pro: true, withDataDir: true }) startWith({
version: '5.5.5',
pro: true,
withDataDir: true,
resetData: true,
})
defaultImage = 'frog.jpg' defaultImage = 'frog.jpg'
ensureUserExists({ email }) ensureUserExists({ email })
before(function () { before(function () {
+2 -2
View File
@@ -206,7 +206,7 @@ export function openProjectViaInviteNotification(
function shareProjectByEmail( function shareProjectByEmail(
projectName: string, projectName: string,
email: string, email: string,
level: 'Viewer' | 'Editor', level: 'Viewer' | 'Editor' | 'Reviewer',
newEditor: boolean = false newEditor: boolean = false
) { ) {
openProjectByName(projectName, newEditor) openProjectByName(projectName, newEditor)
@@ -231,7 +231,7 @@ function shareProjectByEmail(
export function shareProjectByEmailAndAcceptInviteViaDash( export function shareProjectByEmailAndAcceptInviteViaDash(
projectName: string, projectName: string,
email: string, email: string,
level: 'Viewer' | 'Editor', level: 'Viewer' | 'Editor' | 'Reviewer',
newEditor: boolean = false newEditor: boolean = false
) { ) {
shareProjectByEmail(projectName, email, level, newEditor) shareProjectByEmail(projectName, email, level, newEditor)
+1 -1
View File
@@ -21,8 +21,8 @@ import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry
describe('Project Sharing', function () { describe('Project Sharing', function () {
if (isExcludedBySharding('PRO_CUSTOM_4')) return if (isExcludedBySharding('PRO_CUSTOM_4')) return
startWith({ withDataDir: true, pro: true, resetData: true })
ensureUserExists({ email: 'user@example.com' }) ensureUserExists({ email: 'user@example.com' })
startWith({ withDataDir: true, pro: true })
let projectName: string let projectName: string
let recompile: () => void let recompile: () => void
+6 -9
View File
@@ -2,10 +2,10 @@
# Instead run bin/update_build_scripts from # Instead run bin/update_build_scripts from
# https://github.com/overleaf/internal/ # https://github.com/overleaf/internal/
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
PROJECT_NAME = chat export PROJECT_NAME = chat
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd) HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd) export MONOREPO ?= $(shell cd ../../ && pwd)
@@ -28,13 +28,10 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/tools/migrations/package.json \ $(MONOREPO)/tools/migrations/package.json \
$(MONOREPO)/.yarn/patches/* \ $(MONOREPO)/.yarn/patches/* \
| sha256sum | cut -d '-' -f1) | sha256sum | cut -d '-' -f1)
export MOCHA_GREP ?=
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
-1
View File
@@ -5,6 +5,5 @@ chat
--env-pass-through= --env-pass-through=
--esmock-loader=False --esmock-loader=False
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/chat
--pipeline-owner=44 --pipeline-owner=44
--public-repo=False --public-repo=False
+3 -3
View File
@@ -4,7 +4,7 @@
services: services:
test_unit: test_unit:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
user: node user: node
volumes: volumes:
- ./reports:/overleaf/services/chat/reports - ./reports:/overleaf/services/chat/reports
@@ -21,7 +21,7 @@ services:
condition: service_started condition: service_started
test_acceptance: test_acceptance:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
environment: environment:
CI: CI:
RETRIES: RETRIES:
@@ -42,7 +42,7 @@ services:
tar: tar:
build: . build: .
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./:/tmp/build/ - ./:/tmp/build/
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
@@ -5,6 +5,6 @@ before('run migrations', async function () {
this.timeout(60_000) this.timeout(60_000)
await promisify(exec)( await promisify(exec)(
`cd ../../tools/migrations && npm run migrations -- migrate -t server-ce` `cd ../../tools/migrations && yarn run migrations migrate -t 'server-ce & !nonblocking'`
) )
}) })
+2 -1
View File
@@ -1,4 +1,5 @@
compiles compiles
output output
cache cache
uploads uploads
sharded
+6 -9
View File
@@ -2,10 +2,10 @@
# Instead run bin/update_build_scripts from # Instead run bin/update_build_scripts from
# https://github.com/overleaf/internal/ # https://github.com/overleaf/internal/
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
PROJECT_NAME = clsi export PROJECT_NAME = clsi
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd) HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd) export MONOREPO ?= $(shell cd ../../ && pwd)
@@ -29,13 +29,10 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/services/clsi/package.json \ $(MONOREPO)/services/clsi/package.json \
$(MONOREPO)/.yarn/patches/* \ $(MONOREPO)/.yarn/patches/* \
| sha256sum | cut -d '-' -f1) | sha256sum | cut -d '-' -f1)
export MOCHA_GREP ?=
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
+5
View File
@@ -192,6 +192,11 @@ async function doCompile(request, stats, timings) {
} }
} }
// Pass through checkpoint setting
if (request.enableCheckpoint) {
env.ENABLE_CHECKPOINT = '1'
}
const compileStart = Date.now() const compileStart = Date.now()
const compileName = getCompileName(request.project_id, request.user_id) const compileName = getCompileName(request.project_id, request.user_id)
@@ -173,6 +173,7 @@ const LATEX_EXPORT_CONFIGS = {
'--to', '--to',
'html', 'html',
'--standalone', '--standalone',
'--mathml',
], ],
}, },
} }
+5
View File
@@ -65,6 +65,11 @@ function parse(body, callback) {
type: 'number', type: 'number',
} }
) )
response.enableCheckpoint = _parseAttribute(
'enableCheckpoint',
compile.options.enableCheckpoint,
{ default: false, type: 'boolean' }
)
response.timeout = _parseAttribute('timeout', compile.options.timeout, { response.timeout = _parseAttribute('timeout', compile.options.timeout, {
default: MAX_TIMEOUT, default: MAX_TIMEOUT,
type: 'number', type: 'number',
+10
View File
@@ -23,9 +23,15 @@ import Metrics from '@overleaf/metrics'
import logger from '@overleaf/logger' import logger from '@overleaf/logger'
import settings from '@overleaf/settings' import settings from '@overleaf/settings'
import ClsiMetrics from './Metrics.js' import ClsiMetrics from './Metrics.js'
import { Minimatch } from 'minimatch'
const { shouldSkipMetrics } = ClsiMetrics const { shouldSkipMetrics } = ClsiMetrics
// Additional file patterns to keep between compiles
const preciousFileMatcher = new Minimatch(settings.preciousFilePattern, {
dot: true,
})
let ResourceWriter let ResourceWriter
const parallelFileDownloads = settings.parallelFileDownloads || 1 const parallelFileDownloads = settings.parallelFileDownloads || 1
@@ -265,6 +271,10 @@ export default ResourceWriter = {
// Epstopdf generated files // Epstopdf generated files
shouldDelete = false shouldDelete = false
} }
// Keep additional precious files and directories
if (shouldDelete && preciousFileMatcher.match(path)) {
shouldDelete = false
}
if ( if (
path === 'output.tar.gz' || path === 'output.tar.gz' ||
path === 'output.synctex.gz' || path === 'output.synctex.gz' ||
-1
View File
@@ -5,7 +5,6 @@ clsi
--env-pass-through= --env-pass-through=
--esmock-loader=False --esmock-loader=False
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/clsi
--pipeline-owner=32 --pipeline-owner=32
--public-repo=True --public-repo=True
--test-unit-vitest=True --test-unit-vitest=True
@@ -35,6 +35,7 @@ module.exports = {
process.env.PDFTOCAIRO_IMAGE || 'quay.io/sharelatex/pdftocairo:24.02', process.env.PDFTOCAIRO_IMAGE || 'quay.io/sharelatex/pdftocairo:24.02',
enablePdfConversions: process.env.ENABLE_PDF_CONVERSIONS === 'true', enablePdfConversions: process.env.ENABLE_PDF_CONVERSIONS === 'true',
maxUploadSize: 50 * 1024 * 1024, maxUploadSize: 50 * 1024 * 1024,
preciousFilePattern: process.env.PRECIOUS_FILE_PATTERN || '',
internal: { internal: {
clsi: { clsi: {
@@ -202,6 +203,16 @@ if ((process.env.DOCKER_RUNNER || process.env.SANDBOXED_COMPILES) === 'true') {
} }
} }
if (process.env.NEW_APPARMOR_PROFILE) {
try {
module.exports.clsi.docker.new_apparmor_profile =
process.env.NEW_APPARMOR_PROFILE
} catch (error) {
console.error(error, 'could not apply new apparmor profile setting')
process.exit(1)
}
}
if (process.env.ALLOWED_IMAGES) { if (process.env.ALLOWED_IMAGES) {
try { try {
module.exports.clsi.docker.allowedImages = module.exports.clsi.docker.allowedImages =
+3 -3
View File
@@ -4,7 +4,7 @@
services: services:
test_unit: test_unit:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./reports:/overleaf/services/clsi/reports - ./reports:/overleaf/services/clsi/reports
- ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
@@ -17,7 +17,7 @@ services:
VITEST_NO_CACHE: true VITEST_NO_CACHE: true
test_acceptance: test_acceptance:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
environment: environment:
CI: CI:
RETRIES: RETRIES:
@@ -51,7 +51,7 @@ services:
tar: tar:
build: . build: .
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./:/tmp/build/ - ./:/tmp/build/
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
+1
View File
@@ -31,6 +31,7 @@
"dockerode": "^4.0.9", "dockerode": "^4.0.9",
"express": "4.22.1", "express": "4.22.1",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"minimatch": "^10.2.5",
"multer": "2.1.1", "multer": "2.1.1",
"overleaf-editor-core": "workspace:*", "overleaf-editor-core": "workspace:*",
"p-limit": "^3.1.0", "p-limit": "^3.1.0",
@@ -91,6 +91,7 @@ const LATEX_TO_DOCUMENT_CASES = [
'--to', '--to',
'html', 'html',
'--standalone', '--standalone',
'--mathml',
'--resource-path=..', '--resource-path=..',
'--extract-media=.', '--extract-media=.',
], ],
@@ -19,6 +19,12 @@ describe('ResourceWriter', () => {
}), }),
})) }))
vi.doMock('@overleaf/settings', () => ({
default: (ctx.settings = {
preciousFilePattern: '{keepdir/**,.other/keepdir/**}',
}),
}))
vi.doMock('../../../app/js/ResourceStateManager', () => ({ vi.doMock('../../../app/js/ResourceStateManager', () => ({
default: (ctx.ResourceStateManager = {}), default: (ctx.ResourceStateManager = {}),
})) }))
@@ -246,6 +252,12 @@ describe('ResourceWriter', () => {
{ {
path: 'output.stderr', path: 'output.stderr',
}, },
{
path: 'keepdir/file',
},
{
path: '.other/keepdir/.subdir/file',
},
] ]
ctx.resources = 'mock-resources' ctx.resources = 'mock-resources'
ctx.request = { ctx.request = {
@@ -297,6 +309,18 @@ describe('ResourceWriter', () => {
.should.equal(true) .should.equal(true)
}) })
it('should not delete additionally specified files', ctx => {
return ctx.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(ctx.basePath, 'keepdir/file'))
.should.equal(false)
})
it('should not delete additionally specified hidden files', ctx => {
return ctx.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(ctx.basePath, '.other/keepdir/.subdir/file'))
.should.equal(false)
})
it('should not delete the extra aux files', ctx => { it('should not delete the extra aux files', ctx => {
return ctx.ResourceWriter._deleteFileIfNotDirectory return ctx.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(ctx.basePath, 'extra.aux')) .calledWith(path.join(ctx.basePath, 'extra.aux'))
+2
View File
@@ -29,6 +29,7 @@ COPY libraries/object-persistor/package.json /overleaf/libraries/object-persisto
COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
COPY libraries/settings/package.json /overleaf/libraries/settings/package.json COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
COPY libraries/stream-utils/package.json /overleaf/libraries/stream-utils/package.json COPY libraries/stream-utils/package.json /overleaf/libraries/stream-utils/package.json
COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
COPY services/docstore/package.json /overleaf/services/docstore/package.json COPY services/docstore/package.json /overleaf/services/docstore/package.json
COPY tools/migrations/package.json /overleaf/tools/migrations/package.json COPY tools/migrations/package.json /overleaf/tools/migrations/package.json
COPY .yarn/patches/ /overleaf/.yarn/patches/ COPY .yarn/patches/ /overleaf/.yarn/patches/
@@ -45,6 +46,7 @@ COPY libraries/object-persistor/ /overleaf/libraries/object-persistor/
COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/ COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
COPY libraries/settings/ /overleaf/libraries/settings/ COPY libraries/settings/ /overleaf/libraries/settings/
COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/ COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/
COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
COPY services/docstore/ /overleaf/services/docstore/ COPY services/docstore/ /overleaf/services/docstore/
COPY tools/migrations/ /overleaf/tools/migrations/ COPY tools/migrations/ /overleaf/tools/migrations/
+7 -9
View File
@@ -2,10 +2,10 @@
# Instead run bin/update_build_scripts from # Instead run bin/update_build_scripts from
# https://github.com/overleaf/internal/ # https://github.com/overleaf/internal/
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
PROJECT_NAME = docstore export PROJECT_NAME = docstore
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd) HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd) export MONOREPO ?= $(shell cd ../../ && pwd)
@@ -26,17 +26,15 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/libraries/promise-utils/package.json \ $(MONOREPO)/libraries/promise-utils/package.json \
$(MONOREPO)/libraries/settings/package.json \ $(MONOREPO)/libraries/settings/package.json \
$(MONOREPO)/libraries/stream-utils/package.json \ $(MONOREPO)/libraries/stream-utils/package.json \
$(MONOREPO)/libraries/validation-tools/package.json \
$(MONOREPO)/services/docstore/package.json \ $(MONOREPO)/services/docstore/package.json \
$(MONOREPO)/tools/migrations/package.json \ $(MONOREPO)/tools/migrations/package.json \
$(MONOREPO)/.yarn/patches/* \ $(MONOREPO)/.yarn/patches/* \
| sha256sum | cut -d '-' -f1) | sha256sum | cut -d '-' -f1)
export MOCHA_GREP ?=
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
+2 -13
View File
@@ -7,11 +7,7 @@ import Settings from '@overleaf/settings'
import logger from '@overleaf/logger' import logger from '@overleaf/logger'
import express from 'express' import express from 'express'
import bodyParser from 'body-parser' import bodyParser from 'body-parser'
import { import { handleValidationError } from '@overleaf/validation-tools'
celebrate as validate,
Joi,
errors as handleValidationErrors,
} from 'celebrate'
import mongodb from './app/js/mongodb.js' import mongodb from './app/js/mongodb.js'
import Errors from './app/js/Errors.js' import Errors from './app/js/Errors.js'
import HttpController from './app/js/HttpController.js' import HttpController from './app/js/HttpController.js'
@@ -79,13 +75,6 @@ app.post(
app.patch( app.patch(
'/project/:project_id/doc/:doc_id', '/project/:project_id/doc/:doc_id',
bodyParser.json(), bodyParser.json(),
validate({
body: {
deleted: Joi.boolean(),
name: Joi.string().when('deleted', { is: true, then: Joi.required() }),
deletedAt: Joi.date().when('deleted', { is: true, then: Joi.required() }),
},
}),
HttpController.patchDoc HttpController.patchDoc
) )
app.delete('/project/:project_id/doc/:doc_id', (req, res) => { app.delete('/project/:project_id/doc/:doc_id', (req, res) => {
@@ -101,7 +90,7 @@ app.get('/health_check', HttpController.healthCheck)
app.get('/status', (req, res) => res.send('docstore is alive')) app.get('/status', (req, res) => res.send('docstore is alive'))
app.use(handleValidationErrors()) app.use(handleValidationError)
app.use(function (error, req, res, next) { app.use(function (error, req, res, next) {
if (error instanceof Errors.NotFoundError) { if (error instanceof Errors.NotFoundError) {
logger.warn({ req }, 'not found') logger.warn({ req }, 'not found')
+12 -10
View File
@@ -5,6 +5,17 @@ import HealthChecker from './HealthChecker.js'
import Errors from './Errors.js' import Errors from './Errors.js'
import Settings from '@overleaf/settings' import Settings from '@overleaf/settings'
import { expressify } from '@overleaf/promise-utils' import { expressify } from '@overleaf/promise-utils'
import { parseReq, z } from '@overleaf/validation-tools'
const patchDocSchema = z.object({
body: z
.object({
deleted: z.literal(true),
deletedAt: z.coerce.date(),
name: z.string(),
})
.strict(),
})
async function getDoc(req, res) { async function getDoc(req, res) {
const { doc_id: docId, project_id: projectId } = req.params const { doc_id: docId, project_id: projectId } = req.params
@@ -182,18 +193,9 @@ async function updateDoc(req, res) {
} }
async function patchDoc(req, res) { async function patchDoc(req, res) {
const { body: meta } = parseReq(req, patchDocSchema)
const { doc_id: docId, project_id: projectId } = req.params const { doc_id: docId, project_id: projectId } = req.params
logger.debug({ projectId, docId }, 'patching doc') logger.debug({ projectId, docId }, 'patching doc')
const allowedFields = ['deleted', 'deletedAt', 'name']
const meta = {}
Object.entries(req.body).forEach(([field, value]) => {
if (allowedFields.includes(field)) {
meta[field] = value
} else {
logger.fatal({ field }, 'joi validation for pathDoc is broken')
}
})
await DocManager.patchDoc(projectId, docId, meta) await DocManager.patchDoc(projectId, docId, meta)
res.sendStatus(204) res.sendStatus(204)
} }
-1
View File
@@ -5,7 +5,6 @@ docstore
--env-pass-through= --env-pass-through=
--esmock-loader=False --esmock-loader=False
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/docstore
--pipeline-owner=32 --pipeline-owner=32
--public-repo=True --public-repo=True
--test-unit-vitest=True --test-unit-vitest=True
+3 -3
View File
@@ -4,7 +4,7 @@
services: services:
test_unit: test_unit:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
user: node user: node
volumes: volumes:
- ./reports:/overleaf/services/docstore/reports - ./reports:/overleaf/services/docstore/reports
@@ -23,7 +23,7 @@ services:
condition: service_started condition: service_started
test_acceptance: test_acceptance:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
environment: environment:
CI: CI:
RETRIES: RETRIES:
@@ -49,7 +49,7 @@ services:
tar: tar:
build: . build: .
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./:/tmp/build/ - ./:/tmp/build/
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
+1 -1
View File
@@ -25,10 +25,10 @@
"@overleaf/promise-utils": "workspace:*", "@overleaf/promise-utils": "workspace:*",
"@overleaf/settings": "workspace:*", "@overleaf/settings": "workspace:*",
"@overleaf/stream-utils": "^0.1.0", "@overleaf/stream-utils": "^0.1.0",
"@overleaf/validation-tools": "workspace:*",
"async": "^3.2.5", "async": "^3.2.5",
"body-parser": "1.20.4", "body-parser": "1.20.4",
"bunyan": "^1.8.15", "bunyan": "^1.8.15",
"celebrate": "^15.0.3",
"express": "4.22.1", "express": "4.22.1",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"mongodb-legacy": "6.1.3", "mongodb-legacy": "6.1.3",
@@ -5,6 +5,6 @@ before('run migrations', async function () {
this.timeout(60_000) this.timeout(60_000)
await promisify(exec)( await promisify(exec)(
`cd ../../tools/migrations && npm run migrations -- migrate -t server-ce` `cd ../../tools/migrations && yarn run migrations migrate -t 'server-ce & !nonblocking'`
) )
}) })
@@ -486,7 +486,11 @@ describe('HttpController', () => {
project_id: ctx.projectId, project_id: ctx.projectId,
doc_id: ctx.docId, doc_id: ctx.docId,
} }
ctx.req.body = { name: 'foo.tex' } ctx.req.body = {
deleted: true,
deletedAt: '2026-06-15T00:00:00Z',
name: 'foo.tex',
}
ctx.DocManager.patchDoc = sinon.stub().resolves() ctx.DocManager.patchDoc = sinon.stub().resolves()
await ctx.HttpController.patchDoc(ctx.req, ctx.res, ctx.next) await ctx.HttpController.patchDoc(ctx.req, ctx.res, ctx.next)
}) })
@@ -494,7 +498,12 @@ describe('HttpController', () => {
it('should delete the document', ctx => { it('should delete the document', ctx => {
expect(ctx.DocManager.patchDoc).to.have.been.calledWith( expect(ctx.DocManager.patchDoc).to.have.been.calledWith(
ctx.projectId, ctx.projectId,
ctx.docId ctx.docId,
{
deleted: true,
deletedAt: new Date('2026-06-15T00:00:00Z'),
name: 'foo.tex',
}
) )
}) })
@@ -510,19 +519,13 @@ describe('HttpController', () => {
await ctx.HttpController.patchDoc(ctx.req, ctx.res, ctx.next) await ctx.HttpController.patchDoc(ctx.req, ctx.res, ctx.next)
}) })
it('should log a message', ctx => { it('should pass a validation error to next', ctx => {
expect(ctx.logger.fatal).to.have.been.calledWith( expect(ctx.next).to.have.been.calledOnce
{ field: 'cannot' }, expect(ctx.next.firstCall.args[0].name).to.equal('InvalidRequestError')
'joi validation for pathDoc is broken'
)
}) })
it('should not pass the invalid field along', ctx => { it('should not patch the document', ctx => {
expect(ctx.DocManager.patchDoc).to.have.been.calledWith( expect(ctx.DocManager.patchDoc).not.to.have.been.called
ctx.projectId,
ctx.docId,
{}
)
}) })
}) })
}) })
+6 -9
View File
@@ -2,10 +2,10 @@
# Instead run bin/update_build_scripts from # Instead run bin/update_build_scripts from
# https://github.com/overleaf/internal/ # https://github.com/overleaf/internal/
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
PROJECT_NAME = document-updater export PROJECT_NAME = document-updater
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd) HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd) export MONOREPO ?= $(shell cd ../../ && pwd)
@@ -31,13 +31,10 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/tools/migrations/package.json \ $(MONOREPO)/tools/migrations/package.json \
$(MONOREPO)/.yarn/patches/* \ $(MONOREPO)/.yarn/patches/* \
| sha256sum | cut -d '-' -f1) | sha256sum | cut -d '-' -f1)
export MOCHA_GREP ?=
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
@@ -5,6 +5,5 @@ document-updater
--env-pass-through= --env-pass-through=
--esmock-loader=False --esmock-loader=False
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/document-updater
--pipeline-owner=44 --pipeline-owner=44
--public-repo=True --public-repo=True
@@ -4,7 +4,7 @@
services: services:
test_unit: test_unit:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
user: node user: node
volumes: volumes:
- ./reports:/overleaf/services/document-updater/reports - ./reports:/overleaf/services/document-updater/reports
@@ -28,7 +28,7 @@ services:
condition: service_healthy condition: service_healthy
test_acceptance: test_acceptance:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
environment: environment:
CI: CI:
RETRIES: RETRIES:
@@ -56,7 +56,7 @@ services:
tar: tar:
build: . build: .
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./:/tmp/build/ - ./:/tmp/build/
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
@@ -5,6 +5,6 @@ before('run migrations', async function () {
this.timeout(60_000) this.timeout(60_000)
await promisify(exec)( await promisify(exec)(
`cd ../../tools/migrations && npm run migrations -- migrate -t server-ce` `cd ../../tools/migrations && yarn run migrations migrate -t 'server-ce & !nonblocking'`
) )
}) })
+6 -9
View File
@@ -2,10 +2,10 @@
# Instead run bin/update_build_scripts from # Instead run bin/update_build_scripts from
# https://github.com/overleaf/internal/ # https://github.com/overleaf/internal/
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
PROJECT_NAME = filestore export PROJECT_NAME = filestore
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd) HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd) export MONOREPO ?= $(shell cd ../../ && pwd)
@@ -27,13 +27,10 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/services/filestore/package.json \ $(MONOREPO)/services/filestore/package.json \
$(MONOREPO)/.yarn/patches/* \ $(MONOREPO)/.yarn/patches/* \
| sha256sum | cut -d '-' -f1) | sha256sum | cut -d '-' -f1)
export MOCHA_GREP ?=
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
-1
View File
@@ -6,7 +6,6 @@ filestore
--env-pass-through= --env-pass-through=
--esmock-loader=False --esmock-loader=False
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/filestore
--pipeline-owner=32 --pipeline-owner=32
--public-repo=True --public-repo=True
--test-acceptance-shards=SHARD_01_,SHARD_02_,SHARD_03_ --test-acceptance-shards=SHARD_01_,SHARD_02_,SHARD_03_
+3 -3
View File
@@ -7,7 +7,7 @@ volumes:
services: services:
test_unit: test_unit:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
user: node user: node
volumes: volumes:
- ./reports:/overleaf/services/filestore/reports - ./reports:/overleaf/services/filestore/reports
@@ -21,7 +21,7 @@ services:
VITEST_NO_CACHE: true VITEST_NO_CACHE: true
test_acceptance: test_acceptance:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
environment: environment:
CI: CI:
RETRIES: RETRIES:
@@ -65,7 +65,7 @@ services:
tar: tar:
build: . build: .
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./:/tmp/build/ - ./:/tmp/build/
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
+1 -1
View File
@@ -5,7 +5,7 @@ MVN_TARGET := target/writelatex-git-bridge-1.0-SNAPSHOT-jar-with-dependencies.ja
export BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
export COMMIT_SHA ?= $(shell git rev-parse HEAD) export COMMIT_SHA ?= $(shell git rev-parse HEAD)
PROJECT_NAME = git-bridge PROJECT_NAME = git-bridge
+1 -1
View File
@@ -38,7 +38,7 @@
<commons.compress.version>1.28.0</commons.compress.version> <commons.compress.version>1.28.0</commons.compress.version>
<simpleclient.version>0.10.0</simpleclient.version> <simpleclient.version>0.10.0</simpleclient.version>
<bouncycastle.crypto.version>1.84</bouncycastle.crypto.version> <bouncycastle.crypto.version>1.84</bouncycastle.crypto.version>
<netty.version>4.1.132.Final</netty.version> <netty.version>4.1.135.Final</netty.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
+6 -9
View File
@@ -2,10 +2,10 @@
# Instead run bin/update_build_scripts from # Instead run bin/update_build_scripts from
# https://github.com/overleaf/internal/ # https://github.com/overleaf/internal/
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
PROJECT_NAME = history-v1 export PROJECT_NAME = history-v1
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd) HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd) export MONOREPO ?= $(shell cd ../../ && pwd)
@@ -33,13 +33,10 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/tools/migrations/package.json \ $(MONOREPO)/tools/migrations/package.json \
$(MONOREPO)/.yarn/patches/* \ $(MONOREPO)/.yarn/patches/* \
| sha256sum | cut -d '-' -f1) | sha256sum | cut -d '-' -f1)
export MOCHA_GREP ?=
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
-1
View File
@@ -5,7 +5,6 @@ history-v1
--env-pass-through= --env-pass-through=
--esmock-loader=False --esmock-loader=False
--node-version=24.14.1 --node-version=24.14.1
--package-name=overleaf-editor
--pipeline-owner=44 --pipeline-owner=44
--public-repo=False --public-repo=False
--tsconfig-extra-includes=backup-deletion-app.mjs,backup-verifier-app.mjs,backup-worker-app.mjs,api/**/*,migrations/**/*,storage/**/* --tsconfig-extra-includes=backup-deletion-app.mjs,backup-verifier-app.mjs,backup-worker-app.mjs,api/**/*,migrations/**/*,storage/**/*
+3 -3
View File
@@ -7,7 +7,7 @@ volumes:
services: services:
test_unit: test_unit:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
user: node user: node
volumes: volumes:
- ./reports:/overleaf/services/history-v1/reports - ./reports:/overleaf/services/history-v1/reports
@@ -31,7 +31,7 @@ services:
condition: service_healthy condition: service_healthy
test_acceptance: test_acceptance:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
environment: environment:
CI: CI:
RETRIES: RETRIES:
@@ -80,7 +80,7 @@ services:
tar: tar:
build: . build: .
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./:/tmp/build/ - ./:/tmp/build/
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
+1 -1
View File
@@ -22,7 +22,7 @@ async function setupMongoDatabase() {
this.timeout(60_000) this.timeout(60_000)
await promisify(exec)( await promisify(exec)(
// Run saas migrations for backup indexes // Run saas migrations for backup indexes
`cd ../../tools/migrations && npm run migrations -- migrate -t saas` `cd ../../tools/migrations && yarn run migrations migrate -t 'saas & !nonblocking'`
) )
} }
+6 -9
View File
@@ -2,10 +2,10 @@
# Instead run bin/update_build_scripts from # Instead run bin/update_build_scripts from
# https://github.com/overleaf/internal/ # https://github.com/overleaf/internal/
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
PROJECT_NAME = notifications export PROJECT_NAME = notifications
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd) HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd) export MONOREPO ?= $(shell cd ../../ && pwd)
@@ -29,13 +29,10 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/tools/migrations/package.json \ $(MONOREPO)/tools/migrations/package.json \
$(MONOREPO)/.yarn/patches/* \ $(MONOREPO)/.yarn/patches/* \
| sha256sum | cut -d '-' -f1) | sha256sum | cut -d '-' -f1)
export MOCHA_GREP ?=
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
-1
View File
@@ -5,7 +5,6 @@ notifications
--env-pass-through= --env-pass-through=
--esmock-loader=False --esmock-loader=False
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/notifications
--pipeline-owner=32 --pipeline-owner=32
--public-repo=True --public-repo=True
--test-acceptance-vitest=True --test-acceptance-vitest=True
+3 -3
View File
@@ -4,7 +4,7 @@
services: services:
test_unit: test_unit:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
user: node user: node
volumes: volumes:
- ./reports:/overleaf/services/notifications/reports - ./reports:/overleaf/services/notifications/reports
@@ -23,7 +23,7 @@ services:
condition: service_started condition: service_started
test_acceptance: test_acceptance:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
environment: environment:
CI: CI:
RETRIES: RETRIES:
@@ -45,7 +45,7 @@ services:
tar: tar:
build: . build: .
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./:/tmp/build/ - ./:/tmp/build/
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
@@ -4,6 +4,6 @@ import { promisify } from 'node:util'
beforeAll(async function () { beforeAll(async function () {
await promisify(exec)( await promisify(exec)(
`cd ../../tools/migrations && npm run migrations -- migrate -t server-ce` `cd ../../tools/migrations && yarn run migrations migrate -t 'server-ce & !nonblocking'`
) )
}, 60_000) }, 60_000)
+6 -9
View File
@@ -2,10 +2,10 @@
# Instead run bin/update_build_scripts from # Instead run bin/update_build_scripts from
# https://github.com/overleaf/internal/ # https://github.com/overleaf/internal/
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
PROJECT_NAME = project-history export PROJECT_NAME = project-history
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd) HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd) export MONOREPO ?= $(shell cd ../../ && pwd)
@@ -32,13 +32,10 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/tools/migrations/package.json \ $(MONOREPO)/tools/migrations/package.json \
$(MONOREPO)/.yarn/patches/* \ $(MONOREPO)/.yarn/patches/* \
| sha256sum | cut -d '-' -f1) | sha256sum | cut -d '-' -f1)
export MOCHA_GREP ?=
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
@@ -1,5 +1,6 @@
import Metrics from '@overleaf/metrics' import Metrics from '@overleaf/metrics'
import logger from '@overleaf/logger' import logger from '@overleaf/logger'
import OError from '@overleaf/o-error'
import express from 'express' import express from 'express'
import bodyParser from 'body-parser' import bodyParser from 'body-parser'
import * as Errors from './Errors.js' import * as Errors from './Errors.js'
@@ -54,6 +55,13 @@ app.use(function (error, req, res, next) {
res.sendStatus(422) res.sendStatus(422)
} else if (error instanceof Errors.TooManyRequestsError) { } else if (error instanceof Errors.TooManyRequestsError) {
res.status(429).set('Retry-After', 300).end() res.status(429).set('Retry-After', 300).end()
} else if (
error instanceof OError &&
error.message === 'Timeout' &&
error.info?.key
) {
logger.warn({ error, req }, error.message)
res.status(423).json({ message: 'redis lock is taken' })
} else { } else {
logger.error({ err: error, req }, error.message) logger.error({ err: error, req }, error.message)
res.status(500).json({ message: 'an internal error occurred' }) res.status(500).json({ message: 'an internal error occurred' })
-1
View File
@@ -5,6 +5,5 @@ project-history
--env-pass-through= --env-pass-through=
--esmock-loader=True --esmock-loader=True
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/project-history
--pipeline-owner=44 --pipeline-owner=44
--public-repo=False --public-repo=False
@@ -4,7 +4,7 @@
services: services:
test_unit: test_unit:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
user: node user: node
volumes: volumes:
- ./reports:/overleaf/services/project-history/reports - ./reports:/overleaf/services/project-history/reports
@@ -28,7 +28,7 @@ services:
condition: service_healthy condition: service_healthy
test_acceptance: test_acceptance:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
environment: environment:
CI: CI:
RETRIES: RETRIES:
@@ -56,7 +56,7 @@ services:
tar: tar:
build: . build: .
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./:/tmp/build/ - ./:/tmp/build/
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
@@ -3,10 +3,13 @@ import { expect } from 'chai'
import { fetchNothing, fetchJsonWithResponse } from '@overleaf/fetch-utils' import { fetchNothing, fetchJsonWithResponse } from '@overleaf/fetch-utils'
import assert from 'node:assert' import assert from 'node:assert'
import mongodb from 'mongodb-legacy' import mongodb from 'mongodb-legacy'
import RedisWrapper from '@overleaf/redis-wrapper'
import * as ProjectHistoryClient from './helpers/ProjectHistoryClient.js' import * as ProjectHistoryClient from './helpers/ProjectHistoryClient.js'
import * as ProjectHistoryApp from './helpers/ProjectHistoryApp.js' import * as ProjectHistoryApp from './helpers/ProjectHistoryApp.js'
import Settings from '@overleaf/settings' import Settings from '@overleaf/settings'
const { ObjectId } = mongodb const { ObjectId } = mongodb
const LockKey = Settings.redis.lock.key_schema
const lockRClient = RedisWrapper.createClient(Settings.redis.lock)
const MockHistoryStore = () => nock('http://127.0.0.1:3100') const MockHistoryStore = () => nock('http://127.0.0.1:3100')
const MockWeb = () => nock('http://127.0.0.1:3000') const MockWeb = () => nock('http://127.0.0.1:3000')
@@ -150,6 +153,22 @@ describe('Flushing old queues', function () {
}) })
}) })
describe('when the project lock is already held', function () {
it('returns 423', async function () {
const key = LockKey.projectHistoryLock({ project_id: this.projectId })
await lockRClient.set(key, 'taken')
try {
const { statusCode } = await ProjectHistoryClient.flushProject(
this.projectId,
{ allowErrors: true }
)
expect(statusCode).to.equal(423)
} finally {
await lockRClient.del(key)
}
})
})
describe('when the update is newer than the cutoff and project has short queue', function () { describe('when the update is newer than the cutoff and project has short queue', function () {
beforeEach(function () { beforeEach(function () {
Settings.shortHistoryQueues.push(this.projectId) Settings.shortHistoryQueues.push(this.projectId)
@@ -5,6 +5,6 @@ before('run migrations', async function () {
this.timeout(60_000) this.timeout(60_000)
await promisify(exec)( await promisify(exec)(
`cd ../../tools/migrations && npm run migrations -- migrate -t server-ce` `cd ../../tools/migrations && yarn run migrations migrate -t 'server-ce & !nonblocking'`
) )
}) })
@@ -2,7 +2,13 @@ import sinon from 'sinon'
import { expect } from 'chai' import { expect } from 'chai'
import mongodb from 'mongodb-legacy' import mongodb from 'mongodb-legacy'
import tk from 'timekeeper' import tk from 'timekeeper'
import { File, Comment, TrackedChange, Range } from 'overleaf-editor-core' import {
File,
Comment,
TrackedChange,
Range,
TrackingProps,
} from 'overleaf-editor-core'
import { UnprocessableError } from 'overleaf-editor-core/lib/errors.js' import { UnprocessableError } from 'overleaf-editor-core/lib/errors.js'
import { strict as esmock } from 'esmock' import { strict as esmock } from 'esmock'
import { FileContentEmptyError } from '../../../../app/js/Errors.js' import { FileContentEmptyError } from '../../../../app/js/Errors.js'
@@ -2013,32 +2019,44 @@ describe('SyncManager', function () {
describe('syncing tracked changes', function () { describe('syncing tracked changes', function () {
beforeEach(function () { beforeEach(function () {
this.loadedSnapshotDoc.getTrackedChanges().add( this.loadedSnapshotDoc.getTrackedChanges().add(
new TrackedChange(new Range(4, 6), { new TrackedChange(
type: 'delete', new Range(4, 6),
userId: USER_ID, TrackingProps.fromRaw({
ts: new Date(TIMESTAMP), type: 'delete',
}) userId: USER_ID,
ts: new Date(TIMESTAMP).toISOString(),
})
)
) )
this.loadedSnapshotDoc.getTrackedChanges().add( this.loadedSnapshotDoc.getTrackedChanges().add(
new TrackedChange(new Range(10, 6), { new TrackedChange(
type: 'insert', new Range(10, 6),
userId: USER_ID, TrackingProps.fromRaw({
ts: new Date(TIMESTAMP), type: 'insert',
}) userId: USER_ID,
ts: new Date(TIMESTAMP).toISOString(),
})
)
) )
this.loadedSnapshotDoc.getTrackedChanges().add( this.loadedSnapshotDoc.getTrackedChanges().add(
new TrackedChange(new Range(20, 6), { new TrackedChange(
type: 'delete', new Range(20, 6),
userId: USER_ID, TrackingProps.fromRaw({
ts: new Date(TIMESTAMP), type: 'delete',
}) userId: USER_ID,
ts: new Date(TIMESTAMP).toISOString(),
})
)
) )
this.loadedSnapshotDoc.getTrackedChanges().add( this.loadedSnapshotDoc.getTrackedChanges().add(
new TrackedChange(new Range(40, 3), { new TrackedChange(
type: 'insert', new Range(40, 3),
userId: USER_ID, TrackingProps.fromRaw({
ts: new Date(TIMESTAMP), type: 'insert',
}) userId: USER_ID,
ts: new Date(TIMESTAMP).toISOString(),
})
)
) )
this.changes = [ this.changes = [
makeTrackedChange('td1', { p: 4, d: 'quick ' }), makeTrackedChange('td1', { p: 4, d: 'quick ' }),
+6 -9
View File
@@ -2,10 +2,10 @@
# Instead run bin/update_build_scripts from # Instead run bin/update_build_scripts from
# https://github.com/overleaf/internal/ # https://github.com/overleaf/internal/
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
PROJECT_NAME = real-time export PROJECT_NAME = real-time
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd) HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd) export MONOREPO ?= $(shell cd ../../ && pwd)
@@ -28,13 +28,10 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/services/real-time/package.json \ $(MONOREPO)/services/real-time/package.json \
$(MONOREPO)/.yarn/patches/* \ $(MONOREPO)/.yarn/patches/* \
| sha256sum | cut -d '-' -f1) | sha256sum | cut -d '-' -f1)
export MOCHA_GREP ?=
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
-1
View File
@@ -4,7 +4,6 @@ real-time
--env-pass-through= --env-pass-through=
--esmock-loader=False --esmock-loader=False
--node-version=24.14.1 --node-version=24.14.1
--package-name=@overleaf/real-time
--pipeline-owner=32 --pipeline-owner=32
--public-repo=False --public-repo=False
--test-unit-vitest=True --test-unit-vitest=True
+3 -3
View File
@@ -4,7 +4,7 @@
services: services:
test_unit: test_unit:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
user: node user: node
volumes: volumes:
- ./reports:/overleaf/services/real-time/reports - ./reports:/overleaf/services/real-time/reports
@@ -26,7 +26,7 @@ services:
condition: service_healthy condition: service_healthy
test_acceptance: test_acceptance:
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
environment: environment:
CI: CI:
RETRIES: RETRIES:
@@ -50,7 +50,7 @@ services:
tar: tar:
build: . build: .
image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
volumes: volumes:
- ./:/tmp/build/ - ./:/tmp/build/
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
+1
View File
@@ -121,6 +121,7 @@ export default defineMain({
...storybookConfig.resolve?.alias, ...storybookConfig.resolve?.alias,
// custom prefixes for import paths // custom prefixes for import paths
'@': path.join(rootDir, 'frontend/js/'), '@': path.join(rootDir, 'frontend/js/'),
'@modules': path.join(rootDir, 'modules/'),
'@ol-types': path.join(rootDir, 'types/'), '@ol-types': path.join(rootDir, 'types/'),
'@ol-storybook': path.join(rootDir, '.storybook/'), '@ol-storybook': path.join(rootDir, '.storybook/'),
'@wf': path.join( '@wf': path.join(
+2 -2
View File
@@ -1,8 +1,8 @@
ARG PROJECT_NAME ARG PROJECT_NAME
ARG BRANCH_NAME ARG BRANCH_NAME_TAG_SAFE
ARG BUILD_NUMBER ARG BUILD_NUMBER
FROM ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER FROM ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
USER root USER root
+12 -10
View File
@@ -1,11 +1,11 @@
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
export RETRIES ?= $(if $(CI),3,0) export RETRIES ?= $(if $(CI),3,0)
BUILD_NUMBER ?= local export BUILD_NUMBER ?= local
ifeq ($(BRANCH_NAME),) ifeq ($(BRANCH_NAME),)
export BRANCH_NAME := $(shell git rev-parse --abbrev-ref HEAD) export BRANCH_NAME := $(shell git rev-parse --abbrev-ref HEAD)
endif endif
BRANCH_NAME_TAG_SAFE := $(shell echo "$(BRANCH_NAME)" | sed 's/\//\-\-/g' | cut -c1-96) export BRANCH_NAME_TAG_SAFE := $(shell echo "$(BRANCH_NAME)" | sed 's/\//\-\-/g' | cut -c1-96)
.PHONY: print-branch-tag-safe .PHONY: print-branch-tag-safe
print-branch-tag-safe: print-branch-tag-safe:
@@ -14,7 +14,7 @@ print-branch-tag-safe:
ifeq ($(COMMIT_SHA),) ifeq ($(COMMIT_SHA),)
export COMMIT_SHA := $(shell git rev-parse HEAD) export COMMIT_SHA := $(shell git rev-parse HEAD)
endif endif
PROJECT_NAME = web export PROJECT_NAME = web
BUILD_DIR_NAME := web BUILD_DIR_NAME := web
PWD := $(shell pwd) PWD := $(shell pwd)
ifeq ($(MONOREPO),) ifeq ($(MONOREPO),)
@@ -28,11 +28,8 @@ CFG_SAAS=/overleaf/services/web/test/acceptance/config/settings.test.saas.js
CFG_SERVER_CE=/overleaf/services/web/test/acceptance/config/settings.test.server-ce.js CFG_SERVER_CE=/overleaf/services/web/test/acceptance/config/settings.test.server-ce.js
CFG_SERVER_PRO=/overleaf/services/web/test/acceptance/config/settings.test.server-pro.js CFG_SERVER_PRO=/overleaf/services/web/test/acceptance/config/settings.test.server-pro.js
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ export MOCHA_GREP ?=
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \ DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
# Common flags for docker compose run. # Common flags for docker compose run.
# --rm Tear down test runner container # --rm Tear down test runner container
@@ -154,7 +151,7 @@ MOCHA_CMD_LINE = \
mocha \ mocha \
--exit \ --exit \
--file test/unit/bootstrap.js \ --file test/unit/bootstrap.js \
--grep=${MOCHA_GREP} \ --grep="${MOCHA_GREP}" \
--reporter spec \ --reporter spec \
--timeout 25000 \ --timeout 25000 \
@@ -178,7 +175,7 @@ test_unit_module: mongo_migrations_for_tests
$(MAKE) modules/$(MODULE_NAME)/test_unit $(MAKE) modules/$(MODULE_NAME)/test_unit
mongo_migrations_for_tests: mongo_migrations_for_tests:
$(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) --workdir /overleaf/tools/migrations test_unit yarn run migrations migrate -t saas $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) --workdir /overleaf/tools/migrations test_unit yarn run migrations migrate -t 'saas & !nonblocking'
# #
# Frontend tests # Frontend tests
@@ -550,6 +547,10 @@ else
$(RUN_LINTING) lint $(RUN_LINTING) lint
endif endif
lint_fix: lint_eslint_fix
lint_eslint_fix:
$(RUN_LINTING) lint:fix
lint: lint_stylelint lint: lint_stylelint
lint_stylelint: lint_stylelint:
ifeq ($(CI),true) ifeq ($(CI),true)
@@ -559,6 +560,7 @@ else
$(RUN_LINTING) lint:styles $(RUN_LINTING) lint:styles
endif endif
lint_fix: lint_stylelint_fix
lint_stylelint_fix: lint_stylelint_fix:
$(RUN_LINTING) lint:styles:fix $(RUN_LINTING) lint:styles:fix
+9 -10
View File
@@ -1,8 +1,13 @@
BUILD_DIR_NAME ?= web BUILD_DIR_NAME ?= web
ifeq ($(BRANCH_NAME),)
export BRANCH_NAME := $(shell git rev-parse --abbrev-ref HEAD)
endif
export BRANCH_NAME_TAG_SAFE := $(shell echo "$(BRANCH_NAME)" | sed 's/\//\-\-/g' | cut -c1-96)
export MODULE_NAME := $(notdir $(shell pwd)) export MODULE_NAME := $(notdir $(shell pwd))
MODULE_DIR := modules/$(MODULE_NAME) export MODULE_DIR := modules/$(MODULE_NAME)
PROJECT_NAME = web export PROJECT_NAME = web
export RETRIES ?= $(if $(CI),3,0) export RETRIES ?= $(if $(CI),3,0)
export MOCHA_GREP ?=
export OVERLEAF_CONFIG = /overleaf/services/web/$(MODULE_DIR)/test/acceptance/config/settings.test.js export OVERLEAF_CONFIG = /overleaf/services/web/$(MODULE_DIR)/test/acceptance/config/settings.test.js
export BASE_CONFIG ?= /overleaf/services/web/test/acceptance/config/settings.test.saas.js export BASE_CONFIG ?= /overleaf/services/web/test/acceptance/config/settings.test.saas.js
@@ -12,13 +17,7 @@ CFG_SERVER_CE=/overleaf/services/web/test/acceptance/config/settings.test.server
CFG_SERVER_PRO=/overleaf/services/web/test/acceptance/config/settings.test.server-pro.js CFG_SERVER_PRO=/overleaf/services/web/test/acceptance/config/settings.test.server-pro.js
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := cd ../../ && \ DOCKER_COMPOSE := cd ../../ && docker compose ${DOCKER_COMPOSE_FLAGS}
MODULE_DIR=$(MODULE_DIR) \
BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
ifeq (,$(wildcard test/unit)) ifeq (,$(wildcard test/unit))
test_unit: test_unit:
@@ -26,7 +25,7 @@ test_unit:
else else
test_unit: export COMPOSE_PROJECT_NAME=unit_test_$(BUILD_DIR_NAME)_$(MODULE_NAME) test_unit: export COMPOSE_PROJECT_NAME=unit_test_$(BUILD_DIR_NAME)_$(MODULE_NAME)
test_unit: test_unit:
${DOCKER_COMPOSE} run --rm test_unit yarn test:unit:run_dir -- ${MOCHA_ARGS} $(MODULE_DIR)/test/unit/src ${DOCKER_COMPOSE} run --rm test_unit yarn test:unit:run_dir ${MOCHA_ARGS} $(MODULE_DIR)/test/unit/src
${DOCKER_COMPOSE} down ${DOCKER_COMPOSE} down
endif endif
@@ -82,7 +82,7 @@ async function recordEventForUser(userId, event, segmentation) {
userId, userId,
event, event,
segmentation, segmentation,
isLabsUser: Boolean(labsProgram), isLabsUser: labsProgram,
isLoggedIn: true, isLoggedIn: true,
}) })
} }
@@ -97,6 +97,31 @@ function recordEventForUserInBackground(userId, event, segmentation) {
}) })
} }
async function recordEventForMongoUser(user, event, segmentation) {
const { userId, analyticsId, labsProgram } = _getIdsFromMongoUser(user)
if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
return
}
_recordEvent({
analyticsId,
userId,
event,
segmentation,
isLabsUser: labsProgram,
isLoggedIn: true,
})
}
function recordEventForMongoUserInBackground(user, event, segmentation) {
const { userId } = _getIdsFromMongoUser(user) // throw before going into the background.
recordEventForMongoUser(user, event, segmentation).catch(err => {
logger.warn(
{ err, userId, event, segmentation },
'failed to record event for user'
)
})
}
function recordEventForSession(session, event, segmentation) { function recordEventForSession(session, event, segmentation) {
const { analyticsId, userId } = getIdsFromSession(session) const { analyticsId, userId } = getIdsFromSession(session)
if (!analyticsId) { if (!analyticsId) {
@@ -142,7 +167,7 @@ async function setUserPropertyForUser(userId, propertyName, propertyValue) {
if (analyticsId) { if (analyticsId) {
await _setUserProperty({ await _setUserProperty({
analyticsId, analyticsId,
isLabsUser: Boolean(labsProgram), isLabsUser: labsProgram,
propertyName, propertyName,
propertyValue, propertyValue,
}) })
@@ -158,6 +183,41 @@ function setUserPropertyForUserInBackground(userId, property, value) {
}) })
} }
/**
* @param {{_id: ObjectId, analyticsId: string, labsProgram: boolean}} user
* @param {string} propertyName
* @param {any} propertyValue
* @return {Promise<void>}
*/
async function setUserPropertyForMongoUser(user, propertyName, propertyValue) {
const { userId, analyticsId, labsProgram } = _getIdsFromMongoUser(user)
if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
return
}
_checkPropertyValue(propertyValue)
await _setUserProperty({
analyticsId,
isLabsUser: labsProgram,
propertyName,
propertyValue,
})
}
/**
* @param {{_id: ObjectId, analyticsId: string, labsProgram: boolean}} user
* @param {string} property
* @param {any} value
*/
function setUserPropertyForMongoUserInBackground(user, property, value) {
const { userId } = _getIdsFromMongoUser(user) // throw before going into the background.
setUserPropertyForMongoUser(user, property, value).catch(err => {
logger.warn(
{ err, userId, property, value },
'failed to set user property for user'
)
})
}
async function setUserPropertyForAnalyticsId( async function setUserPropertyForAnalyticsId(
analyticsId, analyticsId,
propertyName, propertyName,
@@ -192,8 +252,8 @@ async function setUserPropertyForSession(session, propertyName, propertyValue) {
} }
function setUserPropertyForSessionInBackground(session, property, value) { function setUserPropertyForSessionInBackground(session, property, value) {
const { analyticsId, userId } = getIdsFromSession(session) // throw before going into the background.
setUserPropertyForSession(session, property, value).catch(err => { setUserPropertyForSession(session, property, value).catch(err => {
const { analyticsId, userId } = getIdsFromSession(session)
logger.warn( logger.warn(
{ err, analyticsId, userId, property, value }, { err, analyticsId, userId, property, value },
'failed to set user property for session' 'failed to set user property for session'
@@ -452,6 +512,26 @@ function _isAnalyticsDisabled() {
return !(Settings.analytics && Settings.analytics.enabled) return !(Settings.analytics && Settings.analytics.enabled)
} }
/**
* @param {{_id: ObjectId, analyticsId: string, labsProgram: boolean}} user
* @return {{userId: string, analyticsId: string, labsProgram: boolean}}
*/
function _getIdsFromMongoUser(user) {
const userId = user?._id?.toString()
if (!userId) {
throw new Error('bug: include db.users._id in projection')
}
const analyticsId = user?.analyticsId
if (!analyticsId) {
throw new Error('bug: include db.users.analyticsId in projection')
}
const labsProgram = user?.labsProgram
if (typeof labsProgram !== 'boolean') {
throw new Error('bug: include db.users.labsProgram in projection')
}
return { userId, analyticsId, labsProgram }
}
function _checkPropertyValue(propertyValue) { function _checkPropertyValue(propertyValue) {
if (propertyValue === undefined) { if (propertyValue === undefined) {
throw new Error( throw new Error(
@@ -498,13 +578,15 @@ async function analyticsIdMiddleware(req, res, next) {
if (sessionUser) { if (sessionUser) {
// For old sessions, session.analyticsId is the anon id immediately after login. Do not use it! // For old sessions, session.analyticsId is the anon id immediately after login. Do not use it!
session.analyticsId = sessionUser.analyticsId session.analyticsId = sessionUser.analyticsId
if (!session.analyticsId) { if (!session.analyticsId || typeof sessionUser.labsProgram !== 'boolean') {
session.analyticsId = sessionUser.analyticsId = const { analyticsId, labsProgram } =
await UserAnalyticsDataCache.getAnalyticsId( await UserAnalyticsDataCache.getAnalyticsData(
sessionUser._id, sessionUser._id,
// Do not drill down further, this middleware is on all endpoints. // Do not drill down further, this middleware is on all endpoints.
'analyticsIdMiddleware' 'analyticsIdMiddleware'
) )
session.analyticsId = sessionUser.analyticsId = analyticsId
sessionUser.labsProgram = labsProgram
} }
} else if (!session.analyticsId) { } else if (!session.analyticsId) {
// generate an `analyticsId` if needed // generate an `analyticsId` if needed
@@ -521,9 +603,13 @@ export default {
recordEventForSession, recordEventForSession,
recordEventForUser, recordEventForUser,
recordEventForUserInBackground, recordEventForUserInBackground,
recordEventForMongoUser,
recordEventForMongoUserInBackground,
emitPackageUsage, emitPackageUsage,
setUserPropertyForUser, setUserPropertyForUser,
setUserPropertyForUserInBackground, setUserPropertyForUserInBackground,
setUserPropertyForMongoUser,
setUserPropertyForMongoUserInBackground,
setUserPropertyForSession, setUserPropertyForSession,
setUserPropertyForSessionInBackground, setUserPropertyForSessionInBackground,
setUserPropertyForAnalyticsId, setUserPropertyForAnalyticsId,
@@ -25,32 +25,32 @@ function clearInbound(session) {
} }
} }
function addUserProperties(userId, session) { function addUserProperties(user, session) {
if (!session) { if (!session) {
return return
} }
if (session.required_login_from_product_medium) { if (session.required_login_from_product_medium) {
AnalyticsManager.setUserPropertyForUserInBackground( AnalyticsManager.setUserPropertyForMongoUserInBackground(
userId, user,
`registered-from-product-medium`, `registered-from-product-medium`,
session.required_login_from_product_medium session.required_login_from_product_medium
) )
if (session.required_login_from_product_source) { if (session.required_login_from_product_source) {
AnalyticsManager.setUserPropertyForUserInBackground( AnalyticsManager.setUserPropertyForMongoUserInBackground(
userId, user,
`registered-from-product-source`, `registered-from-product-source`,
session.required_login_from_product_source session.required_login_from_product_source
) )
} }
} else if (session.referal_id) { } else if (session.referal_id) {
AnalyticsManager.setUserPropertyForUserInBackground( AnalyticsManager.setUserPropertyForMongoUserInBackground(
userId, user,
`registered-from-bonus-scheme`, `registered-from-bonus-scheme`,
true true
) )
AnalyticsManager.setUserPropertyForUserInBackground( AnalyticsManager.setUserPropertyForMongoUserInBackground(
userId, user,
`registered-from-product-medium`, `registered-from-product-medium`,
'bonus-scheme' 'bonus-scheme'
) )
@@ -58,16 +58,16 @@ function addUserProperties(userId, session) {
if (session.inbound) { if (session.inbound) {
if (session.inbound.referrer && session.inbound.referrer.medium) { if (session.inbound.referrer && session.inbound.referrer.medium) {
AnalyticsManager.setUserPropertyForUserInBackground( AnalyticsManager.setUserPropertyForMongoUserInBackground(
userId, user,
`registered-from-referrer-medium`, `registered-from-referrer-medium`,
`${session.inbound.referrer.medium `${session.inbound.referrer.medium
.charAt(0) .charAt(0)
.toUpperCase()}${session.inbound.referrer.medium.slice(1)}` .toUpperCase()}${session.inbound.referrer.medium.slice(1)}`
) )
if (session.inbound.referrer.source) { if (session.inbound.referrer.source) {
AnalyticsManager.setUserPropertyForUserInBackground( AnalyticsManager.setUserPropertyForMongoUserInBackground(
userId, user,
`registered-from-referrer-source`, `registered-from-referrer-source`,
session.inbound.referrer.source session.inbound.referrer.source
) )
@@ -77,8 +77,8 @@ function addUserProperties(userId, session) {
if (session.inbound.utm) { if (session.inbound.utm) {
for (const utmKey of RequestHelper.REGISTRATION_UTM_KEYS) { for (const utmKey of RequestHelper.REGISTRATION_UTM_KEYS) {
if (session.inbound.utm[utmKey]) { if (session.inbound.utm[utmKey]) {
AnalyticsManager.setUserPropertyForUserInBackground( AnalyticsManager.setUserPropertyForMongoUserInBackground(
userId, user,
`registered-from-${utmKey.replace('_', '-')}`, `registered-from-${utmKey.replace('_', '-')}`,
session.inbound.utm[utmKey] session.inbound.utm[utmKey]
) )
@@ -685,7 +685,7 @@ function _loginAsyncHandlers(req, user, anonymousAnalyticsId, isNewUser) {
LoginRateLimiter.recordSuccessfulLogin(user.email, () => {}) LoginRateLimiter.recordSuccessfulLogin(user.email, () => {})
AuthenticationController._recordSuccessfulLogin(user._id, () => {}) AuthenticationController._recordSuccessfulLogin(user._id, () => {})
AuthenticationController.ipMatchCheck(req, user) AuthenticationController.ipMatchCheck(req, user)
Analytics.recordEventForUserInBackground(user._id, 'user-logged-in', { Analytics.recordEventForMongoUserInBackground(user, 'user-logged-in', {
source: req.session.saml source: req.session.saml
? 'saml' ? 'saml'
: req.user_info?.auth_provider || 'email-password', : req.user_info?.auth_provider || 'email-password',
@@ -8,7 +8,7 @@ import { expressify } from '@overleaf/promise-utils'
async function optIn(req, res) { async function optIn(req, res) {
const userId = SessionManager.getLoggedInUserId(req.session) const userId = SessionManager.getLoggedInUserId(req.session)
await BetaProgramHandler.promises.optIn(userId) await BetaProgramHandler.promises.optIn(req.session, userId)
try { try {
await SplitTestSessionHandler.promises.sessionMaintenance(req, null) await SplitTestSessionHandler.promises.sessionMaintenance(req, null)
} catch (error) { } catch (error) {
@@ -22,7 +22,7 @@ async function optIn(req, res) {
async function optOut(req, res) { async function optOut(req, res) {
const userId = SessionManager.getLoggedInUserId(req.session) const userId = SessionManager.getLoggedInUserId(req.session)
await BetaProgramHandler.promises.optOut(userId) await BetaProgramHandler.promises.optOut(req.session, userId)
try { try {
await SplitTestSessionHandler.promises.sessionMaintenance(req, null) await SplitTestSessionHandler.promises.sessionMaintenance(req, null)
} catch (error) { } catch (error) {

Some files were not shown because too many files have changed in this diff Show More