mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #18225 from overleaf/em-typescript-eslint
Add typescript-eslint rule: no-floating-promises GitOrigin-RevId: 8c3decdff537c885f5bfeb5250b7805480bc6602
This commit is contained in:
parent
814b085b44
commit
876ee4d967
48 changed files with 1156 additions and 798 deletions
601
package-lock.json
generated
601
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,370 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"standard",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": ["@overleaf"],
|
||||
"env": {
|
||||
"es2020": true
|
||||
},
|
||||
"settings": {
|
||||
// Tell eslint-plugin-react to detect which version of React we are using
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"no-constant-binary-expression": "error",
|
||||
|
||||
// do not allow importing of implicit dependencies.
|
||||
"import/no-extraneous-dependencies": "error",
|
||||
|
||||
// disable some TypeScript rules
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off"
|
||||
},
|
||||
"overrides": [
|
||||
// NOTE: changing paths may require updating them in the Makefile too.
|
||||
{
|
||||
// Node
|
||||
"files": [
|
||||
"**/app/src/**/*.js",
|
||||
"app.js",
|
||||
"i18next-scanner.config.js",
|
||||
"scripts/**/*.js",
|
||||
"webpack.config*.js"
|
||||
],
|
||||
"env": {
|
||||
"node": true
|
||||
}
|
||||
},
|
||||
{
|
||||
// Test specific rules
|
||||
"files": ["**/test/**/*.*"],
|
||||
"plugins": [
|
||||
"mocha",
|
||||
"chai-expect",
|
||||
"chai-friendly"
|
||||
],
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
// mocha-specific rules
|
||||
"mocha/handle-done-callback": "error",
|
||||
"mocha/no-exclusive-tests": "error",
|
||||
"mocha/no-global-tests": "error",
|
||||
"mocha/no-identical-title": "error",
|
||||
"mocha/no-nested-tests": "error",
|
||||
"mocha/no-pending-tests": "error",
|
||||
"mocha/no-skipped-tests": "error",
|
||||
"mocha/no-mocha-arrows": "error",
|
||||
|
||||
|
||||
// Swap the no-unused-expressions rule with a more chai-friendly one
|
||||
"no-unused-expressions": "off",
|
||||
"chai-friendly/no-unused-expressions": "error",
|
||||
|
||||
// chai-specific rules
|
||||
"chai-expect/missing-assertion": "error",
|
||||
"chai-expect/terminating-properties": "error",
|
||||
|
||||
// prefer-arrow-callback applies to all callbacks, not just ones in mocha tests.
|
||||
// we don't enforce this at the top-level - just in tests to manage `this` scope
|
||||
// based on mocha's context mechanism
|
||||
"mocha/prefer-arrow-callback": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Backend specific rules
|
||||
"files": ["**/app/src/**/*.js", "app.js"],
|
||||
"rules": {
|
||||
// do not allow importing of implicit dependencies.
|
||||
"import/no-extraneous-dependencies": ["error", {
|
||||
// do not allow importing of devDependencies.
|
||||
"devDependencies": false
|
||||
}],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
// do not allow node-fetch in backend code
|
||||
{
|
||||
"selector": "CallExpression[callee.name='require'] > .arguments[value='node-fetch']",
|
||||
"message": "Requiring node-fetch is not allowed in production services, please use fetch-utils."
|
||||
},
|
||||
// mongoose populate must set fields to populate
|
||||
{
|
||||
"selector": "CallExpression[callee.property.name='populate'][arguments.length<2]",
|
||||
"message": "Populate without a second argument returns the whole document. Use populate('field',['prop1','prop2']) instead"
|
||||
},
|
||||
// Require `new` when constructing ObjectId (For mongo + mongoose upgrade)
|
||||
{
|
||||
"selector": "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']",
|
||||
"message": "Construct ObjectId with `new ObjectId()` instead of `ObjectId()`"
|
||||
},
|
||||
// Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade)
|
||||
{
|
||||
"selector": "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child",
|
||||
"message": "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
// Backend tests and scripts specific rules
|
||||
"files": ["**/test/**/*.*", "**/scripts/*.*"],
|
||||
"rules": {
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
// Require `new` when constructing ObjectId (For mongo + mongoose upgrade)
|
||||
{
|
||||
"selector": "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']",
|
||||
"message": "Construct ObjectId with `new ObjectId()` instead of `ObjectId()`"
|
||||
},
|
||||
// Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade)
|
||||
{
|
||||
"selector": "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child",
|
||||
"message": "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead"
|
||||
},
|
||||
// Catch incorrect usage of `await db.collection.find()`
|
||||
{
|
||||
"selector": "AwaitExpression > CallExpression > MemberExpression[property.name='find'][object.object.name='db']",
|
||||
"message": "Mongo find returns a cursor not a promise, use `for await (const result of cursor)` or `.toArray()` instead."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
// Cypress specific rules
|
||||
"files": ["cypress/**/*.{js,jsx,ts,tsx}", "**/test/frontend/**/*.spec.{js,jsx,ts,tsx}"],
|
||||
"extends": [
|
||||
"plugin:cypress/recommended"
|
||||
]
|
||||
},
|
||||
{
|
||||
// Frontend specific rules
|
||||
"files": ["**/frontend/js/**/*.{js,jsx,ts,tsx}", "**/frontend/stories/**/*.{js,jsx,ts,tsx}", "**/*.stories.{js,jsx,ts,tsx}", "**/test/frontend/**/*.{js,jsx,ts,tsx}", "**/test/frontend/components/**/*.spec.{js,jsx,ts,tsx}"],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"jsx-a11y"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"standard-jsx",
|
||||
"prettier"
|
||||
],
|
||||
"globals": {
|
||||
"__webpack_public_path__": true,
|
||||
"$": true,
|
||||
"angular": true,
|
||||
"ga": true,
|
||||
// Injected in layout.pug
|
||||
"user_id": true,
|
||||
"ExposedSettings": true
|
||||
},
|
||||
"rules": {
|
||||
// TODO: remove once https://github.com/standard/eslint-config-standard-react/issues/68 (support eslint@8) is fixed.
|
||||
// START: inline standard-react rules
|
||||
// "react/jsx-no-bind": ["error", {
|
||||
// "allowArrowFunctions": true,
|
||||
// "allowBind": false,
|
||||
// "ignoreRefs": true
|
||||
// },],
|
||||
"react/no-did-update-set-state": "error",
|
||||
"react/no-unused-prop-types": "error",
|
||||
"react/prop-types": "error",
|
||||
// "react/react-in-jsx-scope": "error",
|
||||
// END: inline standard-react rules
|
||||
|
||||
"react/no-unknown-property": ["error", {
|
||||
"ignore": ["dnd-container", "dropdown-toggle"]
|
||||
}],
|
||||
|
||||
"react/jsx-no-target-blank": ["error", {
|
||||
"allowReferrer": true
|
||||
}],
|
||||
// Prevent usage of legacy string refs
|
||||
"react/no-string-refs": "error",
|
||||
|
||||
// Prevent curly braces around strings (as they're unnecessary)
|
||||
"react/jsx-curly-brace-presence": ["error", {
|
||||
"props": "never",
|
||||
"children": "never"
|
||||
}],
|
||||
|
||||
// Don't import React for JSX; the JSX runtime is added by a Babel plugin
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/jsx-uses-react": "off",
|
||||
|
||||
// Allow functions as JSX props
|
||||
"react/jsx-no-bind": "off", // TODO: fix occurrences and re-enable this
|
||||
|
||||
// Fix conflict between prettier & standard by overriding to prefer
|
||||
// double quotes
|
||||
"jsx-quotes": ["error", "prefer-double"],
|
||||
|
||||
// Override weird behaviour of jsx-a11y label-has-for (says labels must be
|
||||
// nested *and* have for/id attributes)
|
||||
"jsx-a11y/label-has-for": [
|
||||
"error",
|
||||
{
|
||||
"required": {
|
||||
"some": [
|
||||
"nesting",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// Require .jsx or .tsx file extension when using JSX
|
||||
"react/jsx-filename-extension": ["error", {
|
||||
"extensions": [".jsx", ".tsx"]
|
||||
}],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
// Begin: Make sure angular can withstand minification
|
||||
{
|
||||
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > :function[params.length > 0]",
|
||||
"message": "Wrap the function in an array with the parameter names, to withstand minifcation. E.g. App.controller('MyController', ['param1', function(param1) {}]"
|
||||
},
|
||||
{
|
||||
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > ArrowFunctionExpression",
|
||||
"message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}"
|
||||
},
|
||||
{
|
||||
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrowFunctionExpression",
|
||||
"message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}"
|
||||
},
|
||||
{
|
||||
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > :not(:function, Identifier):last-child",
|
||||
"message": "Last element of the array must be a function. E.g ['param1', function(param1) {}]"
|
||||
},
|
||||
{
|
||||
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression[elements.length=0]",
|
||||
"message": "Array must not be empty. Add parameters and a function. E.g ['param1', function(param1) {}]"
|
||||
},
|
||||
// End: Make sure angular can withstand minification
|
||||
// prohibit direct calls to methods of window.localStorage
|
||||
{
|
||||
"selector": "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']",
|
||||
"message": "Modify location via customLocalStorage instead of calling window.localStorage methods directly"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
// React component specific rules
|
||||
//
|
||||
"files": ["**/frontend/js/**/components/**/*.{js,jsx,ts,tsx}", "**/frontend/js/**/hooks/**/*.{js,jsx,ts,tsx}"],
|
||||
"rules": {
|
||||
"@overleaf/no-unnecessary-trans": "error",
|
||||
"@overleaf/should-unescape-trans": "error",
|
||||
|
||||
// https://astexplorer.net/
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
// prohibit direct calls to methods of window.location
|
||||
{
|
||||
"selector": "CallExpression[callee.object.object.name='window'][callee.object.property.name='location']",
|
||||
"message": "Modify location via useLocation instead of calling window.location methods directly"
|
||||
},
|
||||
// prohibit assignment to window.location
|
||||
{
|
||||
"selector": "AssignmentExpression[left.object.name='window'][left.property.name='location']",
|
||||
"message": "Modify location via useLocation instead of calling window.location methods directly"
|
||||
},
|
||||
// prohibit assignment to window.location.href
|
||||
{
|
||||
"selector": "AssignmentExpression[left.object.object.name='window'][left.object.property.name='location'][left.property.name='href']",
|
||||
"message": "Modify location via useLocation instead of calling window.location methods directly"
|
||||
},
|
||||
// prohibit using lookbehinds due to incidents with Safari simply crashing when the script is parsed
|
||||
{
|
||||
"selector": "Literal[regex.pattern=/\\(\\?<[!=]/]",
|
||||
"message": "Lookbehind is not supported in older Safari versions."
|
||||
},
|
||||
// prohibit direct calls to methods of window.localStorage
|
||||
// NOTE: this rule is also defined for all frontend files, but those rules are overriden by the React component-specific config
|
||||
{
|
||||
"selector": "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']",
|
||||
"message": "Modify location via customLocalStorage instead of calling window.localStorage methods directly"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
// React + TypeScript-specific rules
|
||||
{
|
||||
"files": ["**/*.tsx"],
|
||||
"rules": {
|
||||
"react/prop-types": "off",
|
||||
"no-undef": "off"
|
||||
}
|
||||
},
|
||||
// TypeScript-specific rules
|
||||
{
|
||||
"files": ["**/*.ts"],
|
||||
"rules": {
|
||||
"no-undef": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["scripts/ukamf/*.js"],
|
||||
"rules": {
|
||||
// Do not allow importing of any dependencies unless specified in either
|
||||
// - web/package.json
|
||||
// - web/scripts/ukamf/package.json
|
||||
"import/no-extraneous-dependencies": ["error", {"packageDir": [".", "scripts/ukamf"]}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["scripts/learn/checkSanitize/*.js"],
|
||||
"rules": {
|
||||
// The checkSanitize script is used in the dev-env only.
|
||||
"import/no-extraneous-dependencies": ["error", {
|
||||
"devDependencies": true,
|
||||
"packageDir": [".", "../../"]
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
// Backend: Use @overleaf/logger
|
||||
// Docs: https://manual.dev-overleaf.com/development/code/logging/#structured-logging
|
||||
"**/app/**/*.{js,cjs,mjs}", "app.js", "modules/*/*.js",
|
||||
// Frontend: Prefer debugConsole over bare console
|
||||
// Docs: https://manual.dev-overleaf.com/development/code/logging/#frontend
|
||||
"**/frontend/**/*.{js,jsx,ts,tsx}",
|
||||
// Tests
|
||||
"**/test/**/*.{js,cjs,mjs,jsx,ts,tsx}"
|
||||
],
|
||||
"excludedFiles": [
|
||||
// Allow console logs in scripts
|
||||
"**/scripts/**/*.js",
|
||||
// Allow console logs in stories
|
||||
"**/stories/**/*.{js,jsx,ts,tsx}",
|
||||
// Workers do not have access to the search params for enabling ?debug=true.
|
||||
// self.location.url is the URL of the worker script.
|
||||
"*.worker.{js,ts}"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
432
services/web/.eslintrc.js
Normal file
432
services/web/.eslintrc.js
Normal file
|
@ -0,0 +1,432 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'standard',
|
||||
'prettier',
|
||||
],
|
||||
plugins: ['@overleaf'],
|
||||
env: {
|
||||
es2020: true,
|
||||
},
|
||||
settings: {
|
||||
// Tell eslint-plugin-react to detect which version of React we are using
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-constant-binary-expression': 'error',
|
||||
|
||||
// do not allow importing of implicit dependencies.
|
||||
'import/no-extraneous-dependencies': 'error',
|
||||
|
||||
// disable some TypeScript rules
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-this-alias': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
},
|
||||
overrides: [
|
||||
// NOTE: changing paths may require updating them in the Makefile too.
|
||||
{
|
||||
// Node
|
||||
files: [
|
||||
'**/app/src/**/*.js',
|
||||
'app.js',
|
||||
'i18next-scanner.config.js',
|
||||
'scripts/**/*.js',
|
||||
'webpack.config*.js',
|
||||
],
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Test specific rules
|
||||
files: ['**/test/**/*.*'],
|
||||
plugins: ['mocha', 'chai-expect', 'chai-friendly'],
|
||||
env: {
|
||||
mocha: true,
|
||||
},
|
||||
rules: {
|
||||
// mocha-specific rules
|
||||
'mocha/handle-done-callback': 'error',
|
||||
'mocha/no-exclusive-tests': 'error',
|
||||
'mocha/no-global-tests': 'error',
|
||||
'mocha/no-identical-title': 'error',
|
||||
'mocha/no-nested-tests': 'error',
|
||||
'mocha/no-pending-tests': 'error',
|
||||
'mocha/no-skipped-tests': 'error',
|
||||
'mocha/no-mocha-arrows': 'error',
|
||||
|
||||
// Swap the no-unused-expressions rule with a more chai-friendly one
|
||||
'no-unused-expressions': 'off',
|
||||
'chai-friendly/no-unused-expressions': 'error',
|
||||
|
||||
// chai-specific rules
|
||||
'chai-expect/missing-assertion': 'error',
|
||||
'chai-expect/terminating-properties': 'error',
|
||||
|
||||
// prefer-arrow-callback applies to all callbacks, not just ones in mocha tests.
|
||||
// we don't enforce this at the top-level - just in tests to manage `this` scope
|
||||
// based on mocha's context mechanism
|
||||
'mocha/prefer-arrow-callback': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Backend specific rules
|
||||
files: ['**/app/src/**/*.js', 'app.js'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.backend.json',
|
||||
},
|
||||
rules: {
|
||||
// do not allow importing of implicit dependencies.
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
// do not allow importing of devDependencies.
|
||||
devDependencies: false,
|
||||
},
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
// do not allow node-fetch in backend code
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.name='require'] > .arguments[value='node-fetch']",
|
||||
message:
|
||||
'Requiring node-fetch is not allowed in production services, please use fetch-utils.',
|
||||
},
|
||||
// mongoose populate must set fields to populate
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.property.name='populate'][arguments.length<2]",
|
||||
message:
|
||||
"Populate without a second argument returns the whole document. Use populate('field',['prop1','prop2']) instead",
|
||||
},
|
||||
// Require `new` when constructing ObjectId (For mongo + mongoose upgrade)
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']",
|
||||
message:
|
||||
'Construct ObjectId with `new ObjectId()` instead of `ObjectId()`',
|
||||
},
|
||||
// Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade)
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child",
|
||||
message:
|
||||
"Don't map ObjectId directly. Use `id => new ObjectId(id)` instead",
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Backend tests and scripts specific rules
|
||||
files: ['**/test/**/*.*', '**/scripts/*.*'],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
// Require `new` when constructing ObjectId (For mongo + mongoose upgrade)
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']",
|
||||
message:
|
||||
'Construct ObjectId with `new ObjectId()` instead of `ObjectId()`',
|
||||
},
|
||||
// Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade)
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child",
|
||||
message:
|
||||
"Don't map ObjectId directly. Use `id => new ObjectId(id)` instead",
|
||||
},
|
||||
// Catch incorrect usage of `await db.collection.find()`
|
||||
{
|
||||
selector:
|
||||
"AwaitExpression > CallExpression > MemberExpression[property.name='find'][object.object.name='db']",
|
||||
message:
|
||||
'Mongo find returns a cursor not a promise, use `for await (const result of cursor)` or `.toArray()` instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Cypress specific rules
|
||||
files: [
|
||||
'cypress/**/*.{js,jsx,ts,tsx}',
|
||||
'**/test/frontend/**/*.spec.{js,jsx,ts,tsx}',
|
||||
],
|
||||
extends: ['plugin:cypress/recommended'],
|
||||
},
|
||||
{
|
||||
// Frontend specific rules
|
||||
files: [
|
||||
'**/frontend/js/**/*.{js,jsx,ts,tsx}',
|
||||
'**/frontend/stories/**/*.{js,jsx,ts,tsx}',
|
||||
'**/*.stories.{js,jsx,ts,tsx}',
|
||||
'**/test/frontend/**/*.{js,jsx,ts,tsx}',
|
||||
'**/test/frontend/components/**/*.spec.{js,jsx,ts,tsx}',
|
||||
],
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['jsx-a11y'],
|
||||
extends: [
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:jsx-a11y/recommended',
|
||||
'standard-jsx',
|
||||
'prettier',
|
||||
],
|
||||
globals: {
|
||||
__webpack_public_path__: true,
|
||||
$: true,
|
||||
angular: true,
|
||||
ga: true,
|
||||
// Injected in layout.pug
|
||||
user_id: true,
|
||||
ExposedSettings: true,
|
||||
},
|
||||
rules: {
|
||||
// TODO: remove once https://github.com/standard/eslint-config-standard-react/issues/68 (support eslint@8) is fixed.
|
||||
// START: inline standard-react rules
|
||||
// "react/jsx-no-bind": ["error", {
|
||||
// "allowArrowFunctions": true,
|
||||
// "allowBind": false,
|
||||
// "ignoreRefs": true
|
||||
// },],
|
||||
'react/no-did-update-set-state': 'error',
|
||||
'react/no-unused-prop-types': 'error',
|
||||
'react/prop-types': 'error',
|
||||
// "react/react-in-jsx-scope": "error",
|
||||
// END: inline standard-react rules
|
||||
|
||||
'react/no-unknown-property': [
|
||||
'error',
|
||||
{
|
||||
ignore: ['dnd-container', 'dropdown-toggle'],
|
||||
},
|
||||
],
|
||||
|
||||
'react/jsx-no-target-blank': [
|
||||
'error',
|
||||
{
|
||||
allowReferrer: true,
|
||||
},
|
||||
],
|
||||
// Prevent usage of legacy string refs
|
||||
'react/no-string-refs': 'error',
|
||||
|
||||
// Prevent curly braces around strings (as they're unnecessary)
|
||||
'react/jsx-curly-brace-presence': [
|
||||
'error',
|
||||
{
|
||||
props: 'never',
|
||||
children: 'never',
|
||||
},
|
||||
],
|
||||
|
||||
// Don't import React for JSX; the JSX runtime is added by a Babel plugin
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/jsx-uses-react': 'off',
|
||||
|
||||
// Allow functions as JSX props
|
||||
'react/jsx-no-bind': 'off', // TODO: fix occurrences and re-enable this
|
||||
|
||||
// Fix conflict between prettier & standard by overriding to prefer
|
||||
// double quotes
|
||||
'jsx-quotes': ['error', 'prefer-double'],
|
||||
|
||||
// Override weird behaviour of jsx-a11y label-has-for (says labels must be
|
||||
// nested *and* have for/id attributes)
|
||||
'jsx-a11y/label-has-for': [
|
||||
'error',
|
||||
{
|
||||
required: {
|
||||
some: ['nesting', 'id'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Require .jsx or .tsx file extension when using JSX
|
||||
'react/jsx-filename-extension': [
|
||||
'error',
|
||||
{
|
||||
extensions: ['.jsx', '.tsx'],
|
||||
},
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
// Begin: Make sure angular can withstand minification
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > :function[params.length > 0]",
|
||||
message:
|
||||
"Wrap the function in an array with the parameter names, to withstand minifcation. E.g. App.controller('MyController', ['param1', function(param1) {}]",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > ArrowFunctionExpression",
|
||||
message:
|
||||
'Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrowFunctionExpression",
|
||||
message:
|
||||
'Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > :not(:function, Identifier):last-child",
|
||||
message:
|
||||
"Last element of the array must be a function. E.g ['param1', function(param1) {}]",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression[elements.length=0]",
|
||||
message:
|
||||
"Array must not be empty. Add parameters and a function. E.g ['param1', function(param1) {}]",
|
||||
},
|
||||
// End: Make sure angular can withstand minification
|
||||
// prohibit direct calls to methods of window.localStorage
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']",
|
||||
message:
|
||||
'Modify location via customLocalStorage instead of calling window.localStorage methods directly',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// React component specific rules
|
||||
//
|
||||
files: [
|
||||
'**/frontend/js/**/components/**/*.{js,jsx,ts,tsx}',
|
||||
'**/frontend/js/**/hooks/**/*.{js,jsx,ts,tsx}',
|
||||
],
|
||||
rules: {
|
||||
'@overleaf/no-unnecessary-trans': 'error',
|
||||
'@overleaf/should-unescape-trans': 'error',
|
||||
|
||||
// https://astexplorer.net/
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
// prohibit direct calls to methods of window.location
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.object.name='window'][callee.object.property.name='location']",
|
||||
message:
|
||||
'Modify location via useLocation instead of calling window.location methods directly',
|
||||
},
|
||||
// prohibit assignment to window.location
|
||||
{
|
||||
selector:
|
||||
"AssignmentExpression[left.object.name='window'][left.property.name='location']",
|
||||
message:
|
||||
'Modify location via useLocation instead of calling window.location methods directly',
|
||||
},
|
||||
// prohibit assignment to window.location.href
|
||||
{
|
||||
selector:
|
||||
"AssignmentExpression[left.object.object.name='window'][left.object.property.name='location'][left.property.name='href']",
|
||||
message:
|
||||
'Modify location via useLocation instead of calling window.location methods directly',
|
||||
},
|
||||
// prohibit using lookbehinds due to incidents with Safari simply crashing when the script is parsed
|
||||
{
|
||||
selector: 'Literal[regex.pattern=/\\(\\?<[!=]/]',
|
||||
message: 'Lookbehind is not supported in older Safari versions.',
|
||||
},
|
||||
// prohibit direct calls to methods of window.localStorage
|
||||
// NOTE: this rule is also defined for all frontend files, but those rules are overriden by the React component-specific config
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']",
|
||||
message:
|
||||
'Modify location via customLocalStorage instead of calling window.localStorage methods directly',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// React + TypeScript-specific rules
|
||||
{
|
||||
files: ['**/*.tsx'],
|
||||
rules: {
|
||||
'react/prop-types': 'off',
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
// TypeScript-specific rules
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['scripts/ukamf/*.js'],
|
||||
rules: {
|
||||
// Do not allow importing of any dependencies unless specified in either
|
||||
// - web/package.json
|
||||
// - web/scripts/ukamf/package.json
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{ packageDir: ['.', 'scripts/ukamf'] },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['scripts/learn/checkSanitize/*.js'],
|
||||
rules: {
|
||||
// The checkSanitize script is used in the dev-env only.
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
packageDir: ['.', '../../'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
// Backend: Use @overleaf/logger
|
||||
// Docs: https://manual.dev-overleaf.com/development/code/logging/#structured-logging
|
||||
'**/app/**/*.{js,cjs,mjs}',
|
||||
'app.js',
|
||||
'modules/*/*.js',
|
||||
// Frontend: Prefer debugConsole over bare console
|
||||
// Docs: https://manual.dev-overleaf.com/development/code/logging/#frontend
|
||||
'**/frontend/**/*.{js,jsx,ts,tsx}',
|
||||
// Tests
|
||||
'**/test/**/*.{js,cjs,mjs,jsx,ts,tsx}',
|
||||
],
|
||||
excludedFiles: [
|
||||
// Allow console logs in scripts
|
||||
'**/scripts/**/*.js',
|
||||
// Allow console logs in stories
|
||||
'**/stories/**/*.{js,jsx,ts,tsx}',
|
||||
// Workers do not have access to the search params for enabling ?debug=true.
|
||||
// self.location.url is the URL of the worker script.
|
||||
'*.worker.{js,ts}',
|
||||
],
|
||||
rules: {
|
||||
'no-console': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
|
@ -57,6 +57,15 @@ async function recordEventForUser(userId, event, segmentation) {
|
|||
}
|
||||
}
|
||||
|
||||
function recordEventForUserInBackground(userId, event, segmentation) {
|
||||
recordEventForUser(userId, event, segmentation).catch(err => {
|
||||
logger.warn(
|
||||
{ err, userId, event, segmentation },
|
||||
'failed to record event for user'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function recordEventForSession(session, event, segmentation) {
|
||||
const { analyticsId, userId } = getIdsFromSession(session)
|
||||
if (!analyticsId) {
|
||||
|
@ -88,6 +97,15 @@ async function setUserPropertyForUser(userId, propertyName, propertyValue) {
|
|||
}
|
||||
}
|
||||
|
||||
function setUserPropertyForUserInBackground(userId, property, value) {
|
||||
setUserPropertyForUser(userId, property, value).catch(err => {
|
||||
logger.warn(
|
||||
{ err, userId, property, value },
|
||||
'failed to set user property for user'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async function setUserPropertyForAnalyticsId(
|
||||
analyticsId,
|
||||
propertyName,
|
||||
|
@ -115,6 +133,16 @@ async function setUserPropertyForSession(session, propertyName, propertyValue) {
|
|||
}
|
||||
}
|
||||
|
||||
function setUserPropertyForSessionInBackground(session, property, value) {
|
||||
setUserPropertyForSession(session, property, value).catch(err => {
|
||||
const { analyticsId, userId } = getIdsFromSession(session)
|
||||
logger.warn(
|
||||
{ err, analyticsId, userId, property, value },
|
||||
'failed to set user property for session'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function updateEditingSession(userId, projectId, countryCode, segmentation) {
|
||||
if (!userId) {
|
||||
return
|
||||
|
@ -310,8 +338,11 @@ module.exports = {
|
|||
identifyUser,
|
||||
recordEventForSession,
|
||||
recordEventForUser,
|
||||
recordEventForUserInBackground,
|
||||
setUserPropertyForUser,
|
||||
setUserPropertyForUserInBackground,
|
||||
setUserPropertyForSession,
|
||||
setUserPropertyForSessionInBackground,
|
||||
setUserPropertyForAnalyticsId,
|
||||
updateEditingSession,
|
||||
getIdsFromSession,
|
||||
|
|
|
@ -31,25 +31,25 @@ function addUserProperties(userId, session) {
|
|||
}
|
||||
|
||||
if (session.required_login_from_product_medium) {
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
`registered-from-product-medium`,
|
||||
session.required_login_from_product_medium
|
||||
)
|
||||
if (session.required_login_from_product_source) {
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
`registered-from-product-source`,
|
||||
session.required_login_from_product_source
|
||||
)
|
||||
}
|
||||
} else if (session.referal_id) {
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
`registered-from-bonus-scheme`,
|
||||
true
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
`registered-from-product-medium`,
|
||||
'bonus-scheme'
|
||||
|
@ -58,7 +58,7 @@ function addUserProperties(userId, session) {
|
|||
|
||||
if (session.inbound) {
|
||||
if (session.inbound.referrer && session.inbound.referrer.medium) {
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
`registered-from-referrer-medium`,
|
||||
`${session.inbound.referrer.medium
|
||||
|
@ -66,7 +66,7 @@ function addUserProperties(userId, session) {
|
|||
.toUpperCase()}${session.inbound.referrer.medium.slice(1)}`
|
||||
)
|
||||
if (session.inbound.referrer.source) {
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
`registered-from-referrer-source`,
|
||||
session.inbound.referrer.source
|
||||
|
@ -77,7 +77,7 @@ function addUserProperties(userId, session) {
|
|||
if (session.inbound.utm) {
|
||||
for (const utmKey of RequestHelper.UTM_KEYS) {
|
||||
if (session.inbound.utm[utmKey]) {
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
`registered-from-${utmKey.replace('_', '-')}`,
|
||||
session.inbound.utm[utmKey]
|
||||
|
|
|
@ -27,7 +27,7 @@ function recordUTMTags() {
|
|||
};${utmValues.utm_campaign || 'N/A'};${
|
||||
utmValues.utm_content || utmValues.utm_term || 'N/A'
|
||||
}`
|
||||
AnalyticsManager.setUserPropertyForSession(
|
||||
AnalyticsManager.setUserPropertyForSessionInBackground(
|
||||
req.session,
|
||||
'utm-tags',
|
||||
propertyValue
|
||||
|
|
|
@ -646,7 +646,7 @@ function _loginAsyncHandlers(req, user, anonymousAnalyticsId, isNewUser) {
|
|||
LoginRateLimiter.recordSuccessfulLogin(user.email, () => {})
|
||||
AuthenticationController._recordSuccessfulLogin(user._id, () => {})
|
||||
AuthenticationController.ipMatchCheck(req, user)
|
||||
Analytics.recordEventForUser(user._id, 'user-logged-in', {
|
||||
Analytics.recordEventForUserInBackground(user._id, 'user-logged-in', {
|
||||
source: req.session.saml
|
||||
? 'saml'
|
||||
: req.user_info?.auth_provider || 'email-password',
|
||||
|
|
|
@ -6,7 +6,11 @@ const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
|||
async function optIn(userId) {
|
||||
await UserUpdater.promises.updateUser(userId, { $set: { betaProgram: true } })
|
||||
metrics.inc('beta-program.opt-in')
|
||||
AnalyticsManager.setUserPropertyForUser(userId, 'beta-program', true)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'beta-program',
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
async function optOut(userId) {
|
||||
|
@ -14,7 +18,11 @@ async function optOut(userId) {
|
|||
$set: { betaProgram: false },
|
||||
})
|
||||
metrics.inc('beta-program.opt-out')
|
||||
AnalyticsManager.setUserPropertyForUser(userId, 'beta-program', false)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'beta-program',
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -348,7 +348,7 @@ const CollaboratorsInviteController = {
|
|||
'project:membership:changed',
|
||||
{ invites: true, members: true }
|
||||
)
|
||||
AnalyticsManager.recordEventForUser(
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
currentUser._id,
|
||||
'project-invite-accept',
|
||||
{
|
||||
|
|
|
@ -158,7 +158,7 @@ const CollaboratorsInviteHandler = {
|
|||
},
|
||||
|
||||
async acceptInvite(invite, projectId, user) {
|
||||
CollaboratorsHandler.promises.addUserIdToProject(
|
||||
await CollaboratorsHandler.promises.addUserIdToProject(
|
||||
projectId,
|
||||
invite.sendingUserId,
|
||||
user._id,
|
||||
|
|
|
@ -38,7 +38,7 @@ async function transferOwnership(projectId, newOwnerId, options = {}) {
|
|||
}
|
||||
|
||||
// Track the change of ownership in BigQuery.
|
||||
AnalyticsManager.recordEventForUser(
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
previousOwnerId,
|
||||
'project-ownership-transfer',
|
||||
{ projectId, newOwnerId }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const { callbackify } = require('util')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const logger = require('@overleaf/logger')
|
||||
const EmailBuilder = require('./EmailBuilder')
|
||||
const EmailSender = require('./EmailSender')
|
||||
const Queues = require('../../infrastructure/Queues')
|
||||
|
@ -30,5 +31,7 @@ function sendDeferredEmail(emailType, opts, delay) {
|
|||
'deferred-emails',
|
||||
{ data: { emailType, opts } },
|
||||
delay
|
||||
)
|
||||
).catch(err => {
|
||||
logger.warn({ err, emailType, opts }, 'failed to queue deferred email')
|
||||
})
|
||||
}
|
||||
|
|
|
@ -94,9 +94,13 @@ module.exports = LinkedFilesController = {
|
|||
return LinkedFilesController.handleError(err, req, res, next)
|
||||
}
|
||||
if (name.endsWith('.bib')) {
|
||||
AnalyticsManager.recordEventForUser(userId, 'linked-bib-file', {
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'linked-bib-file',
|
||||
{
|
||||
integration: provider,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
return res.json({ new_file_id: newFileId })
|
||||
}
|
||||
|
|
|
@ -435,12 +435,16 @@ const ProjectController = {
|
|||
SplitTestSessionHandler.sessionMaintenance(req, null, () => {})
|
||||
cb(null, defaultSettingsForAnonymousUser(userId))
|
||||
} else {
|
||||
// Ignore spurious floating promises warning until we promisify
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
User.updateOne(
|
||||
{ _id: new ObjectId(userId) },
|
||||
{ $set: { lastActive: new Date() } },
|
||||
{},
|
||||
() => {}
|
||||
)
|
||||
// Ignore spurious floating promises warning until we promisify
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
User.findById(
|
||||
userId,
|
||||
'email first_name last_name referal_id signUpDate featureSwitches features featuresEpoch refProviders alphaProgram betaProgram isAdmin ace labsProgram completedTutorials writefull',
|
||||
|
@ -693,9 +697,13 @@ const ProjectController = {
|
|||
metrics.inc(metricName)
|
||||
|
||||
if (userId) {
|
||||
AnalyticsManager.recordEventForUser(userId, 'project-opened', {
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'project-opened',
|
||||
{
|
||||
projectId: project._id,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// should not be used in place of split tests query param overrides (?my-split-test-name=my-variant)
|
||||
|
|
|
@ -55,13 +55,13 @@ async function createBlankProject(
|
|||
Object.assign(segmentation, attributes.segmentation)
|
||||
segmentation.projectId = project._id
|
||||
if (isImport) {
|
||||
AnalyticsManager.recordEventForUser(
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
ownerId,
|
||||
'project-imported',
|
||||
segmentation
|
||||
)
|
||||
} else {
|
||||
AnalyticsManager.recordEventForUser(
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
ownerId,
|
||||
'project-created',
|
||||
segmentation
|
||||
|
@ -72,7 +72,7 @@ async function createBlankProject(
|
|||
|
||||
async function createProjectFromSnippet(ownerId, projectName, docLines) {
|
||||
const project = await _createBlankProject(ownerId, projectName)
|
||||
AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
|
||||
AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
|
||||
projectId: project._id,
|
||||
})
|
||||
await _createRootDoc(project, ownerId, docLines)
|
||||
|
@ -85,7 +85,7 @@ async function createBasicProject(ownerId, projectName) {
|
|||
const docLines = await _buildTemplate('mainbasic.tex', ownerId, projectName)
|
||||
await _createRootDoc(project, ownerId, docLines)
|
||||
|
||||
AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
|
||||
AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
|
||||
projectId: project._id,
|
||||
})
|
||||
|
||||
|
@ -97,7 +97,7 @@ async function createExampleProject(ownerId, projectName) {
|
|||
|
||||
await _addExampleProjectFiles(ownerId, projectName, project)
|
||||
|
||||
AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
|
||||
AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
|
||||
projectId: project._id,
|
||||
})
|
||||
|
||||
|
|
|
@ -245,6 +245,8 @@ const ProjectEntityUpdateHandler = {
|
|||
return callback(err)
|
||||
}
|
||||
if (ProjectEntityUpdateHandler.isPathValidForRootDoc(docPath)) {
|
||||
// Ignore spurious floating promises warning until we promisify
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Project.updateOne(
|
||||
{ _id: projectId },
|
||||
{ rootDoc_id: newRootDocID },
|
||||
|
@ -264,6 +266,8 @@ const ProjectEntityUpdateHandler = {
|
|||
|
||||
unsetRootDoc(projectId, callback) {
|
||||
logger.debug({ projectId }, 'removing root doc')
|
||||
// Ignore spurious floating promises warning until we promisify
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Project.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $unset: { rootDoc_id: true } },
|
||||
|
|
|
@ -314,7 +314,19 @@ async function _getAssignment(
|
|||
if (sync === true) {
|
||||
await _recordAssignment(assignmentData)
|
||||
} else {
|
||||
_recordAssignment(assignmentData)
|
||||
_recordAssignment(assignmentData).catch(err => {
|
||||
logger.warn(
|
||||
{
|
||||
err,
|
||||
userId,
|
||||
splitTestName,
|
||||
phase,
|
||||
versionNumber,
|
||||
variantName: selectedVariantName,
|
||||
},
|
||||
'failed to record split test assignment'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
// otherwise this is an anonymous user, we store assignments in session to persist them on registration
|
||||
|
@ -329,11 +341,23 @@ async function _getAssignment(
|
|||
})
|
||||
}
|
||||
|
||||
const effectiveAnalyticsId = user?.analyticsId || analyticsId || userId
|
||||
AnalyticsManager.setUserPropertyForAnalyticsId(
|
||||
user?.analyticsId || analyticsId || userId,
|
||||
effectiveAnalyticsId,
|
||||
`split-test-${splitTestName}-${versionNumber}`,
|
||||
selectedVariantName
|
||||
).catch(err => {
|
||||
logger.warn(
|
||||
{
|
||||
err,
|
||||
analyticsId: effectiveAnalyticsId,
|
||||
splitTest: splitTestName,
|
||||
versionNumber,
|
||||
variant: selectedVariantName,
|
||||
},
|
||||
'failed to set user property for analytics id'
|
||||
)
|
||||
})
|
||||
}
|
||||
return _makeAssignment(splitTest, selectedVariantName, currentVersion)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ async function refreshFeatures(userId, reason) {
|
|||
logger.debug({ userId, features }, 'updating user features')
|
||||
|
||||
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(features)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'feature-set',
|
||||
matchedFeatureSet
|
||||
|
|
|
@ -51,19 +51,27 @@ async function sendRecurlyAnalyticsEvent(event, eventData) {
|
|||
async function _sendSubscriptionStartedEvent(userId, eventData) {
|
||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||
_getSubscriptionData(eventData)
|
||||
AnalyticsManager.recordEventForUser(userId, 'subscription-started', {
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'subscription-started',
|
||||
{
|
||||
plan_code: planCode,
|
||||
quantity,
|
||||
is_trial: isTrial,
|
||||
subscriptionId,
|
||||
})
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
}
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-plan-code',
|
||||
planCode
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-state',
|
||||
state
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-is-trial',
|
||||
isTrial
|
||||
|
@ -77,19 +85,27 @@ async function _sendSubscriptionStartedEvent(userId, eventData) {
|
|||
async function _sendSubscriptionUpdatedEvent(userId, eventData) {
|
||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||
_getSubscriptionData(eventData)
|
||||
AnalyticsManager.recordEventForUser(userId, 'subscription-updated', {
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'subscription-updated',
|
||||
{
|
||||
plan_code: planCode,
|
||||
quantity,
|
||||
is_trial: isTrial,
|
||||
subscriptionId,
|
||||
})
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
}
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-plan-code',
|
||||
planCode
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-state',
|
||||
state
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-is-trial',
|
||||
isTrial
|
||||
|
@ -99,14 +115,22 @@ async function _sendSubscriptionUpdatedEvent(userId, eventData) {
|
|||
async function _sendSubscriptionCancelledEvent(userId, eventData) {
|
||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||
_getSubscriptionData(eventData)
|
||||
AnalyticsManager.recordEventForUser(userId, 'subscription-cancelled', {
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'subscription-cancelled',
|
||||
{
|
||||
plan_code: planCode,
|
||||
quantity,
|
||||
is_trial: isTrial,
|
||||
subscriptionId,
|
||||
})
|
||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
}
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-state',
|
||||
state
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-is-trial',
|
||||
isTrial
|
||||
|
@ -116,19 +140,27 @@ async function _sendSubscriptionCancelledEvent(userId, eventData) {
|
|||
async function _sendSubscriptionExpiredEvent(userId, eventData) {
|
||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||
_getSubscriptionData(eventData)
|
||||
AnalyticsManager.recordEventForUser(userId, 'subscription-expired', {
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'subscription-expired',
|
||||
{
|
||||
plan_code: planCode,
|
||||
quantity,
|
||||
is_trial: isTrial,
|
||||
subscriptionId,
|
||||
})
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
}
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-plan-code',
|
||||
planCode
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-state',
|
||||
state
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-is-trial',
|
||||
isTrial
|
||||
|
@ -138,19 +170,27 @@ async function _sendSubscriptionExpiredEvent(userId, eventData) {
|
|||
async function _sendSubscriptionRenewedEvent(userId, eventData) {
|
||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||
_getSubscriptionData(eventData)
|
||||
AnalyticsManager.recordEventForUser(userId, 'subscription-renewed', {
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'subscription-renewed',
|
||||
{
|
||||
plan_code: planCode,
|
||||
quantity,
|
||||
is_trial: isTrial,
|
||||
subscriptionId,
|
||||
})
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
}
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-plan-code',
|
||||
planCode
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-state',
|
||||
state
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-is-trial',
|
||||
isTrial
|
||||
|
@ -160,18 +200,26 @@ async function _sendSubscriptionRenewedEvent(userId, eventData) {
|
|||
async function _sendSubscriptionReactivatedEvent(userId, eventData) {
|
||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||
_getSubscriptionData(eventData)
|
||||
AnalyticsManager.recordEventForUser(userId, 'subscription-reactivated', {
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'subscription-reactivated',
|
||||
{
|
||||
plan_code: planCode,
|
||||
quantity,
|
||||
subscriptionId,
|
||||
})
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
}
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-plan-code',
|
||||
planCode
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-state',
|
||||
state
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-is-trial',
|
||||
isTrial
|
||||
|
@ -195,7 +243,7 @@ async function _sendInvoicePaidEvent(userId, eventData) {
|
|||
subscriptionIds[`subscriptionId${idx + 1}`] = e
|
||||
}
|
||||
})
|
||||
AnalyticsManager.recordEventForUser(
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'subscription-invoice-collected',
|
||||
{
|
||||
|
@ -208,7 +256,7 @@ async function _sendInvoicePaidEvent(userId, eventData) {
|
|||
...subscriptionIds,
|
||||
}
|
||||
)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'subscription-is-trial',
|
||||
false
|
||||
|
|
|
@ -355,7 +355,7 @@ async function _sendUserGroupPlanCodeUserProperty(userId) {
|
|||
bestFeatures = plan.features
|
||||
}
|
||||
}
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
userId,
|
||||
'group-subscription-plan-code',
|
||||
bestPlanCode
|
||||
|
@ -376,7 +376,7 @@ async function _sendSubscriptionEvent(userId, subscriptionId, event) {
|
|||
if (!subscription || !subscription.groupPlan) {
|
||||
return
|
||||
}
|
||||
AnalyticsManager.recordEventForUser(userId, event, {
|
||||
AnalyticsManager.recordEventForUserInBackground(userId, event, {
|
||||
groupId: subscription._id.toString(),
|
||||
subscriptionId: subscription.recurlySubscription_id,
|
||||
})
|
||||
|
@ -397,7 +397,7 @@ async function _sendSubscriptionEventForAllMembers(subscriptionId, event) {
|
|||
const userIds = (subscription.member_ids || []).filter(Boolean)
|
||||
for (const userId of userIds) {
|
||||
if (userId) {
|
||||
AnalyticsManager.recordEventForUser(userId, event, {
|
||||
AnalyticsManager.recordEventForUserInBackground(userId, event, {
|
||||
groupId: subscription._id.toString(),
|
||||
subscriptionId: subscription.recurlySubscription_id,
|
||||
})
|
||||
|
|
|
@ -3,6 +3,7 @@ const {
|
|||
addRequiredCleanupHandlerBeforeDrainingConnections,
|
||||
} = require('../../infrastructure/GracefulShutdown')
|
||||
const { callbackifyAll } = require('@overleaf/promise-utils')
|
||||
const logger = require('@overleaf/logger')
|
||||
|
||||
const SystemMessageManager = {
|
||||
getMessages() {
|
||||
|
@ -22,9 +23,14 @@ const SystemMessageManager = {
|
|||
await message.save()
|
||||
},
|
||||
|
||||
async refreshCache() {
|
||||
const messages = await this.getMessagesFromDB()
|
||||
refreshCache() {
|
||||
this.getMessagesFromDB()
|
||||
.then(messages => {
|
||||
this._cachedMessages = messages
|
||||
})
|
||||
.catch(err => {
|
||||
logger.warn({ err }, 'failed to refresh system messages cache')
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ const TokenAccessHandler = {
|
|||
async addReadOnlyUserToProject(userId, projectId) {
|
||||
userId = new ObjectId(userId.toString())
|
||||
projectId = new ObjectId(projectId.toString())
|
||||
Analytics.recordEventForUser(userId, 'project-joined', {
|
||||
Analytics.recordEventForUserInBackground(userId, 'project-joined', {
|
||||
mode: 'read-only',
|
||||
})
|
||||
|
||||
|
@ -171,7 +171,7 @@ const TokenAccessHandler = {
|
|||
async addReadAndWriteUserToProject(userId, projectId) {
|
||||
userId = new ObjectId(userId.toString())
|
||||
projectId = new ObjectId(projectId.toString())
|
||||
Analytics.recordEventForUser(userId, 'project-joined', {
|
||||
Analytics.recordEventForUserInBackground(userId, 'project-joined', {
|
||||
mode: 'read-write',
|
||||
})
|
||||
|
||||
|
|
|
@ -165,21 +165,21 @@ function _getUserQuery(providerId, externalUserId) {
|
|||
return query
|
||||
}
|
||||
|
||||
async function _sendSecurityAlert(accountLinked, providerId, user, userId) {
|
||||
function _sendSecurityAlert(accountLinked, providerId, user, userId) {
|
||||
const providerName = oauthProviders[providerId].name
|
||||
const emailOptions = EmailOptionsHelper.linkOrUnlink(
|
||||
accountLinked,
|
||||
providerName,
|
||||
user.email
|
||||
)
|
||||
try {
|
||||
await EmailHandler.promises.sendEmail('securityAlert', emailOptions)
|
||||
} catch (error) {
|
||||
EmailHandler.promises
|
||||
.sendEmail('securityAlert', emailOptions)
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
{ err: error, userId },
|
||||
`could not send security alert email when ${emailOptions.action.toLowerCase()}`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function _thirdPartyIdentifierUpdate(
|
||||
|
|
|
@ -43,7 +43,11 @@ async function recordRegistrationEvent(user) {
|
|||
if (user.thirdPartyIdentifiers && user.thirdPartyIdentifiers.length > 0) {
|
||||
segmentation.provider = user.thirdPartyIdentifiers[0].providerId
|
||||
}
|
||||
Analytics.recordEventForUser(user._id, 'user-registered', segmentation)
|
||||
Analytics.recordEventForUserInBackground(
|
||||
user._id,
|
||||
'user-registered',
|
||||
segmentation
|
||||
)
|
||||
} catch (err) {
|
||||
logger.warn({ err }, 'there was an error recording `user-registered` event')
|
||||
}
|
||||
|
|
|
@ -319,11 +319,15 @@ async function checkSecondaryEmailConfirmationCode(req, res) {
|
|||
|
||||
delete req.session.pendingSecondaryEmail
|
||||
|
||||
AnalyticsManager.recordEventForUser(user._id, 'email-verified', {
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
user._id,
|
||||
'email-verified',
|
||||
{
|
||||
provider: 'email',
|
||||
verification_type: 'token',
|
||||
isPrimary: false,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const redirectUrl =
|
||||
AuthenticationController.getRedirectFromSession(req) || '/project'
|
||||
|
@ -427,7 +431,7 @@ async function primaryEmailCheckPage(req, res) {
|
|||
return res.redirect('/project')
|
||||
}
|
||||
|
||||
AnalyticsManager.recordEventForUser(
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'primary-email-check-page-displayed'
|
||||
)
|
||||
|
@ -439,7 +443,10 @@ async function primaryEmailCheck(req, res) {
|
|||
await UserUpdater.promises.updateUser(userId, {
|
||||
$set: { lastPrimaryEmailCheck: new Date() },
|
||||
})
|
||||
AnalyticsManager.recordEventForUser(userId, 'primary-email-check-done')
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'primary-email-check-done'
|
||||
)
|
||||
AsyncFormHelper.redirect(req, res, '/project')
|
||||
}
|
||||
|
||||
|
@ -610,7 +617,7 @@ const UserEmailsController = {
|
|||
)
|
||||
}
|
||||
const isPrimary = user?.email === userData.email
|
||||
AnalyticsManager.recordEventForUser(
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userData.userId,
|
||||
'email-verified',
|
||||
{
|
||||
|
|
|
@ -113,14 +113,14 @@ const UserRegistrationHandler = {
|
|||
|
||||
const setNewPasswordUrl = `${settings.siteUrl}/user/activate?token=${token}&user_id=${user._id}`
|
||||
|
||||
try {
|
||||
await EmailHandler.promises.sendEmail('registered', {
|
||||
EmailHandler.promises
|
||||
.sendEmail('registered', {
|
||||
to: user.email,
|
||||
setNewPasswordUrl,
|
||||
})
|
||||
} catch (error) {
|
||||
.catch(error => {
|
||||
logger.warn({ err: error }, 'failed to send activation email')
|
||||
}
|
||||
})
|
||||
|
||||
return { user, setNewPasswordUrl }
|
||||
},
|
||||
|
|
|
@ -75,7 +75,10 @@ async function addEmailAddress(userId, newEmail, affiliationOptions, auditLog) {
|
|||
|
||||
await UserGetter.promises.ensureUniqueEmailAddress(newEmail)
|
||||
|
||||
AnalyticsManager.recordEventForUser(userId, 'secondary-email-added')
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'secondary-email-added'
|
||||
)
|
||||
|
||||
await UserAuditLogHandler.promises.addEntry(
|
||||
userId,
|
||||
|
@ -201,7 +204,10 @@ async function setDefaultEmailAddress(
|
|||
throw new Error('email update error')
|
||||
}
|
||||
|
||||
AnalyticsManager.recordEventForUser(userId, 'primary-email-address-updated')
|
||||
AnalyticsManager.recordEventForUserInBackground(
|
||||
userId,
|
||||
'primary-email-address-updated'
|
||||
)
|
||||
|
||||
if (sendSecurityAlert) {
|
||||
// no need to wait, errors are logged and not passed back
|
||||
|
|
|
@ -76,6 +76,9 @@ i18n
|
|||
supportedLngs: availableLanguageCodes,
|
||||
fallbackLng: fallbackLanguageCode,
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error({ err }, 'failed to initialize i18next library')
|
||||
})
|
||||
|
||||
// Make custom language detector for Accept-Language header
|
||||
const headerLangDetector = new middleware.LanguageDetector(i18n.services, {
|
||||
|
|
|
@ -169,6 +169,8 @@ module.exports = LaunchpadController = {
|
|||
return next(err)
|
||||
}
|
||||
|
||||
// Ignore spurious floating promises warning until we promisify
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
User.updateOne(
|
||||
{ _id: user._id },
|
||||
{
|
||||
|
@ -245,6 +247,8 @@ module.exports = LaunchpadController = {
|
|||
}
|
||||
|
||||
logger.debug({ userId: user._id }, 'making user an admin')
|
||||
// Ignore spurious floating promises warning until we promisify
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
User.updateOne(
|
||||
{ _id: user._id },
|
||||
{
|
||||
|
|
|
@ -245,8 +245,8 @@
|
|||
"@types/recurly__recurly-js": "^4.22.0",
|
||||
"@types/sinon-chai": "^3.2.8",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@uppy/core": "^3.8.0",
|
||||
"@uppy/dashboard": "^3.7.1",
|
||||
"@uppy/drag-drop": "^3.0.3",
|
||||
|
|
|
@ -81,7 +81,7 @@ const checkAndUpdateUser = (user, callback) =>
|
|||
}
|
||||
|
||||
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(freshFeatures)
|
||||
AnalyticsManager.setUserPropertyForUser(
|
||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||
user._id,
|
||||
'feature-set',
|
||||
matchedFeatureSet
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
|
|||
requires: {
|
||||
'./AnalyticsManager': (this.AnalyticsManager = {
|
||||
recordEventForSession: sinon.stub().resolves(),
|
||||
setUserPropertyForSession: sinon.stub().resolves(),
|
||||
setUserPropertyForSessionInBackground: sinon.stub(),
|
||||
}),
|
||||
'@overleaf/settings': {
|
||||
siteUrl: 'https://www.overleaf.com',
|
||||
|
@ -56,7 +56,9 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
|
|||
|
||||
it('no event or user property is recorded', function () {
|
||||
sinon.assert.notCalled(this.AnalyticsManager.recordEventForSession)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForSession)
|
||||
sinon.assert.notCalled(
|
||||
this.AnalyticsManager.setUserPropertyForSessionInBackground
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -101,7 +103,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
|
|||
|
||||
it('utm-tags user property is set for session', function () {
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForSession,
|
||||
this.AnalyticsManager.setUserPropertyForSessionInBackground,
|
||||
this.req.session,
|
||||
'utm-tags',
|
||||
'Organic;Facebook;Some Campaign;foo-bar'
|
||||
|
@ -146,7 +148,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
|
|||
|
||||
it('utm-tags user property is set for session', function () {
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForSession,
|
||||
this.AnalyticsManager.setUserPropertyForSessionInBackground,
|
||||
this.req.session,
|
||||
'utm-tags',
|
||||
'N/A;Facebook;Some Campaign;foo'
|
||||
|
@ -190,7 +192,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
|
|||
|
||||
it('utm-tags user property is set for session', function () {
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForSession,
|
||||
this.AnalyticsManager.setUserPropertyForSessionInBackground,
|
||||
this.req.session,
|
||||
'utm-tags',
|
||||
'N/A;Facebook;Some Campaign;N/A'
|
||||
|
|
|
@ -88,7 +88,7 @@ describe('AuthenticationController', function () {
|
|||
setupLoginData: sinon.stub(),
|
||||
}),
|
||||
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
||||
recordEventForUser: sinon.stub(),
|
||||
recordEventForUserInBackground: sinon.stub(),
|
||||
identifyUser: sinon.stub(),
|
||||
getIdsFromSession: sinon.stub().returns({ userId: this.user._id }),
|
||||
}),
|
||||
|
@ -1476,7 +1476,7 @@ describe('AuthenticationController', function () {
|
|||
|
||||
it('should track the login event', function () {
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.user._id,
|
||||
'user-logged-in'
|
||||
)
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('BetaProgramHandler', function () {
|
|||
},
|
||||
}),
|
||||
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
||||
setUserPropertyForUser: sinon.stub().resolves(),
|
||||
setUserPropertyForUserInBackground: sinon.stub(),
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
@ -54,7 +54,7 @@ describe('BetaProgramHandler', function () {
|
|||
this.call(err => {
|
||||
expect(err).to.not.exist
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.user_id,
|
||||
'beta-program',
|
||||
true
|
||||
|
@ -105,7 +105,7 @@ describe('BetaProgramHandler', function () {
|
|||
this.call(err => {
|
||||
expect(err).to.not.exist
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.user_id,
|
||||
'beta-program',
|
||||
false
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
getSessionUser: sinon.stub().returns(this.currentUser),
|
||||
}
|
||||
|
||||
this.AnalyticsManger = { recordEventForUser: sinon.stub() }
|
||||
this.AnalyticsManger = { recordEventForUserInBackground: sinon.stub() }
|
||||
|
||||
this.rateLimiter = {
|
||||
consume: sinon.stub().resolves(),
|
||||
|
|
|
@ -74,7 +74,8 @@ describe('OwnershipTransferHandler', function () {
|
|||
'../Email/EmailHandler': this.EmailHandler,
|
||||
'./CollaboratorsHandler': this.CollaboratorsHandler,
|
||||
'../Analytics/AnalyticsManager': {
|
||||
recordEventForUser: (this.recordEventForUser = sinon.stub()),
|
||||
recordEventForUserInBackground: (this.recordEventForUserInBackground =
|
||||
sinon.stub()),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -214,7 +215,7 @@ describe('OwnershipTransferHandler', function () {
|
|||
this.collaborator._id,
|
||||
{ sessionUserId }
|
||||
)
|
||||
expect(this.recordEventForUser).to.have.been.calledWith(
|
||||
expect(this.recordEventForUserInBackground).to.have.been.calledWith(
|
||||
this.user._id,
|
||||
'project-ownership-transfer',
|
||||
{
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('EmailHandler', function () {
|
|||
},
|
||||
}
|
||||
this.Queues = {
|
||||
createScheduledJob: sinon.stub(),
|
||||
createScheduledJob: sinon.stub().resolves(),
|
||||
}
|
||||
this.EmailHandler = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
|
|
|
@ -189,7 +189,9 @@ describe('ProjectController', function () {
|
|||
this.BrandVariationsHandler,
|
||||
'../ThirdPartyDataStore/TpdsProjectFlusher': this.TpdsProjectFlusher,
|
||||
'../../models/Project': {},
|
||||
'../Analytics/AnalyticsManager': { recordEventForUser: () => {} },
|
||||
'../Analytics/AnalyticsManager': {
|
||||
recordEventForUserInBackground: () => {},
|
||||
},
|
||||
'../Subscription/SubscriptionViewModelBuilder':
|
||||
this.SubscriptionViewModelBuilder,
|
||||
'../Spelling/SpellingHandler': {
|
||||
|
|
|
@ -48,7 +48,7 @@ describe('SplitTestHandler', function () {
|
|||
}
|
||||
this.AnalyticsManager = {
|
||||
getIdsFromSession: sinon.stub(),
|
||||
setUserPropertyForAnalyticsId: sinon.stub(),
|
||||
setUserPropertyForAnalyticsId: sinon.stub().resolves(),
|
||||
}
|
||||
this.LocalsHelper = {
|
||||
setSplitTestVariant: sinon.stub(),
|
||||
|
|
|
@ -93,7 +93,7 @@ describe('FeaturesUpdater', function () {
|
|||
.resolves(this.user)
|
||||
|
||||
this.AnalyticsManager = {
|
||||
setUserPropertyForUser: sinon.stub(),
|
||||
setUserPropertyForUserInBackground: sinon.stub(),
|
||||
}
|
||||
this.Modules = {
|
||||
promises: { hooks: { fire: sinon.stub().resolves() } },
|
||||
|
@ -141,7 +141,7 @@ describe('FeaturesUpdater', function () {
|
|||
|
||||
it('should send the corresponding feature set user property', function () {
|
||||
expect(
|
||||
this.AnalyticsManager.setUserPropertyForUser
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground
|
||||
).to.have.been.calledWith(this.user._id, 'feature-set', 'all')
|
||||
})
|
||||
})
|
||||
|
@ -159,7 +159,7 @@ describe('FeaturesUpdater', function () {
|
|||
|
||||
it('should send mixed feature set user property', function () {
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.user._id,
|
||||
'feature-set',
|
||||
'mixed'
|
||||
|
|
|
@ -31,8 +31,8 @@ describe('RecurlyEventHandler', function () {
|
|||
sendTrialOnboardingEmail: sinon.stub(),
|
||||
}),
|
||||
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
||||
recordEventForUser: sinon.stub(),
|
||||
setUserPropertyForUser: sinon.stub(),
|
||||
recordEventForUserInBackground: sinon.stub(),
|
||||
setUserPropertyForUserInBackground: sinon.stub(),
|
||||
}),
|
||||
'../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
|
||||
promises: {
|
||||
|
@ -51,7 +51,7 @@ describe('RecurlyEventHandler', function () {
|
|||
this.eventData
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-started',
|
||||
{
|
||||
|
@ -62,19 +62,19 @@ describe('RecurlyEventHandler', function () {
|
|||
}
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-plan-code',
|
||||
this.planCode
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-state',
|
||||
'active'
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-is-trial',
|
||||
true
|
||||
|
@ -104,7 +104,7 @@ describe('RecurlyEventHandler', function () {
|
|||
this.eventData
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-started',
|
||||
{
|
||||
|
@ -115,13 +115,13 @@ describe('RecurlyEventHandler', function () {
|
|||
}
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-state',
|
||||
'active'
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-is-trial',
|
||||
false
|
||||
|
@ -136,7 +136,7 @@ describe('RecurlyEventHandler', function () {
|
|||
this.eventData
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-updated',
|
||||
{
|
||||
|
@ -147,19 +147,19 @@ describe('RecurlyEventHandler', function () {
|
|||
}
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-plan-code',
|
||||
this.planCode
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-state',
|
||||
'active'
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-is-trial',
|
||||
true
|
||||
|
@ -173,7 +173,7 @@ describe('RecurlyEventHandler', function () {
|
|||
this.eventData
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-cancelled',
|
||||
{
|
||||
|
@ -184,13 +184,13 @@ describe('RecurlyEventHandler', function () {
|
|||
}
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-state',
|
||||
'cancelled'
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-is-trial',
|
||||
true
|
||||
|
@ -204,7 +204,7 @@ describe('RecurlyEventHandler', function () {
|
|||
this.eventData
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-expired',
|
||||
{
|
||||
|
@ -215,19 +215,19 @@ describe('RecurlyEventHandler', function () {
|
|||
}
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-plan-code',
|
||||
this.planCode
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-state',
|
||||
'expired'
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-is-trial',
|
||||
true
|
||||
|
@ -240,7 +240,7 @@ describe('RecurlyEventHandler', function () {
|
|||
this.eventData
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-renewed',
|
||||
{
|
||||
|
@ -258,7 +258,7 @@ describe('RecurlyEventHandler', function () {
|
|||
this.eventData
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-reactivated',
|
||||
{
|
||||
|
@ -292,7 +292,7 @@ describe('RecurlyEventHandler', function () {
|
|||
}
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-invoice-collected',
|
||||
{
|
||||
|
@ -321,7 +321,7 @@ describe('RecurlyEventHandler', function () {
|
|||
},
|
||||
}
|
||||
)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.recordEventForUser)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground)
|
||||
})
|
||||
|
||||
it('with closed_invoice_notification', function () {
|
||||
|
@ -338,7 +338,7 @@ describe('RecurlyEventHandler', function () {
|
|||
}
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'subscription-invoice-collected'
|
||||
)
|
||||
|
@ -357,7 +357,7 @@ describe('RecurlyEventHandler', function () {
|
|||
},
|
||||
}
|
||||
)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.recordEventForUser)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground)
|
||||
})
|
||||
|
||||
it('nothing is called with invalid account code', function () {
|
||||
|
@ -367,9 +367,15 @@ describe('RecurlyEventHandler', function () {
|
|||
'new_subscription_notification',
|
||||
this.eventData
|
||||
)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.recordEventForUser)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForUser)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForUser)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForUser)
|
||||
sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground)
|
||||
sinon.assert.notCalled(
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground
|
||||
)
|
||||
sinon.assert.notCalled(
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground
|
||||
)
|
||||
sinon.assert.notCalled(
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -146,8 +146,8 @@ describe('SubscriptionUpdater', function () {
|
|||
}
|
||||
|
||||
this.AnalyticsManager = {
|
||||
recordEventForUser: sinon.stub().resolves(),
|
||||
setUserPropertyForUser: sinon.stub(),
|
||||
recordEventForUserInBackground: sinon.stub().resolves(),
|
||||
setUserPropertyForUserInBackground: sinon.stub(),
|
||||
}
|
||||
|
||||
this.Features = {
|
||||
|
@ -448,7 +448,7 @@ describe('SubscriptionUpdater', function () {
|
|||
.calledWith(searchOps, insertOperation)
|
||||
.should.equal(true)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.otherUserId,
|
||||
'group-subscription-joined',
|
||||
{
|
||||
|
@ -477,7 +477,7 @@ describe('SubscriptionUpdater', function () {
|
|||
this.otherUserId
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.otherUserId,
|
||||
'group-subscription-plan-code',
|
||||
'group_subscription'
|
||||
|
@ -493,7 +493,7 @@ describe('SubscriptionUpdater', function () {
|
|||
this.otherUserId
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.otherUserId,
|
||||
'group-subscription-plan-code',
|
||||
'better_group_subscription'
|
||||
|
@ -509,7 +509,7 @@ describe('SubscriptionUpdater', function () {
|
|||
this.otherUserId
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.otherUserId,
|
||||
'group-subscription-plan-code',
|
||||
'better_group_subscription'
|
||||
|
@ -567,7 +567,7 @@ describe('SubscriptionUpdater', function () {
|
|||
this.otherUserId
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.otherUserId,
|
||||
'group-subscription-left',
|
||||
{
|
||||
|
@ -583,7 +583,7 @@ describe('SubscriptionUpdater', function () {
|
|||
this.otherUserId
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.otherUserId,
|
||||
'group-subscription-plan-code',
|
||||
null
|
||||
|
@ -635,7 +635,7 @@ describe('SubscriptionUpdater', function () {
|
|||
this.otherUserId
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.setUserPropertyForUser,
|
||||
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||
this.otherUserId,
|
||||
'group-subscription-plan-code',
|
||||
null
|
||||
|
@ -682,7 +682,7 @@ describe('SubscriptionUpdater', function () {
|
|||
this.otherUserId
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.otherUserId,
|
||||
'group-subscription-left',
|
||||
{
|
||||
|
@ -691,7 +691,7 @@ describe('SubscriptionUpdater', function () {
|
|||
}
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.AnalyticsManager.recordEventForUser,
|
||||
this.AnalyticsManager.recordEventForUserInBackground,
|
||||
this.otherUserId,
|
||||
'group-subscription-left',
|
||||
{
|
||||
|
|
|
@ -32,7 +32,7 @@ describe('TokenAccessHandler', function () {
|
|||
}),
|
||||
crypto: (this.Crypto = require('crypto')),
|
||||
'../Analytics/AnalyticsManager': (this.Analytics = {
|
||||
recordEventForUser: sinon.stub(),
|
||||
recordEventForUserInBackground: sinon.stub(),
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
@ -127,7 +127,7 @@ describe('TokenAccessHandler', function () {
|
|||
'tokenAccessReadOnly_refs'
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.Analytics.recordEventForUser,
|
||||
this.Analytics.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'project-joined',
|
||||
{ mode: 'read-only' }
|
||||
|
@ -175,7 +175,7 @@ describe('TokenAccessHandler', function () {
|
|||
'tokenAccessReadAndWrite_refs'
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.Analytics.recordEventForUser,
|
||||
this.Analytics.recordEventForUserInBackground,
|
||||
this.userId,
|
||||
'project-joined',
|
||||
{ mode: 'read-write' }
|
||||
|
|
|
@ -144,7 +144,7 @@ describe('ThirdPartyIdentityManager', function () {
|
|||
|
||||
describe('EmailHandler', function () {
|
||||
beforeEach(function () {
|
||||
this.EmailHandler.promises.sendEmail.throws(anError)
|
||||
this.EmailHandler.promises.sendEmail.rejects(anError)
|
||||
})
|
||||
it('should log but not return the error', async function () {
|
||||
await expect(
|
||||
|
@ -219,7 +219,7 @@ describe('ThirdPartyIdentityManager', function () {
|
|||
|
||||
describe('EmailHandler', function () {
|
||||
beforeEach(function () {
|
||||
this.EmailHandler.promises.sendEmail.throws(anError)
|
||||
this.EmailHandler.promises.sendEmail.rejects(anError)
|
||||
})
|
||||
it('should log but not return the error', async function () {
|
||||
await expect(
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('UserCreator', function () {
|
|||
},
|
||||
}),
|
||||
'../Analytics/AnalyticsManager': (this.Analytics = {
|
||||
recordEventForUser: sinon.stub(),
|
||||
recordEventForUserInBackground: sinon.stub(),
|
||||
setUserPropertyForUser: sinon.stub(),
|
||||
}),
|
||||
'../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
|
||||
|
@ -278,7 +278,7 @@ describe('UserCreator', function () {
|
|||
})
|
||||
assert.equal(user.email, this.email)
|
||||
sinon.assert.calledWith(
|
||||
this.Analytics.recordEventForUser,
|
||||
this.Analytics.recordEventForUserInBackground,
|
||||
user._id,
|
||||
'user-registered'
|
||||
)
|
||||
|
|
|
@ -57,7 +57,7 @@ describe('UserEmailsController', function () {
|
|||
}
|
||||
this.HttpErrorHandler = { conflict: sinon.stub() }
|
||||
this.AnalyticsManager = {
|
||||
recordEventForUser: sinon.stub(),
|
||||
recordEventForUserInBackground: sinon.stub(),
|
||||
}
|
||||
this.UserAuditLogHandler = {
|
||||
addEntry: sinon.stub().yields(),
|
||||
|
|
|
@ -40,7 +40,7 @@ describe('UserRegistrationHandler', function () {
|
|||
subscribe: sinon.stub(),
|
||||
}
|
||||
this.EmailHandler = {
|
||||
promises: { sendEmail: sinon.stub() },
|
||||
promises: { sendEmail: sinon.stub().resolves() },
|
||||
}
|
||||
this.OneTimeTokenHandler = { promises: { getNewToken: sinon.stub() } }
|
||||
this.handler = SandboxedModule.require(modulePath, {
|
||||
|
|
|
@ -65,7 +65,7 @@ describe('UserUpdater', function () {
|
|||
},
|
||||
}
|
||||
this.AnalyticsManager = {
|
||||
recordEventForUser: sinon.stub(),
|
||||
recordEventForUserInBackground: sinon.stub(),
|
||||
}
|
||||
this.InstitutionsAPI = {
|
||||
promises: {
|
||||
|
|
Loading…
Reference in a new issue