mirror of
https://github.com/overleaf/overleaf.git
synced 2025-12-05 01:10:29 +00:00
Compare commits
23 Commits
3a206074f2
...
6ca1a9e472
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ca1a9e472 | ||
|
|
8cd2f6ac46 | ||
|
|
c3881084be | ||
|
|
1d3d81e67d | ||
|
|
ad3d68ece0 | ||
|
|
632f7c85ed | ||
|
|
1a3ce3f192 | ||
|
|
3522923d4e | ||
|
|
85848b927e | ||
|
|
42a80a1ca3 | ||
|
|
618445722a | ||
|
|
d77be2c94e | ||
|
|
bfa70bf43b | ||
|
|
23fc714d93 | ||
|
|
b1b638dd90 | ||
|
|
a88c307962 | ||
|
|
1720314726 | ||
|
|
5a242cd4ac | ||
|
|
4d1e316bea | ||
|
|
ec5a283e9e | ||
|
|
c48bc282f1 | ||
|
|
a80b69abe5 | ||
|
|
41976072e5 |
17
libraries/validation-tools/handleValidationError.js
Normal file
17
libraries/validation-tools/handleValidationError.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const { isZodErrorLike, fromError } = require('zod-validation-error')
|
||||
/**
|
||||
* @typedef {import('express').ErrorRequestHandler} ErrorRequestHandler
|
||||
*/
|
||||
|
||||
const handleValidationError = [
|
||||
/** @type {ErrorRequestHandler} */
|
||||
(err, req, res, next) => {
|
||||
if (!isZodErrorLike(err)) {
|
||||
return next(err)
|
||||
}
|
||||
|
||||
res.status(400).json({ ...fromError(err), statusCode: 400 })
|
||||
},
|
||||
]
|
||||
|
||||
module.exports = { handleValidationError }
|
||||
@@ -2,10 +2,12 @@ const { ParamsError } = require('./Errors')
|
||||
const { z } = require('zod')
|
||||
const { zz } = require('./zodHelpers')
|
||||
const { validateReq } = require('./validateReq')
|
||||
const { handleValidationError } = require('./handleValidationError')
|
||||
|
||||
module.exports = {
|
||||
z,
|
||||
zz,
|
||||
validateReq,
|
||||
handleValidationError,
|
||||
ParamsError,
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"dependencies": {
|
||||
"@overleaf/o-error": "*",
|
||||
"mongodb": "^6.12.0",
|
||||
"zod": "^4.1.8"
|
||||
"zod": "^4.1.8",
|
||||
"zod-validation-error": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.4",
|
||||
|
||||
207
package-lock.json
generated
207
package-lock.json
generated
@@ -541,7 +541,8 @@
|
||||
"dependencies": {
|
||||
"@overleaf/o-error": "*",
|
||||
"mongodb": "^6.12.0",
|
||||
"zod": "^4.1.8"
|
||||
"zod": "^4.1.8",
|
||||
"zod-validation-error": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.4",
|
||||
@@ -9304,6 +9305,16 @@
|
||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@borewit/text-codec": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
|
||||
"integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@bugsnag/browser": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.25.0.tgz",
|
||||
@@ -10341,26 +10352,6 @@
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@customerio/cdp-analytics-node/node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@customerio/cdp-analytics-node/node_modules/uuid": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
@@ -19126,6 +19117,47 @@
|
||||
"@testing-library/dom": ">=7.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
|
||||
"integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"fflate": "^0.8.2",
|
||||
"token-types": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
@@ -30400,6 +30432,12 @@
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||
@@ -30468,6 +30506,24 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/file-type": {
|
||||
"version": "21.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz",
|
||||
"integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/inflate": "^0.2.7",
|
||||
"strtok3": "^10.2.2",
|
||||
"token-types": "^6.0.0",
|
||||
"uint8array-extras": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
@@ -33121,9 +33177,24 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.1",
|
||||
@@ -46121,6 +46192,22 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/strtok3": {
|
||||
"version": "10.3.4",
|
||||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz",
|
||||
"integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/stubs": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
|
||||
@@ -48051,6 +48138,24 @@
|
||||
"resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ="
|
||||
},
|
||||
"node_modules/token-types": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz",
|
||||
"integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@borewit/text-codec": "^0.1.0",
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"ieee754": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/toposort": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
|
||||
@@ -48499,6 +48604,18 @@
|
||||
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz",
|
||||
"integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA=="
|
||||
},
|
||||
"node_modules/uint8array-extras": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
|
||||
"integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ultron": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
|
||||
@@ -49136,26 +49253,6 @@
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/vinyl-contents/node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/vinyl-fs": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz",
|
||||
@@ -51424,9 +51521,9 @@
|
||||
"@overleaf/promise-utils": "*",
|
||||
"@overleaf/settings": "*",
|
||||
"@overleaf/stream-utils": "*",
|
||||
"@overleaf/validation-tools": "*",
|
||||
"body-parser": "^1.20.3",
|
||||
"bunyan": "^1.8.15",
|
||||
"celebrate": "^15.0.3",
|
||||
"express": "^4.21.2",
|
||||
"p-limit": "^3.1.0"
|
||||
},
|
||||
@@ -53194,6 +53291,7 @@
|
||||
"express-bearer-token": "^2.4.0",
|
||||
"express-http-proxy": "^1.6.0",
|
||||
"express-session": "^1.17.1",
|
||||
"file-type": "^21.0.0",
|
||||
"globby": "^5.0.0",
|
||||
"helmet": "^6.0.1",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
@@ -54194,25 +54292,6 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"services/web/node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"services/web/node_modules/isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
|
||||
@@ -15,27 +15,27 @@
|
||||
<maven.assembly.plugin.version>3.1.0</maven.assembly.plugin.version>
|
||||
<fmt.plugin.version>2.23</fmt.plugin.version>
|
||||
<junit.version>4.13.2</junit.version>
|
||||
<jmock.junit4.version>2.8.4</jmock.junit4.version>
|
||||
<jmock.junit4.version>2.13.1</jmock.junit4.version>
|
||||
<jetty.servlet.version>9.4.57.v20241219</jetty.servlet.version>
|
||||
<gson.version>2.9.0</gson.version>
|
||||
<async.http.client.version>3.0.2</async.http.client.version>
|
||||
<gson.version>2.13.2</gson.version>
|
||||
<async.http.client.version>3.0.3</async.http.client.version>
|
||||
<jgit.version>6.10.1.202505221210-r</jgit.version>
|
||||
<sqlite.jdbc.version>3.41.2.2</sqlite.jdbc.version>
|
||||
<joda.time.version>2.9.9</joda.time.version>
|
||||
<google.oauth.client.version>1.37.0</google.oauth.client.version>
|
||||
<google.oauth.client.version>1.39.0</google.oauth.client.version>
|
||||
<google.http.client.version>1.23.0</google.http.client.version>
|
||||
<commons.lang3.version>3.18.0</commons.lang3.version>
|
||||
<logback.classic.version>1.5.18</logback.classic.version>
|
||||
<mockserver.version>5.12.0</mockserver.version>
|
||||
<mockito.version>5.12.0</mockito.version>
|
||||
<aws.java.sdk.version>1.12.780</aws.java.sdk.version>
|
||||
<commons.lang3.version>3.19.0</commons.lang3.version>
|
||||
<logback.classic.version>1.5.20</logback.classic.version>
|
||||
<mockserver.version>5.15.0</mockserver.version>
|
||||
<mockito.version>5.20.0</mockito.version>
|
||||
<aws.java.sdk.version>1.12.793</aws.java.sdk.version>
|
||||
<jakarta.xml.bind.api.version>${jaxb.runtime.version}</jakarta.xml.bind.api.version>
|
||||
<jaxb.runtime.version>2.3.2</jaxb.runtime.version>
|
||||
<httpclient.version>4.5.14</httpclient.version>
|
||||
<commons.io.version>2.18.0</commons.io.version>
|
||||
<commons.compress.version>1.27.1</commons.compress.version>
|
||||
<commons.io.version>2.20.0</commons.io.version>
|
||||
<commons.compress.version>1.28.0</commons.compress.version>
|
||||
<simpleclient.version>0.10.0</simpleclient.version>
|
||||
<bouncycastle.crypto.version>1.81</bouncycastle.crypto.version>
|
||||
<bouncycastle.crypto.version>1.82</bouncycastle.crypto.version>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
5
services/web/.vscode/settings.json
vendored
5
services/web/.vscode/settings.json
vendored
@@ -3,5 +3,8 @@
|
||||
"node_modules": true,
|
||||
"data": true
|
||||
},
|
||||
"cSpell.words": ["docstore", "Tpds"]
|
||||
"cSpell.words": [
|
||||
"docstore",
|
||||
"Tpds"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
|
||||
|
||||
BUILD_NUMBER ?= local
|
||||
export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/')
|
||||
export COMMIT_SHA ?= $(shell git rev-parse HEAD)
|
||||
PROJECT_NAME = web
|
||||
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
|
||||
@@ -16,7 +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
|
||||
|
||||
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
|
||||
BRANCH_NAME=$(BRANCH_NAME) \
|
||||
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
|
||||
PROJECT_NAME=$(PROJECT_NAME) \
|
||||
MOCHA_GREP=${MOCHA_GREP} \
|
||||
docker compose ${DOCKER_COMPOSE_FLAGS}
|
||||
@@ -426,7 +427,7 @@ ci:
|
||||
#
|
||||
ORG_PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
RUN_LINT_FORMAT ?= \
|
||||
docker run --rm --env BRANCH_NAME --env CI --env COMMIT_SHA --env MONOREPO --volume ${PWD}/data/reports:/overleaf/services/web/data/reports ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
||||
docker run --rm --env BRANCH_NAME --env CI --env COMMIT_SHA --env MONOREPO --volume ${PWD}/data/reports:/overleaf/services/web/data/reports ${IMAGE_CI}
|
||||
|
||||
NODE_MODULES_PATH := ${PATH}:${PWD}/node_modules/.bin:/overleaf/services/web/node_modules/.bin
|
||||
WITH_NODE_MODULES_PATH = \
|
||||
@@ -549,9 +550,9 @@ shellcheck_fix:
|
||||
# Build & publish
|
||||
#
|
||||
|
||||
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
||||
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
|
||||
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
|
||||
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
||||
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
|
||||
# A shared scratch tag that is used by all the pipelines for pushing the layers of the production image ASAP from a parallel step.
|
||||
# We do not want to make the image available before all the tests have passed.
|
||||
# Using a single tag avoids generating cruft in our docker repository / AR.
|
||||
|
||||
@@ -52,6 +52,8 @@ function getNewCompileBackendClass(projectId, compileBackendClass) {
|
||||
return 'n4'
|
||||
case 'c2d':
|
||||
return 'n4'
|
||||
case 'c3d':
|
||||
return 'n4'
|
||||
case 'c4d':
|
||||
return 'n4'
|
||||
default:
|
||||
|
||||
@@ -135,7 +135,7 @@ async function _getProjectCompileLimits(project) {
|
||||
timeout:
|
||||
ownerFeatures.compileTimeout || Settings.defaultFeatures.compileTimeout,
|
||||
compileGroup,
|
||||
compileBackendClass: compileGroup === 'standard' ? 'n2d' : 'c4d',
|
||||
compileBackendClass: compileGroup === 'standard' ? 'c3d' : 'c4d',
|
||||
ownerAnalyticsId: analyticsId,
|
||||
}
|
||||
return limits
|
||||
|
||||
@@ -445,7 +445,6 @@ async function projectListPage(req, res, next) {
|
||||
_.sample(['on-premise', 'FOMO', 'FOMO', 'FOMO'])
|
||||
|
||||
let showInrGeoBanner = false
|
||||
let showBrlGeoBanner = false
|
||||
let showLATAMBanner = false
|
||||
let recommendedCurrency
|
||||
|
||||
@@ -459,7 +458,6 @@ async function projectListPage(req, res, next) {
|
||||
if (countryCode === 'IN') {
|
||||
showInrGeoBanner = true
|
||||
}
|
||||
showBrlGeoBanner = countryCode === 'BR'
|
||||
|
||||
showLATAMBanner = ['MX', 'CO', 'CL', 'PE'].includes(countryCode)
|
||||
// LATAM Banner needs to know which currency to display
|
||||
@@ -566,7 +564,6 @@ async function projectListPage(req, res, next) {
|
||||
showLATAMBanner,
|
||||
recommendedCurrency,
|
||||
showInrGeoBanner,
|
||||
showBrlGeoBanner,
|
||||
projectDashboardReact: true, // used in navbar
|
||||
groupSsoSetupSuccess,
|
||||
joinedGroupName,
|
||||
|
||||
@@ -2,7 +2,6 @@ import express from 'express'
|
||||
import Settings from '@overleaf/settings'
|
||||
import logger from '@overleaf/logger'
|
||||
import metrics from '@overleaf/metrics'
|
||||
import Validation from './Validation.js'
|
||||
import csp, { removeCSPHeaders } from './CSP.mjs'
|
||||
import Router from '../router.mjs'
|
||||
import helmet from 'helmet'
|
||||
@@ -38,6 +37,7 @@ import os from 'node:os'
|
||||
import http from 'node:http'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import serveStaticWrapper from './ServeStaticWrapper.mjs'
|
||||
import { handleValidationError } from '@overleaf/validation-tools'
|
||||
|
||||
const { hasAdminAccess } = AdminAuthorizationHelper
|
||||
const sessionsRedisClient = UserSessionsRedis.client()
|
||||
@@ -356,17 +356,17 @@ const server = http.createServer(app)
|
||||
if (Settings.enabledServices.includes('api')) {
|
||||
logger.debug({}, 'providing api router')
|
||||
app.use(privateApiRouter)
|
||||
app.use(Validation.errorMiddleware)
|
||||
app.use(handleValidationError)
|
||||
app.use(ErrorController.handleApiError)
|
||||
}
|
||||
|
||||
if (Settings.enabledServices.includes('web')) {
|
||||
logger.debug({}, 'providing web router')
|
||||
app.use(publicApiRouter) // public API goes with web router for public access
|
||||
app.use(Validation.errorMiddleware)
|
||||
app.use(handleValidationError)
|
||||
app.use(ErrorController.handleApiError)
|
||||
app.use(webRouter)
|
||||
app.use(Validation.errorMiddleware)
|
||||
app.use(handleValidationError)
|
||||
app.use(ErrorController.handleError)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,22 +7,6 @@ const {
|
||||
zz,
|
||||
ParamsError,
|
||||
} = require('@overleaf/validation-tools')
|
||||
const { isZodErrorLike, fromError } = require('zod-validation-error')
|
||||
|
||||
/**
|
||||
* @typedef {import('express').ErrorRequestHandler} ErrorRequestHandler
|
||||
*/
|
||||
|
||||
const errorMiddleware = [
|
||||
/** @type {ErrorRequestHandler} */
|
||||
(err, req, res, next) => {
|
||||
if (!isZodErrorLike(err)) {
|
||||
return next(err)
|
||||
}
|
||||
|
||||
res.status(400).json({ ...fromError(err), statusCode: 400 })
|
||||
},
|
||||
]
|
||||
|
||||
const validateReqWeb = (req, schema) => {
|
||||
try {
|
||||
@@ -37,7 +21,6 @@ const validateReqWeb = (req, schema) => {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
errorMiddleware,
|
||||
validateReq: validateReqWeb,
|
||||
z,
|
||||
zz,
|
||||
|
||||
@@ -61,7 +61,6 @@ block append meta
|
||||
content=groupsAndEnterpriseBannerVariant
|
||||
)
|
||||
meta(name='ol-showInrGeoBanner' data-type='boolean' content=showInrGeoBanner)
|
||||
meta(name='ol-showBrlGeoBanner' data-type='boolean' content=showBrlGeoBanner)
|
||||
meta(
|
||||
name='ol-recommendedCurrency'
|
||||
data-type='string'
|
||||
|
||||
@@ -13,5 +13,4 @@ block append meta
|
||||
meta(name='ol-user' data-type='json' content=user)
|
||||
|
||||
block content
|
||||
#main-content.content.content-alt
|
||||
#token-access-page
|
||||
#token-access-page
|
||||
|
||||
@@ -715,10 +715,14 @@ module.exports = {
|
||||
parseInt(process.env.OVERLEAF_PROJECT_HARD_DELETION_DELAY, 10) ||
|
||||
1000 * 60 * 60 * 24 * 90, // 90 days
|
||||
|
||||
// Delay before sending comment mention notifications
|
||||
commentMentionDelay:
|
||||
// Maximum Delay before sending comment mention notifications
|
||||
notificationMaxDelay:
|
||||
parseInt(process.env.COMMENT_MENTION_DELAY_MINUTES) || 30 * 60 * 1000, // 30 minutes
|
||||
|
||||
// Comment mention notifications will wait at least this long before being sent
|
||||
notificationMinDelay:
|
||||
parseInt(process.env.COMMENT_MENTION_DELAY_MINUTES) || 10 * 60 * 1000, // 10 minutes
|
||||
|
||||
// Maximum JSON size in HTTP requests
|
||||
// We should be able to process twice the max doc length, to allow for
|
||||
// - the doc content
|
||||
|
||||
@@ -4,7 +4,6 @@ import localesPromise from '@/i18n'
|
||||
import './shared/commands'
|
||||
import './shared/exceptions'
|
||||
import './ct/commands'
|
||||
import './ct/codemirror'
|
||||
import '../../test/frontend/helpers/bootstrap'
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { EditorView } from '@codemirror/view'
|
||||
|
||||
// @ts-ignore (disable EditContext-based editing until stable)
|
||||
EditorView.EDIT_CONTEXT = false
|
||||
@@ -412,6 +412,7 @@
|
||||
"delete_figure": "",
|
||||
"delete_message": "",
|
||||
"delete_message_confirmation": "",
|
||||
"delete_permanently": "",
|
||||
"delete_projects": "",
|
||||
"delete_row_or_column": "",
|
||||
"delete_sso_config": "",
|
||||
@@ -2037,6 +2038,7 @@
|
||||
"unlink_all_users_explanation": "",
|
||||
"unlink_dropbox_folder": "",
|
||||
"unlink_dropbox_warning": "",
|
||||
"unlink_from_sso": "",
|
||||
"unlink_github_repository": "",
|
||||
"unlink_github_warning": "",
|
||||
"unlink_linked_accounts": "",
|
||||
@@ -2045,7 +2047,6 @@
|
||||
"unlink_provider_account_warning": "",
|
||||
"unlink_reference": "",
|
||||
"unlink_the_project_from_the_current_github_repo": "",
|
||||
"unlink_user": "",
|
||||
"unlink_user_explanation": "",
|
||||
"unlink_users": "",
|
||||
"unlink_warning_reference": "",
|
||||
|
||||
@@ -2,6 +2,7 @@ This directory contains fonts used by the Overleaf web application.
|
||||
|
||||
* [DM Mono](https://github.com/googlefonts/dm-mono)
|
||||
* [Font Awesome](https://fontawesome.com/v4/)
|
||||
* [Inter](https://rsms.me/inter/)
|
||||
* [Lato](https://www.latofonts.com/)
|
||||
* [Material Symbols](https://github.com/google/material-design-icons)
|
||||
* [Merriweather](https://github.com/SorkinType/Merriweather)
|
||||
|
||||
@@ -24,10 +24,19 @@ const families = [
|
||||
archive: 'https://fontawesome.com/v4/assets/font-awesome-4.7.0.zip',
|
||||
fonts: ['font-awesome-4.7.0/fonts/fontawesome-webfont.woff2'],
|
||||
},
|
||||
{
|
||||
folder: 'inter',
|
||||
url: 'https://github.com/rsms/inter/releases/tag/v4.1',
|
||||
archive:
|
||||
'https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip',
|
||||
fonts: ['web/Inter-Regular.woff2', 'web/Inter-SemiBold.woff2'],
|
||||
license:
|
||||
'https://raw.githubusercontent.com/rsms/inter/refs/heads/master/LICENSE.txt',
|
||||
},
|
||||
{
|
||||
folder: 'lato',
|
||||
url: 'https://www.latofonts.com/',
|
||||
archive: 'https://www.latofonts.com/download/lato2oflweb-zip/',
|
||||
archive: 'https://www.latofonts.com/files/Lato2OFLWeb.zip',
|
||||
fonts: [
|
||||
'Lato2OFLWeb/LatoLatin/fonts/LatoLatin-Bold.woff2',
|
||||
'Lato2OFLWeb/LatoLatin/fonts/LatoLatin-BoldItalic.woff2',
|
||||
|
||||
BIN
services/web/frontend/fonts/inter/Inter-Regular.woff2
Normal file
BIN
services/web/frontend/fonts/inter/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
services/web/frontend/fonts/inter/Inter-SemiBold.woff2
Normal file
BIN
services/web/frontend/fonts/inter/Inter-SemiBold.woff2
Normal file
Binary file not shown.
92
services/web/frontend/fonts/inter/LICENSE.txt
Normal file
92
services/web/frontend/fonts/inter/LICENSE.txt
Normal file
@@ -0,0 +1,92 @@
|
||||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION AND CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
14
services/web/frontend/fonts/inter/inter.css
Normal file
14
services/web/frontend/fonts/inter/inter.css
Normal file
@@ -0,0 +1,14 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('Inter-Regular.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('Inter-SemiBold.woff2') format('woff2');
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
let hasHandledAutoplayFailure = false
|
||||
|
||||
function enableControls(videoEl, reason) {
|
||||
debugConsole.log(`Enabling video controls: ${reason}`)
|
||||
videoEl.setAttribute('controls', '')
|
||||
}
|
||||
|
||||
function setup(videoEl) {
|
||||
const reducedMotionReduce = window.matchMedia(
|
||||
'(prefers-reduced-motion: reduce)'
|
||||
@@ -10,7 +17,7 @@ function setup(videoEl) {
|
||||
// in console, if user seeks the control seek bar relatively fast
|
||||
// AbortError: The fetching process for the media resource was aborted by the user agent at the user's request.
|
||||
// this is only a problem in firefox (tested in macOS), chrome and safari is fine
|
||||
videoEl.setAttribute('controls', '')
|
||||
enableControls(videoEl, 'reduced motion preference')
|
||||
|
||||
return
|
||||
}
|
||||
@@ -21,11 +28,29 @@ function setup(videoEl) {
|
||||
|
||||
let videoIsVisible
|
||||
|
||||
function handleAutoplayFailure(error) {
|
||||
// Possible HTMLMediaElement.play() errors
|
||||
// Only show controls for errors where manual play would work
|
||||
// NotAllowedError: autoplay blocked by browser/device settings
|
||||
// AbortError: play attempt interrupted by browser
|
||||
if (error.name === 'NotAllowedError' || error.name === 'AbortError') {
|
||||
if (!hasHandledAutoplayFailure) {
|
||||
hasHandledAutoplayFailure = true
|
||||
|
||||
document.querySelectorAll('[data-ol-autoplay-video]').forEach(video => {
|
||||
enableControls(video, `autoplay blocked (${error.name})`)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
debugConsole.error('Video playback error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
videoEl.addEventListener('ended', () => {
|
||||
setTimeout(() => {
|
||||
videoEl.currentTime = 0
|
||||
if (videoIsVisible) {
|
||||
videoEl.play()
|
||||
if (videoIsVisible && !hasHandledAutoplayFailure) {
|
||||
videoEl.play().catch(handleAutoplayFailure)
|
||||
}
|
||||
}, DELAY_BEFORE_REPLAY)
|
||||
})
|
||||
@@ -35,23 +60,18 @@ function setup(videoEl) {
|
||||
for (const change of changes) {
|
||||
if (change.isIntersecting) {
|
||||
videoIsVisible = true
|
||||
if (videoEl.readyState >= videoEl.HAVE_FUTURE_DATA) {
|
||||
if (!videoEl.ended) {
|
||||
videoEl
|
||||
.play()
|
||||
.catch(error =>
|
||||
debugConsole.error('Video autoplay failed:', error)
|
||||
)
|
||||
} else {
|
||||
videoEl
|
||||
.play()
|
||||
.catch(error =>
|
||||
debugConsole.error('Video autoplay failed:', error)
|
||||
)
|
||||
}
|
||||
if (
|
||||
!hasHandledAutoplayFailure &&
|
||||
videoEl.readyState >= videoEl.HAVE_FUTURE_DATA
|
||||
) {
|
||||
videoEl.play().catch(handleAutoplayFailure)
|
||||
}
|
||||
} else {
|
||||
videoIsVisible = false
|
||||
// Pause video when it leaves viewport to save resources
|
||||
if (!videoEl.paused) {
|
||||
videoEl.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -223,7 +223,7 @@ export default function DropdownButton({
|
||||
key="unlink-user-action"
|
||||
data-testid="unlink-user-action"
|
||||
>
|
||||
{t('unlink_user')}
|
||||
{t('unlink_from_sso')}
|
||||
</MenuItemButton>
|
||||
)
|
||||
}
|
||||
@@ -250,7 +250,7 @@ export default function DropdownButton({
|
||||
data-testid="delete-user-action"
|
||||
onClick={onDeleteUserClick}
|
||||
>
|
||||
{t('delete_user')}
|
||||
{t('delete_permanently')}
|
||||
</MenuItemButton>
|
||||
)
|
||||
buttons.push(
|
||||
@@ -259,7 +259,7 @@ export default function DropdownButton({
|
||||
data-testid="release-user-action"
|
||||
onClick={onReleaseUserClick}
|
||||
>
|
||||
{t('remove_user')}
|
||||
{t('remove_from_group')}
|
||||
</MenuItemButton>
|
||||
)
|
||||
} else if (!isUserManaged) {
|
||||
|
||||
@@ -85,7 +85,7 @@ export default function UnlinkUserModal({
|
||||
return (
|
||||
<OLModal show onHide={onClose}>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('unlink_user')}</OLModalTitle>
|
||||
<OLModalTitle>{t('unlink_from_sso')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>
|
||||
{hasError && (
|
||||
@@ -121,7 +121,7 @@ export default function UnlinkUserModal({
|
||||
onClick={e => handleUnlink(e)}
|
||||
disabled={unlinkInFlight}
|
||||
>
|
||||
{t('unlink_user')}
|
||||
{t('unlink_from_sso')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import bannerImage from '../../../images/brl-banner.png'
|
||||
import usePersistedState from '../../../../../shared/hooks/use-persisted-state'
|
||||
import * as eventTracking from '../../../../../infrastructure/event-tracking'
|
||||
import {
|
||||
OLModal,
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/shared/components/ol/ol-modal'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function BRLBanner() {
|
||||
const { t } = useTranslation()
|
||||
const [dismissedUntil, setDismissedUntil] = usePersistedState<
|
||||
Date | undefined
|
||||
>(`has_dismissed_brl_banner_until`)
|
||||
const viewEventSent = useRef<boolean>(false)
|
||||
|
||||
const [showModal, setShowModal] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (dismissedUntil && new Date(dismissedUntil) > new Date()) {
|
||||
return
|
||||
}
|
||||
if (!viewEventSent.current) {
|
||||
eventTracking.sendMB('promo-prompt', {
|
||||
location: 'dashboard-modal',
|
||||
name: 'geo-pricing',
|
||||
page: '/project',
|
||||
content: 'modal',
|
||||
country: 'BR',
|
||||
})
|
||||
viewEventSent.current = true
|
||||
}
|
||||
}, [dismissedUntil])
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
eventTracking.sendMB('promo-click', {
|
||||
location: 'dashboard-modal',
|
||||
name: 'geo-pricing',
|
||||
page: '/project',
|
||||
content: 'modal',
|
||||
country: 'BR',
|
||||
type: 'click',
|
||||
})
|
||||
|
||||
setShowModal(false)
|
||||
|
||||
window.open('/user/subscription/plans')
|
||||
}, [])
|
||||
|
||||
const bannerDismissed = useCallback(() => {
|
||||
eventTracking.sendMB('promo-dismiss', {
|
||||
location: 'dashboard-modal',
|
||||
name: 'geo-pricing',
|
||||
page: '/project',
|
||||
content: 'modal',
|
||||
country: 'BR',
|
||||
})
|
||||
const until = new Date()
|
||||
until.setDate(until.getDate() + 30) // 30 days
|
||||
setDismissedUntil(until)
|
||||
}, [setDismissedUntil])
|
||||
|
||||
const handleHide = useCallback(() => {
|
||||
setShowModal(false)
|
||||
bannerDismissed()
|
||||
}, [bannerDismissed])
|
||||
|
||||
const handleMaybeLater = useCallback(() => {
|
||||
eventTracking.sendMB('promo-click', {
|
||||
location: 'dashboard-modal',
|
||||
name: 'geo-pricing',
|
||||
page: '/project',
|
||||
content: 'modal',
|
||||
country: 'BR',
|
||||
type: 'pause',
|
||||
})
|
||||
setShowModal(false)
|
||||
const until = new Date()
|
||||
until.setDate(until.getDate() + 1) // 1 day
|
||||
setDismissedUntil(until)
|
||||
}, [setDismissedUntil])
|
||||
|
||||
if (dismissedUntil && new Date(dismissedUntil) > new Date()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<OLModal show={showModal} onHide={handleHide}>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('latam_discount_modal_title')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>
|
||||
<p>
|
||||
<img
|
||||
alt={t('latam_discount_modal_title')}
|
||||
src={bannerImage}
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
{t('latam_discount_modal_info', {
|
||||
discount: '50',
|
||||
currencyName: 'Brazilian Reais',
|
||||
})}
|
||||
</p>
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton variant="secondary" onClick={handleMaybeLater}>
|
||||
{t('maybe_later')}
|
||||
</OLButton>
|
||||
<OLButton variant="primary" onClick={handleClick}>
|
||||
{t('get_discounted_plan')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import getMeta from '@/utils/meta'
|
||||
import BRLBanner from './ads/brl-banner'
|
||||
import INRBanner from './ads/inr-banner'
|
||||
import LATAMBanner from './ads/latam-banner'
|
||||
|
||||
function GeoBanners() {
|
||||
const showInrGeoBanner = getMeta('ol-showInrGeoBanner')
|
||||
const showBrlGeoBanner = getMeta('ol-showBrlGeoBanner')
|
||||
const showLATAMBanner = getMeta('ol-showLATAMBanner')
|
||||
return (
|
||||
<>
|
||||
{showBrlGeoBanner && <BRLBanner />}
|
||||
{showLATAMBanner && <LATAMBanner />}
|
||||
{showInrGeoBanner && <INRBanner />}
|
||||
</>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB |
@@ -19,6 +19,7 @@ import {
|
||||
import MathPreviewTooltip from './math-preview-tooltip'
|
||||
import { useToolbarMenuBarEditorCommands } from '@/features/ide-redesign/hooks/use-toolbar-menu-editor-commands'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
|
||||
// TODO: remove this when definitely no longer used
|
||||
export * from './codemirror-context'
|
||||
@@ -34,12 +35,15 @@ function CodeMirrorEditor() {
|
||||
})
|
||||
|
||||
const isMounted = useIsMounted()
|
||||
const editContextEnabled = useFeatureFlag('edit-context')
|
||||
|
||||
// create the view using the initial state and intercept transactions
|
||||
const viewRef = useRef<EditorView | null>(null)
|
||||
if (viewRef.current === null) {
|
||||
// @ts-ignore (disable EditContext-based editing until stable)
|
||||
EditorView.EDIT_CONTEXT = false
|
||||
if (!editContextEnabled) {
|
||||
// @ts-expect-error (disable EditContext-based editing until stable)
|
||||
EditorView.EDIT_CONTEXT = false
|
||||
}
|
||||
|
||||
const view = new EditorView({
|
||||
state,
|
||||
|
||||
@@ -10,6 +10,8 @@ import { WordCountServer } from './word-count-server'
|
||||
import { WordCountClient } from './word-count-client'
|
||||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||
import SplitTestBadge from '@/shared/components/split-test-badge'
|
||||
import { useEffect } from 'react'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
|
||||
// NOTE: this component is only mounted when the modal is open
|
||||
export default function WordCountModalContent({
|
||||
@@ -19,6 +21,15 @@ export default function WordCountModalContent({
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
|
||||
useEffect(() => {
|
||||
// record when the word count modal is opened
|
||||
sendEvent('word-count-opened', {
|
||||
mode: isSplitTestEnabled('word-count-client') ? 'client' : 'server',
|
||||
})
|
||||
}, [sendEvent])
|
||||
|
||||
return (
|
||||
<>
|
||||
<OLModalHeader>
|
||||
|
||||
@@ -28,7 +28,8 @@ const en = {
|
||||
'ask-texgpt': 'Ask TeXGPT',
|
||||
'synonyms-in-context': 'Synonyms in Context',
|
||||
translate: 'Translate',
|
||||
'translate.zh': 'Chinese',
|
||||
'translate.zh-CN': 'Chinese (Simplified)',
|
||||
'translate.zh-HANT': 'Chinese (Traditional)',
|
||||
'translate.en': 'English',
|
||||
'translate.fr': 'French',
|
||||
'translate.hi': 'Hindi',
|
||||
@@ -46,7 +47,7 @@ const en = {
|
||||
'translate.hu': 'Hungarian',
|
||||
'translate.it': 'Italian',
|
||||
'translate.ms': 'Malay',
|
||||
'translate.fa': 'Persian(Farsi)',
|
||||
'translate.fa': 'Persian (Farsi)',
|
||||
'translate.pl': 'Polish',
|
||||
'translate.ro': 'Romanian',
|
||||
'translate.ru': 'Russian',
|
||||
@@ -55,11 +56,22 @@ const en = {
|
||||
'translate.tr': 'Turkish',
|
||||
'translate.uk': 'Ukrainian',
|
||||
'translate.vi': 'Vietnamese',
|
||||
'translate.pt': 'Portuguese',
|
||||
'translate.ko': 'Korean',
|
||||
'translate.id': 'Indonesian',
|
||||
'translate.other': 'Other',
|
||||
'translate.not-listed': 'Language not listed?',
|
||||
'translate.more-languages-coming-soon.title': 'More languages coming soon',
|
||||
'translate.more-languages-coming-soon.body':
|
||||
'Sorry, we don’t currently offer any other languages for Translate. We will be adding more throughout November so watch this space!',
|
||||
'translate.request-new-language.title': 'Request a Language',
|
||||
'translate.request-new-language.body':
|
||||
"Sorry, we don't currently offer any other languages. Tell us what language you wanted to use and we will review whether we can add it in the future.",
|
||||
'translate.request-new-language.submit': 'Request',
|
||||
'translate.request-new-language.thanks.title':
|
||||
'Thank you for your language request',
|
||||
'translate.request-new-language.thanks.body':
|
||||
'We’ll review your request and consider adding it in the future',
|
||||
'blocked-suggestion-signpost.question':
|
||||
'Do you want to permanently stop this suggestion from appearing again?',
|
||||
'blocked-suggestion-signpost.tooltip': 'You can block a suggestion here.',
|
||||
@@ -390,7 +402,8 @@ const es = {
|
||||
'ask-texgpt': 'Preguntar a TeXGPT',
|
||||
'synonyms-in-context': 'Sinónimos en Contexto',
|
||||
translate: 'Traducir',
|
||||
'translate.zh': 'Chino',
|
||||
'translate.zh-CN': 'Chino (Simplificado)',
|
||||
'translate.zh-HANT': 'Chino (Tradicional)',
|
||||
'translate.en': 'Inglés',
|
||||
'translate.fr': 'Francés',
|
||||
'translate.hi': 'Hindi',
|
||||
@@ -417,11 +430,22 @@ const es = {
|
||||
'translate.tr': 'Turco',
|
||||
'translate.uk': 'Ucraniano',
|
||||
'translate.vi': 'Vietnamita',
|
||||
'translate.pt': 'Portugués',
|
||||
'translate.ko': 'Coreano',
|
||||
'translate.id': 'Indonesio',
|
||||
'translate.other': 'Otro',
|
||||
'translate.not-listed': '¿Idioma no listado?',
|
||||
'translate.more-languages-coming-soon.title': 'Más idiomas próximamente',
|
||||
'translate.more-languages-coming-soon.body':
|
||||
'Lo sentimos, actualmente no ofrecemos otros idiomas para la traducción. Agregaremos más a lo largo de noviembre, ¡así que mantente atento!',
|
||||
'translate.request-new-language.title': 'Solicitar un nuevo idioma',
|
||||
'translate.request-new-language.body':
|
||||
'Lo sentimos, actualmente no ofrecemos otros idiomas. Dinos qué idioma querías usar y revisaremos si podemos agregarlo en el futuro.',
|
||||
'translate.request-new-language.submit': 'Solicitar',
|
||||
'translate.request-new-language.thanks.title':
|
||||
'Gracias por tu solicitud de idioma',
|
||||
'translate.request-new-language.thanks.body':
|
||||
'Revisaremos tu solicitud y consideraremos agregarlo en el futuro',
|
||||
'blocked-suggestion-signpost.question':
|
||||
'¿Quieres dejar de ver esta sugerencia permanentemente? Puedes bloquear esta sugerencia aquí.',
|
||||
'blocked-suggestion-signpost.tooltip':
|
||||
|
||||
@@ -253,7 +253,6 @@ export interface Meta {
|
||||
'ol-shouldAllowEditingDetails': boolean
|
||||
'ol-shouldLoadHotjar': boolean
|
||||
'ol-showAiErrorAssistant': boolean
|
||||
'ol-showBrlGeoBanner': boolean
|
||||
'ol-showCouponField': boolean
|
||||
'ol-showGroupDiscount': boolean
|
||||
'ol-showGroupsAndEnterpriseBanner': boolean
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Web fonts
|
||||
@import '../fonts/noto-sans/noto-sans.css';
|
||||
@import '../fonts/inter/inter.css';
|
||||
@import '../fonts/dm-mono/dm-mono.css';
|
||||
@import '../fonts/merriweather/merriweather.css';
|
||||
@import '../fonts/source-code-pro/source-code-pro.css';
|
||||
|
||||
@@ -1016,7 +1016,7 @@
|
||||
"log_in_with": "Log ind med __provider__",
|
||||
"log_in_with_a_different_account": "Log ind med en anden konto",
|
||||
"log_in_with_email": "Log ind med __email__",
|
||||
"log_in_with_existing_institution_email": "For at forbinde din <b>__appName__</b>- og din <b>__institutionName__</b>-konto, er du nødt til først at logge ind med din eksisterende <b>__appName__</b>-konto.",
|
||||
"log_in_with_existing_institution_email": "For at forbinde din <0>__appName__</0>- og din <0>__institutionName__</0>-konto, er du nødt til først at logge ind med din eksisterende <0>__appName__</0>-konto.",
|
||||
"log_in_with_primary_email_address": "Dette bliver e-mailaddressen du skal bruge hvis du logger ind med e-mailaddresse og kode. Vigtige __appName__ beskeder vil blive sendt til denne adresse.",
|
||||
"log_in_with_sso": "Log ind med SSO",
|
||||
"log_in_with_sso_email": "Arbejde- eller universitets-e-mailaddresse",
|
||||
@@ -2003,7 +2003,6 @@
|
||||
"unlink_provider_account_warning": "Advarsel: Når du afkobler din konto fra __provider__ kan du ikke længere logge ind igennem __provider__.",
|
||||
"unlink_reference": "Fjern link til reference udbyder",
|
||||
"unlink_the_project_from_the_current_github_repo": "Afkobl projektet fra det nuværende GitHub repository og opret en forbindelse til et repository du ejer. (Der kræves et aktivt __appName__ abonnement for at sætte GitHub synkronisering op).",
|
||||
"unlink_user": "Afkobl bruger",
|
||||
"unlink_users": "Afkobl brugere",
|
||||
"unlink_warning_reference": "Advarsel: Når du fjerner linket til denne udbyder fra din konto, vil du ikke længere have mulighed for at importere referencer ind i dine projekter.",
|
||||
"unlinking": "Fjerner forbindelse",
|
||||
|
||||
@@ -744,7 +744,7 @@
|
||||
"log_in_first_to_proceed": "Du musst dich zuerst <b>anmelden</b>, um fortzufahren.",
|
||||
"log_in_with": "Einloggen mit __provider__",
|
||||
"log_in_with_email": "Melde dich mit __email__ an",
|
||||
"log_in_with_existing_institution_email": "Bitte melde dich mit deinem bestehenden <b>__appName__</b>-Konto an, um deine institutionellen Konten <b>__appName__</b> und <b>__institutionName__</b> zu verknüpfen.",
|
||||
"log_in_with_existing_institution_email": "Bitte melde dich mit deinem bestehenden <0>__appName__</0>-Konto an, um deine institutionellen Konten <0>__appName__</0> und <0>__institutionName__</0> zu verknüpfen.",
|
||||
"log_out": "Abmelden",
|
||||
"log_out_from": "Von __email__ abmelden",
|
||||
"log_viewer_error": "Beim Anzeigen der Kompilierfehler und -protokolle dieses Projekts ist ein Problem aufgetreten.",
|
||||
|
||||
@@ -252,7 +252,6 @@
|
||||
"bold": "Bold",
|
||||
"booktabs": "Booktabs",
|
||||
"breadcrumbs": "Breadcrumbs",
|
||||
"brl_discount_offer_plans_page_banner": "__flag__ <b>Great news!</b> We’ve applied a 50% discount to premium plans on this page for our users in Brazil. Check out the new lower prices.",
|
||||
"browser": "Browser",
|
||||
"built_in": "Built-In",
|
||||
"bullet_list": "Bullet list",
|
||||
@@ -535,6 +534,7 @@
|
||||
"delete_figure": "Delete figure",
|
||||
"delete_message": "Delete message",
|
||||
"delete_message_confirmation": "Are you sure you want to delete this message? This can’t be undone.",
|
||||
"delete_permanently": "Delete permanently",
|
||||
"delete_projects": "Delete Projects",
|
||||
"delete_row_or_column": "Delete row or column",
|
||||
"delete_sso_config": "Delete SSO configuration",
|
||||
@@ -1304,7 +1304,7 @@
|
||||
"log_in_with": "Log in with __provider__",
|
||||
"log_in_with_a_different_account": "Log in with a different account",
|
||||
"log_in_with_email": "Log in with __email__",
|
||||
"log_in_with_existing_institution_email": "Please log in with your existing <b>__appName__</b> account in order to get your <b>__appName__</b> and <b>__institutionName__</b> institutional accounts linked.",
|
||||
"log_in_with_existing_institution_email": "Please log in with your existing <0>__appName__</0> account in order to get your <0>__appName__</0> and <0>__institutionName__</0> institutional accounts linked.",
|
||||
"log_in_with_primary_email_address": "This will be the email address to use if you log in with an email address and password. Important __appName__ notifications will be sent to this email address.",
|
||||
"log_in_with_sso": "Log in with SSO",
|
||||
"log_in_with_sso_email": "Work or university email address",
|
||||
@@ -2565,6 +2565,7 @@
|
||||
"unlink_all_users_explanation": "You’re about to remove the SSO login option for all users in your group. If SSO is enabled, this will force users to reauthenticate their Overleaf accounts with your IdP. They’ll receive an email asking them to do this.",
|
||||
"unlink_dropbox_folder": "Unlink Dropbox Account",
|
||||
"unlink_dropbox_warning": "Any projects that you have synced with Dropbox will be disconnected and no longer kept in sync with Dropbox. Are you sure you want to unlink your Dropbox account?",
|
||||
"unlink_from_sso": "Unlink from SSO",
|
||||
"unlink_github_repository": "Unlink GitHub repository",
|
||||
"unlink_github_warning": "Any projects that you have synced with GitHub will be disconnected and no longer kept in sync with GitHub. Are you sure you want to unlink your GitHub account?",
|
||||
"unlink_linked_accounts": "Unlink any linked accounts (such as ORCID ID, IEEE). <0>Remove them in Account settings (under Linked Accounts).</0>",
|
||||
@@ -2573,7 +2574,6 @@
|
||||
"unlink_provider_account_warning": "Warning: When you unlink your account from __provider__ you will not be able to sign in using __provider__ anymore.",
|
||||
"unlink_reference": "Unlink References Provider",
|
||||
"unlink_the_project_from_the_current_github_repo": "Unlink the project from the current GitHub repository and create a connection to a repository you own. (You need an active __appName__ subscription to set up a GitHub Sync).",
|
||||
"unlink_user": "Unlink user",
|
||||
"unlink_user_explanation": "You’re about to remove the SSO login option for <0>__email__</0>. This will force them to reauthenticate their Overleaf account with your IdP. They’ll receive an email asking them to do this.",
|
||||
"unlink_users": "Unlink users",
|
||||
"unlink_warning_reference": "Warning: When you unlink your account from this provider you will not be able to import references into your projects.",
|
||||
|
||||
@@ -170,7 +170,6 @@
|
||||
"blank_project": "Proyecto vacío",
|
||||
"blocked_filename": "Este nombre de archivo está bloqueado.",
|
||||
"blog": "Blog",
|
||||
"brl_discount_offer_plans_page_banner": "__flag__ <b>¡Grandes noticias!</b> Hemos aplicado un 50% de descuento a los planes premium de esta página para nuestros usuarios en Brasil. Echa un vistazo a los nuevos precios más bajos.",
|
||||
"browser": "Navegador",
|
||||
"built_in": "Integrado",
|
||||
"buy_now_no_exclamation_mark": "Comprar ahora",
|
||||
|
||||
@@ -688,7 +688,7 @@
|
||||
"log_in_first_to_proceed": "Vous aurez besoin de <b>vous connecter</b> avant de poursuivre.",
|
||||
"log_in_with": "Se connecter avec __provider__",
|
||||
"log_in_with_email": "Se connecter avec __email__",
|
||||
"log_in_with_existing_institution_email": "Veuillez vous connecter sur votre compte <b>__appName__</b> existant afin de lier vos comptes institutionnels <b>__appName__</b> et <b>__institutionName__</b>.",
|
||||
"log_in_with_existing_institution_email": "Veuillez vous connecter sur votre compte <0>__appName__</0> existant afin de lier vos comptes institutionnels <0>__appName__</0> et <0>__institutionName__</0>.",
|
||||
"log_out": "Déconnexion",
|
||||
"log_out_from": "Se déconnecter de __email__",
|
||||
"logged_in_with_email": "Vous êtes actuellement connecté à <b>__appName__</b> avec l’adresse <b>__email__</b>.",
|
||||
|
||||
@@ -72,7 +72,6 @@
|
||||
"bibliographies": "Bibliografia",
|
||||
"blank_project": "Projeto Em Branco",
|
||||
"blog": "Blog",
|
||||
"brl_discount_offer_plans_page_banner": "__flag__ Aplicamos um desconto de 50% aos planos premium nesta página para nossos usuários no Brasil. Confira os novos preços mais baixos.",
|
||||
"built_in": "Embutido",
|
||||
"by": "por",
|
||||
"cancel": "Cancelar",
|
||||
|
||||
@@ -451,7 +451,7 @@
|
||||
"log_in_first_to_proceed": "Du måste först <b>logga in</b> för att fortsätta.",
|
||||
"log_in_with": "Logga in med __provider__",
|
||||
"log_in_with_email": "Logga in med __email__",
|
||||
"log_in_with_existing_institution_email": "Vänligen logga in med ditt befintliga <b>__appName__</b>-konto för att länka kontot <b>__appName__</b> och det institutionella kontot <b>__institutionName__ </b>.",
|
||||
"log_in_with_existing_institution_email": "Vänligen logga in med ditt befintliga <0>__appName__</0>-konto för att länka kontot <0>__appName__</0> och det institutionella kontot <0>__institutionName__ </0>.",
|
||||
"log_out": "Logga ut",
|
||||
"log_out_from": "Logga ut från __email__",
|
||||
"logged_in_with_email": "Du är för närvarande inloggad i <b>__appName__</b> med e-postadressen <b>__email__</b>.",
|
||||
|
||||
@@ -231,7 +231,6 @@
|
||||
"blocked_filename": "此文件名被阻止。",
|
||||
"blog": "博客",
|
||||
"bold": "粗体",
|
||||
"brl_discount_offer_plans_page_banner": "__flag__<b>好消息</b> 我们为巴西用户在本页面上的高级计划提供了50%的折扣。看看新的低价。",
|
||||
"browser": "浏览器",
|
||||
"built_in": "内嵌",
|
||||
"bullet_list": "项目符号列表",
|
||||
@@ -1214,7 +1213,7 @@
|
||||
"log_in_with": "用 __provider__ 账户登陆",
|
||||
"log_in_with_a_different_account": "以另外一个账户登录",
|
||||
"log_in_with_email": "使用 __email__ 登录",
|
||||
"log_in_with_existing_institution_email": "请使用您现有的 <b>__appName__</b> 帐户登录,以便将您的<b>__appName__</b>和<b>__institutionName__</b> 机构帐户关联起来。",
|
||||
"log_in_with_existing_institution_email": "请使用您现有的 <0>__appName__</0> 帐户登录,以便将您的<0>__appName__</0>和<0>__institutionName__</0> 机构帐户关联起来。",
|
||||
"log_in_with_primary_email_address": "如果您使用电子邮件地址和密码登录,这将是要使用的电子邮件地址。 重要的 __appName__ 通知将发送到此电子邮件地址。",
|
||||
"log_in_with_sso": "通过 SSO 登录",
|
||||
"log_in_with_sso_email": "工作或大学电子邮件账户",
|
||||
@@ -2396,7 +2395,6 @@
|
||||
"unlink_provider_account_warning": "警告:当您取消帐户与 __provider__ 的链接后,您将无法再使用 __provider__ 登录。",
|
||||
"unlink_reference": "取消关联参考文献提供者",
|
||||
"unlink_the_project_from_the_current_github_repo": "取消项目与当前 GitHub 存储库的链接,并创建与您拥有的存储库的连接。 (您需要有效的 __appName__ 订阅才能设置 GitHub 同步)。",
|
||||
"unlink_user": "取消链接用户",
|
||||
"unlink_user_explanation": "您即将删除 <0>__email__</0> 的 SSO 登录选项。 这将迫使他们向您的 IdP 重新验证其 Overleaf 帐户。 他们会收到一封电子邮件,要求他们这样做。",
|
||||
"unlink_users": "取消用户链接",
|
||||
"unlink_warning_reference": "警告:如果将账户与此提供者取消关联,您将无法把参考文献导入到项目中。",
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
"express-bearer-token": "^2.4.0",
|
||||
"express-http-proxy": "^1.6.0",
|
||||
"express-session": "^1.17.1",
|
||||
"file-type": "^21.0.0",
|
||||
"globby": "^5.0.0",
|
||||
"helmet": "^6.0.1",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
|
||||
@@ -274,7 +274,7 @@ describe('MembersList', function () {
|
||||
|
||||
it('should show successs notification and update the user row after unlinking', function () {
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('button', { name: /unlink user/i }).click()
|
||||
cy.findByRole('button', { name: /unlink from sso/i }).click()
|
||||
})
|
||||
cy.findByRole('alert').should(
|
||||
'contain.text',
|
||||
@@ -314,7 +314,7 @@ describe('MembersList', function () {
|
||||
|
||||
it('should show successs notification and update the user row after unlinking', function () {
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('button', { name: /unlink user/i }).click()
|
||||
cy.findByRole('button', { name: /unlink from sso/i }).click()
|
||||
})
|
||||
cy.findByRole('alert').should(
|
||||
'contain.text',
|
||||
@@ -347,7 +347,7 @@ describe('MembersList', function () {
|
||||
|
||||
it('should show successs notification and update the user row after unlinking', function () {
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('button', { name: /unlink user/i }).click()
|
||||
cy.findByRole('button', { name: /unlink from sso/i }).click()
|
||||
})
|
||||
cy.findByRole('alert').should(
|
||||
'contain.text',
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('<UnlinkUserModal />', function () {
|
||||
it('displays the modal', async function () {
|
||||
renderWithContext(<UnlinkUserModal {...defaultProps} />)
|
||||
await screen.findByRole('heading', {
|
||||
name: 'Unlink user',
|
||||
name: 'Unlink from SSO',
|
||||
})
|
||||
screen.getByText('You’re about to remove the SSO login option for', {
|
||||
exact: false,
|
||||
@@ -47,10 +47,12 @@ describe('<UnlinkUserModal />', function () {
|
||||
|
||||
renderWithContext(<UnlinkUserModal {...defaultProps} />)
|
||||
await screen.findByRole('heading', {
|
||||
name: 'Unlink user',
|
||||
name: 'Unlink from SSO',
|
||||
})
|
||||
|
||||
const confirmButton = screen.getByRole('button', { name: 'Unlink user' })
|
||||
const confirmButton = screen.getByRole('button', {
|
||||
name: 'Unlink from SSO',
|
||||
})
|
||||
fireEvent.click(confirmButton)
|
||||
|
||||
await waitFor(() => expect(defaultProps.onClose).to.have.been.called)
|
||||
@@ -70,10 +72,12 @@ describe('<UnlinkUserModal />', function () {
|
||||
|
||||
renderWithContext(<UnlinkUserModal {...defaultProps} />)
|
||||
await screen.findByRole('heading', {
|
||||
name: 'Unlink user',
|
||||
name: 'Unlink from SSO',
|
||||
})
|
||||
|
||||
const confirmButton = screen.getByRole('button', { name: 'Unlink user' })
|
||||
const confirmButton = screen.getByRole('button', {
|
||||
name: 'Unlink from SSO',
|
||||
})
|
||||
fireEvent.click(confirmButton)
|
||||
|
||||
await waitFor(() => screen.findByText('Sorry, something went wrong'))
|
||||
|
||||
@@ -55,17 +55,17 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'',
|
||||
'n2d'
|
||||
'c3d'
|
||||
)
|
||||
ctx.redis.get
|
||||
.calledWith(`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`)
|
||||
.calledWith(`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`)
|
||||
.should.equal(true)
|
||||
serverId.should.equal('clsi-7')
|
||||
})
|
||||
|
||||
it('should fallback to old key', async function (ctx) {
|
||||
ctx.redis.get
|
||||
.withArgs(`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`)
|
||||
.withArgs(`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`)
|
||||
.resolves(null)
|
||||
ctx.redis.get
|
||||
.withArgs(`clsiserver:${ctx.project_id}:${ctx.user_id}`)
|
||||
@@ -74,10 +74,10 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'',
|
||||
'n2d'
|
||||
'c3d'
|
||||
)
|
||||
ctx.redis.get
|
||||
.calledWith(`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`)
|
||||
.calledWith(`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`)
|
||||
.should.equal(true)
|
||||
ctx.redis.get
|
||||
.calledWith(`clsiserver:${ctx.project_id}:${ctx.user_id}`)
|
||||
@@ -109,7 +109,7 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'',
|
||||
'n2d'
|
||||
'c3d'
|
||||
)
|
||||
ctx.ClsiCookieManager.promises._populateServerIdViaRequest
|
||||
.calledWith(ctx.project_id, ctx.user_id)
|
||||
@@ -140,13 +140,13 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d'
|
||||
'c3d'
|
||||
)
|
||||
const args = ctx.ClsiCookieManager.promises.setServerId.args[0]
|
||||
args[0].should.equal(ctx.project_id)
|
||||
args[1].should.equal(ctx.user_id)
|
||||
args[2].should.equal('standard')
|
||||
args[3].should.equal('n2d')
|
||||
args[3].should.equal('c3d')
|
||||
args[4].should.deep.equal(ctx.clsiServerId)
|
||||
})
|
||||
|
||||
@@ -156,7 +156,7 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'',
|
||||
'n2d'
|
||||
'c3d'
|
||||
)
|
||||
serverId.should.equal(ctx.clsiServerId)
|
||||
})
|
||||
@@ -175,7 +175,7 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
'c3d',
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
@@ -189,10 +189,10 @@ describe('ClsiCookieManager', function () {
|
||||
await ctx.ClsiCookieManager.promises.clearServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'n2d'
|
||||
'c3d'
|
||||
)
|
||||
ctx.redis.del.should.have.been.calledWith(
|
||||
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
|
||||
`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`,
|
||||
`clsiserver:${ctx.project_id}:${ctx.user_id}`
|
||||
)
|
||||
})
|
||||
@@ -211,12 +211,12 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
'c3d',
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
ctx.redis.setex.should.have.been.calledWith(
|
||||
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
|
||||
`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`,
|
||||
ctx.settings.clsiCookie.ttlInSeconds,
|
||||
ctx.clsiServerId
|
||||
)
|
||||
@@ -228,12 +228,12 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
'c3d',
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
expect(ctx.redis.setex).to.have.been.calledWith(
|
||||
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
|
||||
`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`,
|
||||
ctx.settings.clsiCookie.ttlInSecondsRegular,
|
||||
ctx.clsiServerId
|
||||
)
|
||||
@@ -256,7 +256,7 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
'c3d',
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
@@ -281,12 +281,12 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
'c3d',
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
ctx.redis_secondary.setex.should.have.been.calledWith(
|
||||
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
|
||||
`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`,
|
||||
ctx.settings.clsiCookie.ttlInSeconds,
|
||||
ctx.clsiServerId
|
||||
)
|
||||
@@ -300,14 +300,14 @@ describe('ClsiCookieManager', function () {
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
'c3d',
|
||||
ctx.clsiServerId,
|
||||
'previous-clsi-server-id'
|
||||
)
|
||||
expect(
|
||||
ctx.fetchUtils.fetchStringWithResponse
|
||||
).to.have.been.calledWith(
|
||||
`${ctx.settings.apis.clsi.url}/instance-state?clsiserverid=previous-clsi-server-id&compileGroup=standard&compileBackendClass=n2d`,
|
||||
`${ctx.settings.apis.clsi.url}/instance-state?clsiserverid=previous-clsi-server-id&compileGroup=standard&compileBackendClass=c3d`,
|
||||
{ method: 'GET', signal: sinon.match.instanceOf(AbortSignal) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ describe('ClsiManager', function () {
|
||||
},
|
||||
clsi: {
|
||||
url: `http://${CLSI_HOST}`,
|
||||
submissionBackendClass: 'n2d',
|
||||
submissionBackendClass: 'c3d',
|
||||
},
|
||||
clsi_new: {
|
||||
sample: 100,
|
||||
@@ -280,7 +280,7 @@ describe('ClsiManager', function () {
|
||||
ctx.project._id,
|
||||
ctx.user_id,
|
||||
{
|
||||
compileBackendClass: 'n2d',
|
||||
compileBackendClass: 'c3d',
|
||||
compileGroup: 'standard',
|
||||
timeout: ctx.timeout,
|
||||
}
|
||||
@@ -294,7 +294,7 @@ describe('ClsiManager', function () {
|
||||
url.host === CLSI_HOST &&
|
||||
url.pathname ===
|
||||
`/project/${ctx.project._id}/user/${ctx.user_id}/compile` &&
|
||||
url.searchParams.get('compileBackendClass') === 'n2d' &&
|
||||
url.searchParams.get('compileBackendClass') === 'c3d' &&
|
||||
url.searchParams.get('compileGroup') === 'standard'
|
||||
),
|
||||
{
|
||||
@@ -374,7 +374,7 @@ describe('ClsiManager', function () {
|
||||
ctx.project._id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
'c3d',
|
||||
ctx.newClsiServerId
|
||||
)
|
||||
})
|
||||
@@ -417,7 +417,7 @@ describe('ClsiManager', function () {
|
||||
ctx.result = await ctx.ClsiManager.promises.sendRequest(
|
||||
ctx.project._id,
|
||||
ctx.user_id,
|
||||
{ compileBackendClass: 'n2d', compileGroup: 'standard' }
|
||||
{ compileBackendClass: 'c3d', compileGroup: 'standard' }
|
||||
)
|
||||
})
|
||||
|
||||
@@ -452,7 +452,7 @@ describe('ClsiManager', function () {
|
||||
{
|
||||
timeout: 100,
|
||||
incrementalCompilesEnabled: true,
|
||||
compileBackendClass: 'n2d',
|
||||
compileBackendClass: 'c3d',
|
||||
compileGroup: 'priority',
|
||||
compileFromClsiCache: true,
|
||||
populateClsiCache: true,
|
||||
@@ -499,7 +499,7 @@ describe('ClsiManager', function () {
|
||||
url.hostname === CLSI_HOST &&
|
||||
url.pathname ===
|
||||
`/project/${ctx.project._id}/user/${ctx.user_id}/compile` &&
|
||||
url.searchParams.get('compileBackendClass') === 'n2d' &&
|
||||
url.searchParams.get('compileBackendClass') === 'c3d' &&
|
||||
url.searchParams.get('compileGroup') === 'priority'
|
||||
),
|
||||
{
|
||||
@@ -798,7 +798,7 @@ describe('ClsiManager', function () {
|
||||
ctx.result = await ctx.ClsiManager.promises.sendRequest(
|
||||
ctx.project._id,
|
||||
ctx.user_id,
|
||||
{ compileBackendClass: 'n2d' }
|
||||
{ compileBackendClass: 'c3d' }
|
||||
)
|
||||
})
|
||||
|
||||
@@ -823,7 +823,7 @@ describe('ClsiManager', function () {
|
||||
it('should clear the CLSI server id cookie', function (ctx) {
|
||||
expect(
|
||||
ctx.ClsiCookieManager.promises.clearServerId
|
||||
).to.have.been.calledWith(ctx.project._id, ctx.user_id, 'n2d')
|
||||
).to.have.been.calledWith(ctx.project._id, ctx.user_id, 'c3d')
|
||||
})
|
||||
|
||||
it('should return a success status', function (ctx) {
|
||||
@@ -930,7 +930,7 @@ describe('ClsiManager', function () {
|
||||
ctx.result = await ctx.ClsiManager.promises.sendExternalRequest(
|
||||
ctx.submissionId,
|
||||
ctx.clsiRequest,
|
||||
{ compileBackendClass: 'n2d', compileGroup: 'standard' }
|
||||
{ compileBackendClass: 'c3d', compileGroup: 'standard' }
|
||||
)
|
||||
})
|
||||
|
||||
@@ -940,7 +940,7 @@ describe('ClsiManager', function () {
|
||||
url =>
|
||||
url.host === CLSI_HOST &&
|
||||
url.pathname === `/project/${ctx.submissionId}/compile` &&
|
||||
url.searchParams.get('compileBackendClass') === 'n2d' &&
|
||||
url.searchParams.get('compileBackendClass') === 'c3d' &&
|
||||
url.searchParams.get('compileGroup') === 'standard'
|
||||
),
|
||||
{
|
||||
@@ -1005,7 +1005,7 @@ describe('ClsiManager', function () {
|
||||
await ctx.ClsiManager.promises.deleteAuxFiles(
|
||||
ctx.project._id,
|
||||
ctx.user_id,
|
||||
{ compileBackendClass: 'n2d', compileGroup: 'standard' },
|
||||
{ compileBackendClass: 'c3d', compileGroup: 'standard' },
|
||||
'node-1'
|
||||
)
|
||||
})
|
||||
@@ -1017,7 +1017,7 @@ describe('ClsiManager', function () {
|
||||
url.host === CLSI_HOST &&
|
||||
url.pathname ===
|
||||
`/project/${ctx.project._id}/user/${ctx.user_id}` &&
|
||||
url.searchParams.get('compileBackendClass') === 'n2d' &&
|
||||
url.searchParams.get('compileBackendClass') === 'c3d' &&
|
||||
url.searchParams.get('compileGroup') === 'standard' &&
|
||||
url.searchParams.get('clsiserverid') === 'node-1'
|
||||
),
|
||||
@@ -1039,7 +1039,7 @@ describe('ClsiManager', function () {
|
||||
|
||||
it('should clear the clsi persistance', function (ctx) {
|
||||
ctx.ClsiCookieManager.promises.clearServerId
|
||||
.calledWith(ctx.project._id, ctx.user_id, 'n2d')
|
||||
.calledWith(ctx.project._id, ctx.user_id, 'c3d')
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
@@ -1107,7 +1107,7 @@ describe('ClsiManager', function () {
|
||||
ctx.project._id,
|
||||
ctx.user_id,
|
||||
false,
|
||||
{ compileBackendClass: 'n2d', compileGroup: 'standard' },
|
||||
{ compileBackendClass: 'c3d', compileGroup: 'standard' },
|
||||
'node-1'
|
||||
)
|
||||
})
|
||||
@@ -1117,7 +1117,7 @@ describe('ClsiManager', function () {
|
||||
sinon.match(
|
||||
url =>
|
||||
url.toString() ===
|
||||
`http://clsi.example.com/project/${ctx.project._id}/user/${ctx.user_id}/wordcount?compileBackendClass=n2d&compileGroup=standard&file=main.tex&image=mock-image-name&clsiserverid=node-1`
|
||||
`http://clsi.example.com/project/${ctx.project._id}/user/${ctx.user_id}/wordcount?compileBackendClass=c3d&compileGroup=standard&file=main.tex&image=mock-image-name&clsiserverid=node-1`
|
||||
)
|
||||
)
|
||||
})
|
||||
@@ -1134,7 +1134,7 @@ describe('ClsiManager', function () {
|
||||
ctx.project._id,
|
||||
ctx.user_id,
|
||||
'other.tex',
|
||||
{ compileBackendClass: 'n2d', compileGroup: 'standard' },
|
||||
{ compileBackendClass: 'c3d', compileGroup: 'standard' },
|
||||
'node-2'
|
||||
)
|
||||
})
|
||||
@@ -1146,7 +1146,7 @@ describe('ClsiManager', function () {
|
||||
url.host === CLSI_HOST &&
|
||||
url.pathname ===
|
||||
`/project/${ctx.project._id}/user/${ctx.user_id}/wordcount` &&
|
||||
url.searchParams.get('compileBackendClass') === 'n2d' &&
|
||||
url.searchParams.get('compileBackendClass') === 'c3d' &&
|
||||
url.searchParams.get('compileGroup') === 'standard' &&
|
||||
url.searchParams.get('clsiserverid') === 'node-2' &&
|
||||
url.searchParams.get('file') === 'other.tex' &&
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('CompileController', function () {
|
||||
apis: {
|
||||
clsi: {
|
||||
url: 'http://clsi.example.com',
|
||||
submissionBackendClass: 'n2d',
|
||||
submissionBackendClass: 'c3d',
|
||||
},
|
||||
clsi_priority: {
|
||||
url: 'http://clsi-priority.example.com',
|
||||
@@ -442,7 +442,7 @@ describe('CompileController', function () {
|
||||
ctx.ClsiManager.promises.sendExternalRequest.should.have.been.calledWith(
|
||||
ctx.submission_id,
|
||||
{ compileGroup: 'special', timeout: 600 },
|
||||
{ compileGroup: 'special', compileBackendClass: 'n2d', timeout: 600 }
|
||||
{ compileGroup: 'special', compileBackendClass: 'c3d', timeout: 600 }
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -473,7 +473,7 @@ describe('CompileController', function () {
|
||||
draft: true,
|
||||
check: 'validate',
|
||||
compileGroup: 'standard',
|
||||
compileBackendClass: 'n2d',
|
||||
compileBackendClass: 'c3d',
|
||||
timeout: 60,
|
||||
}
|
||||
)
|
||||
@@ -606,7 +606,7 @@ describe('CompileController', function () {
|
||||
'output-file',
|
||||
ctx.expected_url,
|
||||
{},
|
||||
{ compileGroup: 'standard', compileBackendClass: 'n2d' }
|
||||
{ compileGroup: 'standard', compileBackendClass: 'c3d' }
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -629,7 +629,7 @@ describe('CompileController', function () {
|
||||
{},
|
||||
{
|
||||
compileGroup: 'special',
|
||||
compileBackendClass: 'n2d',
|
||||
compileBackendClass: 'c3d',
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -753,7 +753,7 @@ describe('CompileController', function () {
|
||||
.stub()
|
||||
.resolves({
|
||||
compileGroup: 'standard',
|
||||
compileBackendClass: 'n2d',
|
||||
compileBackendClass: 'c3d',
|
||||
})
|
||||
await ctx.CompileController._proxyToClsi(
|
||||
ctx.projectId,
|
||||
@@ -768,7 +768,7 @@ describe('CompileController', function () {
|
||||
|
||||
it('should open a request to the CLSI', function (ctx) {
|
||||
ctx.fetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
|
||||
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=n2d&query=foo`
|
||||
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=c3d&query=foo`
|
||||
)
|
||||
})
|
||||
|
||||
@@ -810,7 +810,7 @@ describe('CompileController', function () {
|
||||
.stub()
|
||||
.resolves({
|
||||
compileGroup: 'standard',
|
||||
compileBackendClass: 'n2d',
|
||||
compileBackendClass: 'c3d',
|
||||
})
|
||||
await ctx.CompileController._proxyToClsi(
|
||||
ctx.projectId,
|
||||
@@ -825,7 +825,7 @@ describe('CompileController', function () {
|
||||
|
||||
it('should open a request to the CLSI', function (ctx) {
|
||||
ctx.fetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
|
||||
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=n2d`
|
||||
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=c3d`
|
||||
)
|
||||
})
|
||||
|
||||
@@ -841,7 +841,7 @@ describe('CompileController', function () {
|
||||
.stub()
|
||||
.resolves({
|
||||
compileGroup: 'standard',
|
||||
compileBackendClass: 'n2d',
|
||||
compileBackendClass: 'c3d',
|
||||
})
|
||||
await ctx.CompileController._proxyToClsi(
|
||||
ctx.projectId,
|
||||
@@ -856,7 +856,7 @@ describe('CompileController', function () {
|
||||
|
||||
it('should proxy to the standard url', function (ctx) {
|
||||
ctx.fetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
|
||||
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=n2d`
|
||||
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=c3d`
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -867,7 +867,7 @@ describe('CompileController', function () {
|
||||
.stub()
|
||||
.resolves({
|
||||
compileGroup: 'standard',
|
||||
compileBackendClass: 'n2d',
|
||||
compileBackendClass: 'c3d',
|
||||
})
|
||||
ctx.req.query = { build: 1234 }
|
||||
await ctx.CompileController._proxyToClsi(
|
||||
@@ -883,7 +883,7 @@ describe('CompileController', function () {
|
||||
|
||||
it('should proxy to the standard url without the build parameter', function (ctx) {
|
||||
ctx.fetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
|
||||
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=n2d`
|
||||
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=c3d`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('CompileManager', function () {
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: (ctx.settings = {
|
||||
apis: {
|
||||
clsi: { submissionBackendClass: 'n2d' },
|
||||
clsi: { submissionBackendClass: 'c3d' },
|
||||
},
|
||||
redis: { web: { host: '127.0.0.1', port: 42 } },
|
||||
rateLimit: { autoCompile: {} },
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
import Helpers from './lib/helpers.mjs'
|
||||
import { getCollectionInternal } from './lib/mongodb.mjs'
|
||||
|
||||
const tags = ['server-pro', 'saas']
|
||||
|
||||
const oldIndexes = [
|
||||
{
|
||||
key: {
|
||||
scheduledAt: 1,
|
||||
},
|
||||
name: 'scheduledAt_1',
|
||||
expireAfterSeconds: 60 * 60 * 24, // expire after 24 hours
|
||||
},
|
||||
]
|
||||
|
||||
const newIndexes = [
|
||||
{
|
||||
key: {
|
||||
recipient_id: 1,
|
||||
},
|
||||
name: 'recipientId_1',
|
||||
expireAfterSeconds: 60 * 60 * 24, // expire after 24 hours
|
||||
},
|
||||
]
|
||||
|
||||
const migrate = async client => {
|
||||
const emailNotifications = await getCollectionInternal('emailNotifications')
|
||||
await Helpers.dropIndexesFromCollection(emailNotifications, oldIndexes)
|
||||
await Helpers.addIndexesToCollection(emailNotifications, newIndexes)
|
||||
}
|
||||
|
||||
const rollback = async client => {
|
||||
const emailNotifications = await getCollectionInternal('emailNotifications')
|
||||
await Helpers.dropIndexesFromCollection(emailNotifications, newIndexes)
|
||||
await Helpers.addIndexesToCollection(emailNotifications, oldIndexes)
|
||||
}
|
||||
|
||||
export default {
|
||||
tags,
|
||||
migrate,
|
||||
rollback,
|
||||
}
|
||||
Reference in New Issue
Block a user