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) {
|
function recordEventForSession(session, event, segmentation) {
|
||||||
const { analyticsId, userId } = getIdsFromSession(session)
|
const { analyticsId, userId } = getIdsFromSession(session)
|
||||||
if (!analyticsId) {
|
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(
|
async function setUserPropertyForAnalyticsId(
|
||||||
analyticsId,
|
analyticsId,
|
||||||
propertyName,
|
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) {
|
function updateEditingSession(userId, projectId, countryCode, segmentation) {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return
|
return
|
||||||
|
@ -310,8 +338,11 @@ module.exports = {
|
||||||
identifyUser,
|
identifyUser,
|
||||||
recordEventForSession,
|
recordEventForSession,
|
||||||
recordEventForUser,
|
recordEventForUser,
|
||||||
|
recordEventForUserInBackground,
|
||||||
setUserPropertyForUser,
|
setUserPropertyForUser,
|
||||||
|
setUserPropertyForUserInBackground,
|
||||||
setUserPropertyForSession,
|
setUserPropertyForSession,
|
||||||
|
setUserPropertyForSessionInBackground,
|
||||||
setUserPropertyForAnalyticsId,
|
setUserPropertyForAnalyticsId,
|
||||||
updateEditingSession,
|
updateEditingSession,
|
||||||
getIdsFromSession,
|
getIdsFromSession,
|
||||||
|
|
|
@ -31,25 +31,25 @@ function addUserProperties(userId, session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.required_login_from_product_medium) {
|
if (session.required_login_from_product_medium) {
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
`registered-from-product-medium`,
|
`registered-from-product-medium`,
|
||||||
session.required_login_from_product_medium
|
session.required_login_from_product_medium
|
||||||
)
|
)
|
||||||
if (session.required_login_from_product_source) {
|
if (session.required_login_from_product_source) {
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
`registered-from-product-source`,
|
`registered-from-product-source`,
|
||||||
session.required_login_from_product_source
|
session.required_login_from_product_source
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (session.referal_id) {
|
} else if (session.referal_id) {
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
`registered-from-bonus-scheme`,
|
`registered-from-bonus-scheme`,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
`registered-from-product-medium`,
|
`registered-from-product-medium`,
|
||||||
'bonus-scheme'
|
'bonus-scheme'
|
||||||
|
@ -58,7 +58,7 @@ function addUserProperties(userId, session) {
|
||||||
|
|
||||||
if (session.inbound) {
|
if (session.inbound) {
|
||||||
if (session.inbound.referrer && session.inbound.referrer.medium) {
|
if (session.inbound.referrer && session.inbound.referrer.medium) {
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
`registered-from-referrer-medium`,
|
`registered-from-referrer-medium`,
|
||||||
`${session.inbound.referrer.medium
|
`${session.inbound.referrer.medium
|
||||||
|
@ -66,7 +66,7 @@ function addUserProperties(userId, session) {
|
||||||
.toUpperCase()}${session.inbound.referrer.medium.slice(1)}`
|
.toUpperCase()}${session.inbound.referrer.medium.slice(1)}`
|
||||||
)
|
)
|
||||||
if (session.inbound.referrer.source) {
|
if (session.inbound.referrer.source) {
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
`registered-from-referrer-source`,
|
`registered-from-referrer-source`,
|
||||||
session.inbound.referrer.source
|
session.inbound.referrer.source
|
||||||
|
@ -77,7 +77,7 @@ function addUserProperties(userId, session) {
|
||||||
if (session.inbound.utm) {
|
if (session.inbound.utm) {
|
||||||
for (const utmKey of RequestHelper.UTM_KEYS) {
|
for (const utmKey of RequestHelper.UTM_KEYS) {
|
||||||
if (session.inbound.utm[utmKey]) {
|
if (session.inbound.utm[utmKey]) {
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
`registered-from-${utmKey.replace('_', '-')}`,
|
`registered-from-${utmKey.replace('_', '-')}`,
|
||||||
session.inbound.utm[utmKey]
|
session.inbound.utm[utmKey]
|
||||||
|
|
|
@ -27,7 +27,7 @@ function recordUTMTags() {
|
||||||
};${utmValues.utm_campaign || 'N/A'};${
|
};${utmValues.utm_campaign || 'N/A'};${
|
||||||
utmValues.utm_content || utmValues.utm_term || 'N/A'
|
utmValues.utm_content || utmValues.utm_term || 'N/A'
|
||||||
}`
|
}`
|
||||||
AnalyticsManager.setUserPropertyForSession(
|
AnalyticsManager.setUserPropertyForSessionInBackground(
|
||||||
req.session,
|
req.session,
|
||||||
'utm-tags',
|
'utm-tags',
|
||||||
propertyValue
|
propertyValue
|
||||||
|
|
|
@ -646,7 +646,7 @@ function _loginAsyncHandlers(req, user, anonymousAnalyticsId, isNewUser) {
|
||||||
LoginRateLimiter.recordSuccessfulLogin(user.email, () => {})
|
LoginRateLimiter.recordSuccessfulLogin(user.email, () => {})
|
||||||
AuthenticationController._recordSuccessfulLogin(user._id, () => {})
|
AuthenticationController._recordSuccessfulLogin(user._id, () => {})
|
||||||
AuthenticationController.ipMatchCheck(req, user)
|
AuthenticationController.ipMatchCheck(req, user)
|
||||||
Analytics.recordEventForUser(user._id, 'user-logged-in', {
|
Analytics.recordEventForUserInBackground(user._id, 'user-logged-in', {
|
||||||
source: req.session.saml
|
source: req.session.saml
|
||||||
? 'saml'
|
? 'saml'
|
||||||
: req.user_info?.auth_provider || 'email-password',
|
: req.user_info?.auth_provider || 'email-password',
|
||||||
|
|
|
@ -6,7 +6,11 @@ const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
||||||
async function optIn(userId) {
|
async function optIn(userId) {
|
||||||
await UserUpdater.promises.updateUser(userId, { $set: { betaProgram: true } })
|
await UserUpdater.promises.updateUser(userId, { $set: { betaProgram: true } })
|
||||||
metrics.inc('beta-program.opt-in')
|
metrics.inc('beta-program.opt-in')
|
||||||
AnalyticsManager.setUserPropertyForUser(userId, 'beta-program', true)
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'beta-program',
|
||||||
|
true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function optOut(userId) {
|
async function optOut(userId) {
|
||||||
|
@ -14,7 +18,11 @@ async function optOut(userId) {
|
||||||
$set: { betaProgram: false },
|
$set: { betaProgram: false },
|
||||||
})
|
})
|
||||||
metrics.inc('beta-program.opt-out')
|
metrics.inc('beta-program.opt-out')
|
||||||
AnalyticsManager.setUserPropertyForUser(userId, 'beta-program', false)
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'beta-program',
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -348,7 +348,7 @@ const CollaboratorsInviteController = {
|
||||||
'project:membership:changed',
|
'project:membership:changed',
|
||||||
{ invites: true, members: true }
|
{ invites: true, members: true }
|
||||||
)
|
)
|
||||||
AnalyticsManager.recordEventForUser(
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
currentUser._id,
|
currentUser._id,
|
||||||
'project-invite-accept',
|
'project-invite-accept',
|
||||||
{
|
{
|
||||||
|
|
|
@ -158,7 +158,7 @@ const CollaboratorsInviteHandler = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async acceptInvite(invite, projectId, user) {
|
async acceptInvite(invite, projectId, user) {
|
||||||
CollaboratorsHandler.promises.addUserIdToProject(
|
await CollaboratorsHandler.promises.addUserIdToProject(
|
||||||
projectId,
|
projectId,
|
||||||
invite.sendingUserId,
|
invite.sendingUserId,
|
||||||
user._id,
|
user._id,
|
||||||
|
|
|
@ -38,7 +38,7 @@ async function transferOwnership(projectId, newOwnerId, options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track the change of ownership in BigQuery.
|
// Track the change of ownership in BigQuery.
|
||||||
AnalyticsManager.recordEventForUser(
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
previousOwnerId,
|
previousOwnerId,
|
||||||
'project-ownership-transfer',
|
'project-ownership-transfer',
|
||||||
{ projectId, newOwnerId }
|
{ projectId, newOwnerId }
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const { callbackify } = require('util')
|
const { callbackify } = require('util')
|
||||||
const Settings = require('@overleaf/settings')
|
const Settings = require('@overleaf/settings')
|
||||||
|
const logger = require('@overleaf/logger')
|
||||||
const EmailBuilder = require('./EmailBuilder')
|
const EmailBuilder = require('./EmailBuilder')
|
||||||
const EmailSender = require('./EmailSender')
|
const EmailSender = require('./EmailSender')
|
||||||
const Queues = require('../../infrastructure/Queues')
|
const Queues = require('../../infrastructure/Queues')
|
||||||
|
@ -30,5 +31,7 @@ function sendDeferredEmail(emailType, opts, delay) {
|
||||||
'deferred-emails',
|
'deferred-emails',
|
||||||
{ data: { emailType, opts } },
|
{ data: { emailType, opts } },
|
||||||
delay
|
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)
|
return LinkedFilesController.handleError(err, req, res, next)
|
||||||
}
|
}
|
||||||
if (name.endsWith('.bib')) {
|
if (name.endsWith('.bib')) {
|
||||||
AnalyticsManager.recordEventForUser(userId, 'linked-bib-file', {
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'linked-bib-file',
|
||||||
|
{
|
||||||
integration: provider,
|
integration: provider,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return res.json({ new_file_id: newFileId })
|
return res.json({ new_file_id: newFileId })
|
||||||
}
|
}
|
||||||
|
|
|
@ -435,12 +435,16 @@ const ProjectController = {
|
||||||
SplitTestSessionHandler.sessionMaintenance(req, null, () => {})
|
SplitTestSessionHandler.sessionMaintenance(req, null, () => {})
|
||||||
cb(null, defaultSettingsForAnonymousUser(userId))
|
cb(null, defaultSettingsForAnonymousUser(userId))
|
||||||
} else {
|
} else {
|
||||||
|
// Ignore spurious floating promises warning until we promisify
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
User.updateOne(
|
User.updateOne(
|
||||||
{ _id: new ObjectId(userId) },
|
{ _id: new ObjectId(userId) },
|
||||||
{ $set: { lastActive: new Date() } },
|
{ $set: { lastActive: new Date() } },
|
||||||
{},
|
{},
|
||||||
() => {}
|
() => {}
|
||||||
)
|
)
|
||||||
|
// Ignore spurious floating promises warning until we promisify
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
User.findById(
|
User.findById(
|
||||||
userId,
|
userId,
|
||||||
'email first_name last_name referal_id signUpDate featureSwitches features featuresEpoch refProviders alphaProgram betaProgram isAdmin ace labsProgram completedTutorials writefull',
|
'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)
|
metrics.inc(metricName)
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
AnalyticsManager.recordEventForUser(userId, 'project-opened', {
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'project-opened',
|
||||||
|
{
|
||||||
projectId: project._id,
|
projectId: project._id,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// should not be used in place of split tests query param overrides (?my-split-test-name=my-variant)
|
// 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)
|
Object.assign(segmentation, attributes.segmentation)
|
||||||
segmentation.projectId = project._id
|
segmentation.projectId = project._id
|
||||||
if (isImport) {
|
if (isImport) {
|
||||||
AnalyticsManager.recordEventForUser(
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
ownerId,
|
ownerId,
|
||||||
'project-imported',
|
'project-imported',
|
||||||
segmentation
|
segmentation
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
AnalyticsManager.recordEventForUser(
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
ownerId,
|
ownerId,
|
||||||
'project-created',
|
'project-created',
|
||||||
segmentation
|
segmentation
|
||||||
|
@ -72,7 +72,7 @@ async function createBlankProject(
|
||||||
|
|
||||||
async function createProjectFromSnippet(ownerId, projectName, docLines) {
|
async function createProjectFromSnippet(ownerId, projectName, docLines) {
|
||||||
const project = await _createBlankProject(ownerId, projectName)
|
const project = await _createBlankProject(ownerId, projectName)
|
||||||
AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
|
AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
|
||||||
projectId: project._id,
|
projectId: project._id,
|
||||||
})
|
})
|
||||||
await _createRootDoc(project, ownerId, docLines)
|
await _createRootDoc(project, ownerId, docLines)
|
||||||
|
@ -85,7 +85,7 @@ async function createBasicProject(ownerId, projectName) {
|
||||||
const docLines = await _buildTemplate('mainbasic.tex', ownerId, projectName)
|
const docLines = await _buildTemplate('mainbasic.tex', ownerId, projectName)
|
||||||
await _createRootDoc(project, ownerId, docLines)
|
await _createRootDoc(project, ownerId, docLines)
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
|
AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
|
||||||
projectId: project._id,
|
projectId: project._id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ async function createExampleProject(ownerId, projectName) {
|
||||||
|
|
||||||
await _addExampleProjectFiles(ownerId, projectName, project)
|
await _addExampleProjectFiles(ownerId, projectName, project)
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
|
AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
|
||||||
projectId: project._id,
|
projectId: project._id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -245,6 +245,8 @@ const ProjectEntityUpdateHandler = {
|
||||||
return callback(err)
|
return callback(err)
|
||||||
}
|
}
|
||||||
if (ProjectEntityUpdateHandler.isPathValidForRootDoc(docPath)) {
|
if (ProjectEntityUpdateHandler.isPathValidForRootDoc(docPath)) {
|
||||||
|
// Ignore spurious floating promises warning until we promisify
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
Project.updateOne(
|
Project.updateOne(
|
||||||
{ _id: projectId },
|
{ _id: projectId },
|
||||||
{ rootDoc_id: newRootDocID },
|
{ rootDoc_id: newRootDocID },
|
||||||
|
@ -264,6 +266,8 @@ const ProjectEntityUpdateHandler = {
|
||||||
|
|
||||||
unsetRootDoc(projectId, callback) {
|
unsetRootDoc(projectId, callback) {
|
||||||
logger.debug({ projectId }, 'removing root doc')
|
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(
|
Project.updateOne(
|
||||||
{ _id: projectId },
|
{ _id: projectId },
|
||||||
{ $unset: { rootDoc_id: true } },
|
{ $unset: { rootDoc_id: true } },
|
||||||
|
|
|
@ -314,7 +314,19 @@ async function _getAssignment(
|
||||||
if (sync === true) {
|
if (sync === true) {
|
||||||
await _recordAssignment(assignmentData)
|
await _recordAssignment(assignmentData)
|
||||||
} else {
|
} 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
|
// 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(
|
AnalyticsManager.setUserPropertyForAnalyticsId(
|
||||||
user?.analyticsId || analyticsId || userId,
|
effectiveAnalyticsId,
|
||||||
`split-test-${splitTestName}-${versionNumber}`,
|
`split-test-${splitTestName}-${versionNumber}`,
|
||||||
selectedVariantName
|
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)
|
return _makeAssignment(splitTest, selectedVariantName, currentVersion)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ async function refreshFeatures(userId, reason) {
|
||||||
logger.debug({ userId, features }, 'updating user features')
|
logger.debug({ userId, features }, 'updating user features')
|
||||||
|
|
||||||
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(features)
|
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(features)
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'feature-set',
|
'feature-set',
|
||||||
matchedFeatureSet
|
matchedFeatureSet
|
||||||
|
|
|
@ -51,19 +51,27 @@ async function sendRecurlyAnalyticsEvent(event, eventData) {
|
||||||
async function _sendSubscriptionStartedEvent(userId, eventData) {
|
async function _sendSubscriptionStartedEvent(userId, eventData) {
|
||||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||||
_getSubscriptionData(eventData)
|
_getSubscriptionData(eventData)
|
||||||
AnalyticsManager.recordEventForUser(userId, 'subscription-started', {
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'subscription-started',
|
||||||
|
{
|
||||||
plan_code: planCode,
|
plan_code: planCode,
|
||||||
quantity,
|
quantity,
|
||||||
is_trial: isTrial,
|
is_trial: isTrial,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
})
|
}
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-plan-code',
|
'subscription-plan-code',
|
||||||
planCode
|
planCode
|
||||||
)
|
)
|
||||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
userId,
|
||||||
|
'subscription-state',
|
||||||
|
state
|
||||||
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
isTrial
|
isTrial
|
||||||
|
@ -77,19 +85,27 @@ async function _sendSubscriptionStartedEvent(userId, eventData) {
|
||||||
async function _sendSubscriptionUpdatedEvent(userId, eventData) {
|
async function _sendSubscriptionUpdatedEvent(userId, eventData) {
|
||||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||||
_getSubscriptionData(eventData)
|
_getSubscriptionData(eventData)
|
||||||
AnalyticsManager.recordEventForUser(userId, 'subscription-updated', {
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'subscription-updated',
|
||||||
|
{
|
||||||
plan_code: planCode,
|
plan_code: planCode,
|
||||||
quantity,
|
quantity,
|
||||||
is_trial: isTrial,
|
is_trial: isTrial,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
})
|
}
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-plan-code',
|
'subscription-plan-code',
|
||||||
planCode
|
planCode
|
||||||
)
|
)
|
||||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
userId,
|
||||||
|
'subscription-state',
|
||||||
|
state
|
||||||
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
isTrial
|
isTrial
|
||||||
|
@ -99,14 +115,22 @@ async function _sendSubscriptionUpdatedEvent(userId, eventData) {
|
||||||
async function _sendSubscriptionCancelledEvent(userId, eventData) {
|
async function _sendSubscriptionCancelledEvent(userId, eventData) {
|
||||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||||
_getSubscriptionData(eventData)
|
_getSubscriptionData(eventData)
|
||||||
AnalyticsManager.recordEventForUser(userId, 'subscription-cancelled', {
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'subscription-cancelled',
|
||||||
|
{
|
||||||
plan_code: planCode,
|
plan_code: planCode,
|
||||||
quantity,
|
quantity,
|
||||||
is_trial: isTrial,
|
is_trial: isTrial,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
})
|
}
|
||||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
)
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'subscription-state',
|
||||||
|
state
|
||||||
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
isTrial
|
isTrial
|
||||||
|
@ -116,19 +140,27 @@ async function _sendSubscriptionCancelledEvent(userId, eventData) {
|
||||||
async function _sendSubscriptionExpiredEvent(userId, eventData) {
|
async function _sendSubscriptionExpiredEvent(userId, eventData) {
|
||||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||||
_getSubscriptionData(eventData)
|
_getSubscriptionData(eventData)
|
||||||
AnalyticsManager.recordEventForUser(userId, 'subscription-expired', {
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'subscription-expired',
|
||||||
|
{
|
||||||
plan_code: planCode,
|
plan_code: planCode,
|
||||||
quantity,
|
quantity,
|
||||||
is_trial: isTrial,
|
is_trial: isTrial,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
})
|
}
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-plan-code',
|
'subscription-plan-code',
|
||||||
planCode
|
planCode
|
||||||
)
|
)
|
||||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
userId,
|
||||||
|
'subscription-state',
|
||||||
|
state
|
||||||
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
isTrial
|
isTrial
|
||||||
|
@ -138,19 +170,27 @@ async function _sendSubscriptionExpiredEvent(userId, eventData) {
|
||||||
async function _sendSubscriptionRenewedEvent(userId, eventData) {
|
async function _sendSubscriptionRenewedEvent(userId, eventData) {
|
||||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||||
_getSubscriptionData(eventData)
|
_getSubscriptionData(eventData)
|
||||||
AnalyticsManager.recordEventForUser(userId, 'subscription-renewed', {
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'subscription-renewed',
|
||||||
|
{
|
||||||
plan_code: planCode,
|
plan_code: planCode,
|
||||||
quantity,
|
quantity,
|
||||||
is_trial: isTrial,
|
is_trial: isTrial,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
})
|
}
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-plan-code',
|
'subscription-plan-code',
|
||||||
planCode
|
planCode
|
||||||
)
|
)
|
||||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
userId,
|
||||||
|
'subscription-state',
|
||||||
|
state
|
||||||
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
isTrial
|
isTrial
|
||||||
|
@ -160,18 +200,26 @@ async function _sendSubscriptionRenewedEvent(userId, eventData) {
|
||||||
async function _sendSubscriptionReactivatedEvent(userId, eventData) {
|
async function _sendSubscriptionReactivatedEvent(userId, eventData) {
|
||||||
const { planCode, quantity, state, isTrial, subscriptionId } =
|
const { planCode, quantity, state, isTrial, subscriptionId } =
|
||||||
_getSubscriptionData(eventData)
|
_getSubscriptionData(eventData)
|
||||||
AnalyticsManager.recordEventForUser(userId, 'subscription-reactivated', {
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'subscription-reactivated',
|
||||||
|
{
|
||||||
plan_code: planCode,
|
plan_code: planCode,
|
||||||
quantity,
|
quantity,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
})
|
}
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-plan-code',
|
'subscription-plan-code',
|
||||||
planCode
|
planCode
|
||||||
)
|
)
|
||||||
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
userId,
|
||||||
|
'subscription-state',
|
||||||
|
state
|
||||||
|
)
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
isTrial
|
isTrial
|
||||||
|
@ -195,7 +243,7 @@ async function _sendInvoicePaidEvent(userId, eventData) {
|
||||||
subscriptionIds[`subscriptionId${idx + 1}`] = e
|
subscriptionIds[`subscriptionId${idx + 1}`] = e
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
AnalyticsManager.recordEventForUser(
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-invoice-collected',
|
'subscription-invoice-collected',
|
||||||
{
|
{
|
||||||
|
@ -208,7 +256,7 @@ async function _sendInvoicePaidEvent(userId, eventData) {
|
||||||
...subscriptionIds,
|
...subscriptionIds,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
false
|
false
|
||||||
|
|
|
@ -355,7 +355,7 @@ async function _sendUserGroupPlanCodeUserProperty(userId) {
|
||||||
bestFeatures = plan.features
|
bestFeatures = plan.features
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'group-subscription-plan-code',
|
'group-subscription-plan-code',
|
||||||
bestPlanCode
|
bestPlanCode
|
||||||
|
@ -376,7 +376,7 @@ async function _sendSubscriptionEvent(userId, subscriptionId, event) {
|
||||||
if (!subscription || !subscription.groupPlan) {
|
if (!subscription || !subscription.groupPlan) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
AnalyticsManager.recordEventForUser(userId, event, {
|
AnalyticsManager.recordEventForUserInBackground(userId, event, {
|
||||||
groupId: subscription._id.toString(),
|
groupId: subscription._id.toString(),
|
||||||
subscriptionId: subscription.recurlySubscription_id,
|
subscriptionId: subscription.recurlySubscription_id,
|
||||||
})
|
})
|
||||||
|
@ -397,7 +397,7 @@ async function _sendSubscriptionEventForAllMembers(subscriptionId, event) {
|
||||||
const userIds = (subscription.member_ids || []).filter(Boolean)
|
const userIds = (subscription.member_ids || []).filter(Boolean)
|
||||||
for (const userId of userIds) {
|
for (const userId of userIds) {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
AnalyticsManager.recordEventForUser(userId, event, {
|
AnalyticsManager.recordEventForUserInBackground(userId, event, {
|
||||||
groupId: subscription._id.toString(),
|
groupId: subscription._id.toString(),
|
||||||
subscriptionId: subscription.recurlySubscription_id,
|
subscriptionId: subscription.recurlySubscription_id,
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,6 +3,7 @@ const {
|
||||||
addRequiredCleanupHandlerBeforeDrainingConnections,
|
addRequiredCleanupHandlerBeforeDrainingConnections,
|
||||||
} = require('../../infrastructure/GracefulShutdown')
|
} = require('../../infrastructure/GracefulShutdown')
|
||||||
const { callbackifyAll } = require('@overleaf/promise-utils')
|
const { callbackifyAll } = require('@overleaf/promise-utils')
|
||||||
|
const logger = require('@overleaf/logger')
|
||||||
|
|
||||||
const SystemMessageManager = {
|
const SystemMessageManager = {
|
||||||
getMessages() {
|
getMessages() {
|
||||||
|
@ -22,9 +23,14 @@ const SystemMessageManager = {
|
||||||
await message.save()
|
await message.save()
|
||||||
},
|
},
|
||||||
|
|
||||||
async refreshCache() {
|
refreshCache() {
|
||||||
const messages = await this.getMessagesFromDB()
|
this.getMessagesFromDB()
|
||||||
|
.then(messages => {
|
||||||
this._cachedMessages = 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) {
|
async addReadOnlyUserToProject(userId, projectId) {
|
||||||
userId = new ObjectId(userId.toString())
|
userId = new ObjectId(userId.toString())
|
||||||
projectId = new ObjectId(projectId.toString())
|
projectId = new ObjectId(projectId.toString())
|
||||||
Analytics.recordEventForUser(userId, 'project-joined', {
|
Analytics.recordEventForUserInBackground(userId, 'project-joined', {
|
||||||
mode: 'read-only',
|
mode: 'read-only',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ const TokenAccessHandler = {
|
||||||
async addReadAndWriteUserToProject(userId, projectId) {
|
async addReadAndWriteUserToProject(userId, projectId) {
|
||||||
userId = new ObjectId(userId.toString())
|
userId = new ObjectId(userId.toString())
|
||||||
projectId = new ObjectId(projectId.toString())
|
projectId = new ObjectId(projectId.toString())
|
||||||
Analytics.recordEventForUser(userId, 'project-joined', {
|
Analytics.recordEventForUserInBackground(userId, 'project-joined', {
|
||||||
mode: 'read-write',
|
mode: 'read-write',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -165,21 +165,21 @@ function _getUserQuery(providerId, externalUserId) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _sendSecurityAlert(accountLinked, providerId, user, userId) {
|
function _sendSecurityAlert(accountLinked, providerId, user, userId) {
|
||||||
const providerName = oauthProviders[providerId].name
|
const providerName = oauthProviders[providerId].name
|
||||||
const emailOptions = EmailOptionsHelper.linkOrUnlink(
|
const emailOptions = EmailOptionsHelper.linkOrUnlink(
|
||||||
accountLinked,
|
accountLinked,
|
||||||
providerName,
|
providerName,
|
||||||
user.email
|
user.email
|
||||||
)
|
)
|
||||||
try {
|
EmailHandler.promises
|
||||||
await EmailHandler.promises.sendEmail('securityAlert', emailOptions)
|
.sendEmail('securityAlert', emailOptions)
|
||||||
} catch (error) {
|
.catch(error => {
|
||||||
logger.error(
|
logger.error(
|
||||||
{ err: error, userId },
|
{ err: error, userId },
|
||||||
`could not send security alert email when ${emailOptions.action.toLowerCase()}`
|
`could not send security alert email when ${emailOptions.action.toLowerCase()}`
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function _thirdPartyIdentifierUpdate(
|
function _thirdPartyIdentifierUpdate(
|
||||||
|
|
|
@ -43,7 +43,11 @@ async function recordRegistrationEvent(user) {
|
||||||
if (user.thirdPartyIdentifiers && user.thirdPartyIdentifiers.length > 0) {
|
if (user.thirdPartyIdentifiers && user.thirdPartyIdentifiers.length > 0) {
|
||||||
segmentation.provider = user.thirdPartyIdentifiers[0].providerId
|
segmentation.provider = user.thirdPartyIdentifiers[0].providerId
|
||||||
}
|
}
|
||||||
Analytics.recordEventForUser(user._id, 'user-registered', segmentation)
|
Analytics.recordEventForUserInBackground(
|
||||||
|
user._id,
|
||||||
|
'user-registered',
|
||||||
|
segmentation
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn({ err }, 'there was an error recording `user-registered` event')
|
logger.warn({ err }, 'there was an error recording `user-registered` event')
|
||||||
}
|
}
|
||||||
|
|
|
@ -319,11 +319,15 @@ async function checkSecondaryEmailConfirmationCode(req, res) {
|
||||||
|
|
||||||
delete req.session.pendingSecondaryEmail
|
delete req.session.pendingSecondaryEmail
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUser(user._id, 'email-verified', {
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
user._id,
|
||||||
|
'email-verified',
|
||||||
|
{
|
||||||
provider: 'email',
|
provider: 'email',
|
||||||
verification_type: 'token',
|
verification_type: 'token',
|
||||||
isPrimary: false,
|
isPrimary: false,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const redirectUrl =
|
const redirectUrl =
|
||||||
AuthenticationController.getRedirectFromSession(req) || '/project'
|
AuthenticationController.getRedirectFromSession(req) || '/project'
|
||||||
|
@ -427,7 +431,7 @@ async function primaryEmailCheckPage(req, res) {
|
||||||
return res.redirect('/project')
|
return res.redirect('/project')
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUser(
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
userId,
|
userId,
|
||||||
'primary-email-check-page-displayed'
|
'primary-email-check-page-displayed'
|
||||||
)
|
)
|
||||||
|
@ -439,7 +443,10 @@ async function primaryEmailCheck(req, res) {
|
||||||
await UserUpdater.promises.updateUser(userId, {
|
await UserUpdater.promises.updateUser(userId, {
|
||||||
$set: { lastPrimaryEmailCheck: new Date() },
|
$set: { lastPrimaryEmailCheck: new Date() },
|
||||||
})
|
})
|
||||||
AnalyticsManager.recordEventForUser(userId, 'primary-email-check-done')
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'primary-email-check-done'
|
||||||
|
)
|
||||||
AsyncFormHelper.redirect(req, res, '/project')
|
AsyncFormHelper.redirect(req, res, '/project')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -610,7 +617,7 @@ const UserEmailsController = {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const isPrimary = user?.email === userData.email
|
const isPrimary = user?.email === userData.email
|
||||||
AnalyticsManager.recordEventForUser(
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
userData.userId,
|
userData.userId,
|
||||||
'email-verified',
|
'email-verified',
|
||||||
{
|
{
|
||||||
|
|
|
@ -113,14 +113,14 @@ const UserRegistrationHandler = {
|
||||||
|
|
||||||
const setNewPasswordUrl = `${settings.siteUrl}/user/activate?token=${token}&user_id=${user._id}`
|
const setNewPasswordUrl = `${settings.siteUrl}/user/activate?token=${token}&user_id=${user._id}`
|
||||||
|
|
||||||
try {
|
EmailHandler.promises
|
||||||
await EmailHandler.promises.sendEmail('registered', {
|
.sendEmail('registered', {
|
||||||
to: user.email,
|
to: user.email,
|
||||||
setNewPasswordUrl,
|
setNewPasswordUrl,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
.catch(error => {
|
||||||
logger.warn({ err: error }, 'failed to send activation email')
|
logger.warn({ err: error }, 'failed to send activation email')
|
||||||
}
|
})
|
||||||
|
|
||||||
return { user, setNewPasswordUrl }
|
return { user, setNewPasswordUrl }
|
||||||
},
|
},
|
||||||
|
|
|
@ -75,7 +75,10 @@ async function addEmailAddress(userId, newEmail, affiliationOptions, auditLog) {
|
||||||
|
|
||||||
await UserGetter.promises.ensureUniqueEmailAddress(newEmail)
|
await UserGetter.promises.ensureUniqueEmailAddress(newEmail)
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUser(userId, 'secondary-email-added')
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'secondary-email-added'
|
||||||
|
)
|
||||||
|
|
||||||
await UserAuditLogHandler.promises.addEntry(
|
await UserAuditLogHandler.promises.addEntry(
|
||||||
userId,
|
userId,
|
||||||
|
@ -201,7 +204,10 @@ async function setDefaultEmailAddress(
|
||||||
throw new Error('email update error')
|
throw new Error('email update error')
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUser(userId, 'primary-email-address-updated')
|
AnalyticsManager.recordEventForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'primary-email-address-updated'
|
||||||
|
)
|
||||||
|
|
||||||
if (sendSecurityAlert) {
|
if (sendSecurityAlert) {
|
||||||
// no need to wait, errors are logged and not passed back
|
// no need to wait, errors are logged and not passed back
|
||||||
|
|
|
@ -76,6 +76,9 @@ i18n
|
||||||
supportedLngs: availableLanguageCodes,
|
supportedLngs: availableLanguageCodes,
|
||||||
fallbackLng: fallbackLanguageCode,
|
fallbackLng: fallbackLanguageCode,
|
||||||
})
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.error({ err }, 'failed to initialize i18next library')
|
||||||
|
})
|
||||||
|
|
||||||
// Make custom language detector for Accept-Language header
|
// Make custom language detector for Accept-Language header
|
||||||
const headerLangDetector = new middleware.LanguageDetector(i18n.services, {
|
const headerLangDetector = new middleware.LanguageDetector(i18n.services, {
|
||||||
|
|
|
@ -169,6 +169,8 @@ module.exports = LaunchpadController = {
|
||||||
return next(err)
|
return next(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore spurious floating promises warning until we promisify
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
User.updateOne(
|
User.updateOne(
|
||||||
{ _id: user._id },
|
{ _id: user._id },
|
||||||
{
|
{
|
||||||
|
@ -245,6 +247,8 @@ module.exports = LaunchpadController = {
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug({ userId: user._id }, 'making user an admin')
|
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(
|
User.updateOne(
|
||||||
{ _id: user._id },
|
{ _id: user._id },
|
||||||
{
|
{
|
||||||
|
|
|
@ -245,8 +245,8 @@
|
||||||
"@types/recurly__recurly-js": "^4.22.0",
|
"@types/recurly__recurly-js": "^4.22.0",
|
||||||
"@types/sinon-chai": "^3.2.8",
|
"@types/sinon-chai": "^3.2.8",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||||
"@typescript-eslint/parser": "^6.7.4",
|
"@typescript-eslint/parser": "^7.8.0",
|
||||||
"@uppy/core": "^3.8.0",
|
"@uppy/core": "^3.8.0",
|
||||||
"@uppy/dashboard": "^3.7.1",
|
"@uppy/dashboard": "^3.7.1",
|
||||||
"@uppy/drag-drop": "^3.0.3",
|
"@uppy/drag-drop": "^3.0.3",
|
||||||
|
|
|
@ -81,7 +81,7 @@ const checkAndUpdateUser = (user, callback) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(freshFeatures)
|
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(freshFeatures)
|
||||||
AnalyticsManager.setUserPropertyForUser(
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
user._id,
|
user._id,
|
||||||
'feature-set',
|
'feature-set',
|
||||||
matchedFeatureSet
|
matchedFeatureSet
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
|
||||||
requires: {
|
requires: {
|
||||||
'./AnalyticsManager': (this.AnalyticsManager = {
|
'./AnalyticsManager': (this.AnalyticsManager = {
|
||||||
recordEventForSession: sinon.stub().resolves(),
|
recordEventForSession: sinon.stub().resolves(),
|
||||||
setUserPropertyForSession: sinon.stub().resolves(),
|
setUserPropertyForSessionInBackground: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
'@overleaf/settings': {
|
'@overleaf/settings': {
|
||||||
siteUrl: 'https://www.overleaf.com',
|
siteUrl: 'https://www.overleaf.com',
|
||||||
|
@ -56,7 +56,9 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
|
||||||
|
|
||||||
it('no event or user property is recorded', function () {
|
it('no event or user property is recorded', function () {
|
||||||
sinon.assert.notCalled(this.AnalyticsManager.recordEventForSession)
|
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 () {
|
it('utm-tags user property is set for session', function () {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForSession,
|
this.AnalyticsManager.setUserPropertyForSessionInBackground,
|
||||||
this.req.session,
|
this.req.session,
|
||||||
'utm-tags',
|
'utm-tags',
|
||||||
'Organic;Facebook;Some Campaign;foo-bar'
|
'Organic;Facebook;Some Campaign;foo-bar'
|
||||||
|
@ -146,7 +148,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
|
||||||
|
|
||||||
it('utm-tags user property is set for session', function () {
|
it('utm-tags user property is set for session', function () {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForSession,
|
this.AnalyticsManager.setUserPropertyForSessionInBackground,
|
||||||
this.req.session,
|
this.req.session,
|
||||||
'utm-tags',
|
'utm-tags',
|
||||||
'N/A;Facebook;Some Campaign;foo'
|
'N/A;Facebook;Some Campaign;foo'
|
||||||
|
@ -190,7 +192,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
|
||||||
|
|
||||||
it('utm-tags user property is set for session', function () {
|
it('utm-tags user property is set for session', function () {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForSession,
|
this.AnalyticsManager.setUserPropertyForSessionInBackground,
|
||||||
this.req.session,
|
this.req.session,
|
||||||
'utm-tags',
|
'utm-tags',
|
||||||
'N/A;Facebook;Some Campaign;N/A'
|
'N/A;Facebook;Some Campaign;N/A'
|
||||||
|
|
|
@ -88,7 +88,7 @@ describe('AuthenticationController', function () {
|
||||||
setupLoginData: sinon.stub(),
|
setupLoginData: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
||||||
recordEventForUser: sinon.stub(),
|
recordEventForUserInBackground: sinon.stub(),
|
||||||
identifyUser: sinon.stub(),
|
identifyUser: sinon.stub(),
|
||||||
getIdsFromSession: sinon.stub().returns({ userId: this.user._id }),
|
getIdsFromSession: sinon.stub().returns({ userId: this.user._id }),
|
||||||
}),
|
}),
|
||||||
|
@ -1476,7 +1476,7 @@ describe('AuthenticationController', function () {
|
||||||
|
|
||||||
it('should track the login event', function () {
|
it('should track the login event', function () {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.user._id,
|
this.user._id,
|
||||||
'user-logged-in'
|
'user-logged-in'
|
||||||
)
|
)
|
||||||
|
|
|
@ -28,7 +28,7 @@ describe('BetaProgramHandler', function () {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
||||||
setUserPropertyForUser: sinon.stub().resolves(),
|
setUserPropertyForUserInBackground: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -54,7 +54,7 @@ describe('BetaProgramHandler', function () {
|
||||||
this.call(err => {
|
this.call(err => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.user_id,
|
this.user_id,
|
||||||
'beta-program',
|
'beta-program',
|
||||||
true
|
true
|
||||||
|
@ -105,7 +105,7 @@ describe('BetaProgramHandler', function () {
|
||||||
this.call(err => {
|
this.call(err => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.user_id,
|
this.user_id,
|
||||||
'beta-program',
|
'beta-program',
|
||||||
false
|
false
|
||||||
|
|
|
@ -33,7 +33,7 @@ describe('CollaboratorsInviteController', function () {
|
||||||
getSessionUser: sinon.stub().returns(this.currentUser),
|
getSessionUser: sinon.stub().returns(this.currentUser),
|
||||||
}
|
}
|
||||||
|
|
||||||
this.AnalyticsManger = { recordEventForUser: sinon.stub() }
|
this.AnalyticsManger = { recordEventForUserInBackground: sinon.stub() }
|
||||||
|
|
||||||
this.rateLimiter = {
|
this.rateLimiter = {
|
||||||
consume: sinon.stub().resolves(),
|
consume: sinon.stub().resolves(),
|
||||||
|
|
|
@ -74,7 +74,8 @@ describe('OwnershipTransferHandler', function () {
|
||||||
'../Email/EmailHandler': this.EmailHandler,
|
'../Email/EmailHandler': this.EmailHandler,
|
||||||
'./CollaboratorsHandler': this.CollaboratorsHandler,
|
'./CollaboratorsHandler': this.CollaboratorsHandler,
|
||||||
'../Analytics/AnalyticsManager': {
|
'../Analytics/AnalyticsManager': {
|
||||||
recordEventForUser: (this.recordEventForUser = sinon.stub()),
|
recordEventForUserInBackground: (this.recordEventForUserInBackground =
|
||||||
|
sinon.stub()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -214,7 +215,7 @@ describe('OwnershipTransferHandler', function () {
|
||||||
this.collaborator._id,
|
this.collaborator._id,
|
||||||
{ sessionUserId }
|
{ sessionUserId }
|
||||||
)
|
)
|
||||||
expect(this.recordEventForUser).to.have.been.calledWith(
|
expect(this.recordEventForUserInBackground).to.have.been.calledWith(
|
||||||
this.user._id,
|
this.user._id,
|
||||||
'project-ownership-transfer',
|
'project-ownership-transfer',
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe('EmailHandler', function () {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
this.Queues = {
|
this.Queues = {
|
||||||
createScheduledJob: sinon.stub(),
|
createScheduledJob: sinon.stub().resolves(),
|
||||||
}
|
}
|
||||||
this.EmailHandler = SandboxedModule.require(MODULE_PATH, {
|
this.EmailHandler = SandboxedModule.require(MODULE_PATH, {
|
||||||
requires: {
|
requires: {
|
||||||
|
|
|
@ -189,7 +189,9 @@ describe('ProjectController', function () {
|
||||||
this.BrandVariationsHandler,
|
this.BrandVariationsHandler,
|
||||||
'../ThirdPartyDataStore/TpdsProjectFlusher': this.TpdsProjectFlusher,
|
'../ThirdPartyDataStore/TpdsProjectFlusher': this.TpdsProjectFlusher,
|
||||||
'../../models/Project': {},
|
'../../models/Project': {},
|
||||||
'../Analytics/AnalyticsManager': { recordEventForUser: () => {} },
|
'../Analytics/AnalyticsManager': {
|
||||||
|
recordEventForUserInBackground: () => {},
|
||||||
|
},
|
||||||
'../Subscription/SubscriptionViewModelBuilder':
|
'../Subscription/SubscriptionViewModelBuilder':
|
||||||
this.SubscriptionViewModelBuilder,
|
this.SubscriptionViewModelBuilder,
|
||||||
'../Spelling/SpellingHandler': {
|
'../Spelling/SpellingHandler': {
|
||||||
|
|
|
@ -48,7 +48,7 @@ describe('SplitTestHandler', function () {
|
||||||
}
|
}
|
||||||
this.AnalyticsManager = {
|
this.AnalyticsManager = {
|
||||||
getIdsFromSession: sinon.stub(),
|
getIdsFromSession: sinon.stub(),
|
||||||
setUserPropertyForAnalyticsId: sinon.stub(),
|
setUserPropertyForAnalyticsId: sinon.stub().resolves(),
|
||||||
}
|
}
|
||||||
this.LocalsHelper = {
|
this.LocalsHelper = {
|
||||||
setSplitTestVariant: sinon.stub(),
|
setSplitTestVariant: sinon.stub(),
|
||||||
|
|
|
@ -93,7 +93,7 @@ describe('FeaturesUpdater', function () {
|
||||||
.resolves(this.user)
|
.resolves(this.user)
|
||||||
|
|
||||||
this.AnalyticsManager = {
|
this.AnalyticsManager = {
|
||||||
setUserPropertyForUser: sinon.stub(),
|
setUserPropertyForUserInBackground: sinon.stub(),
|
||||||
}
|
}
|
||||||
this.Modules = {
|
this.Modules = {
|
||||||
promises: { hooks: { fire: sinon.stub().resolves() } },
|
promises: { hooks: { fire: sinon.stub().resolves() } },
|
||||||
|
@ -141,7 +141,7 @@ describe('FeaturesUpdater', function () {
|
||||||
|
|
||||||
it('should send the corresponding feature set user property', function () {
|
it('should send the corresponding feature set user property', function () {
|
||||||
expect(
|
expect(
|
||||||
this.AnalyticsManager.setUserPropertyForUser
|
this.AnalyticsManager.setUserPropertyForUserInBackground
|
||||||
).to.have.been.calledWith(this.user._id, 'feature-set', 'all')
|
).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 () {
|
it('should send mixed feature set user property', function () {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.user._id,
|
this.user._id,
|
||||||
'feature-set',
|
'feature-set',
|
||||||
'mixed'
|
'mixed'
|
||||||
|
|
|
@ -31,8 +31,8 @@ describe('RecurlyEventHandler', function () {
|
||||||
sendTrialOnboardingEmail: sinon.stub(),
|
sendTrialOnboardingEmail: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
||||||
recordEventForUser: sinon.stub(),
|
recordEventForUserInBackground: sinon.stub(),
|
||||||
setUserPropertyForUser: sinon.stub(),
|
setUserPropertyForUserInBackground: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
'../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
|
'../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
|
||||||
promises: {
|
promises: {
|
||||||
|
@ -51,7 +51,7 @@ describe('RecurlyEventHandler', function () {
|
||||||
this.eventData
|
this.eventData
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-started',
|
'subscription-started',
|
||||||
{
|
{
|
||||||
|
@ -62,19 +62,19 @@ describe('RecurlyEventHandler', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-plan-code',
|
'subscription-plan-code',
|
||||||
this.planCode
|
this.planCode
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-state',
|
'subscription-state',
|
||||||
'active'
|
'active'
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
true
|
true
|
||||||
|
@ -104,7 +104,7 @@ describe('RecurlyEventHandler', function () {
|
||||||
this.eventData
|
this.eventData
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-started',
|
'subscription-started',
|
||||||
{
|
{
|
||||||
|
@ -115,13 +115,13 @@ describe('RecurlyEventHandler', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-state',
|
'subscription-state',
|
||||||
'active'
|
'active'
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
false
|
false
|
||||||
|
@ -136,7 +136,7 @@ describe('RecurlyEventHandler', function () {
|
||||||
this.eventData
|
this.eventData
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-updated',
|
'subscription-updated',
|
||||||
{
|
{
|
||||||
|
@ -147,19 +147,19 @@ describe('RecurlyEventHandler', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-plan-code',
|
'subscription-plan-code',
|
||||||
this.planCode
|
this.planCode
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-state',
|
'subscription-state',
|
||||||
'active'
|
'active'
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
true
|
true
|
||||||
|
@ -173,7 +173,7 @@ describe('RecurlyEventHandler', function () {
|
||||||
this.eventData
|
this.eventData
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-cancelled',
|
'subscription-cancelled',
|
||||||
{
|
{
|
||||||
|
@ -184,13 +184,13 @@ describe('RecurlyEventHandler', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-state',
|
'subscription-state',
|
||||||
'cancelled'
|
'cancelled'
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
true
|
true
|
||||||
|
@ -204,7 +204,7 @@ describe('RecurlyEventHandler', function () {
|
||||||
this.eventData
|
this.eventData
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-expired',
|
'subscription-expired',
|
||||||
{
|
{
|
||||||
|
@ -215,19 +215,19 @@ describe('RecurlyEventHandler', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-plan-code',
|
'subscription-plan-code',
|
||||||
this.planCode
|
this.planCode
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-state',
|
'subscription-state',
|
||||||
'expired'
|
'expired'
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
true
|
true
|
||||||
|
@ -240,7 +240,7 @@ describe('RecurlyEventHandler', function () {
|
||||||
this.eventData
|
this.eventData
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-renewed',
|
'subscription-renewed',
|
||||||
{
|
{
|
||||||
|
@ -258,7 +258,7 @@ describe('RecurlyEventHandler', function () {
|
||||||
this.eventData
|
this.eventData
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-reactivated',
|
'subscription-reactivated',
|
||||||
{
|
{
|
||||||
|
@ -292,7 +292,7 @@ describe('RecurlyEventHandler', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-invoice-collected',
|
'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 () {
|
it('with closed_invoice_notification', function () {
|
||||||
|
@ -338,7 +338,7 @@ describe('RecurlyEventHandler', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'subscription-invoice-collected'
|
'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 () {
|
it('nothing is called with invalid account code', function () {
|
||||||
|
@ -367,9 +367,15 @@ describe('RecurlyEventHandler', function () {
|
||||||
'new_subscription_notification',
|
'new_subscription_notification',
|
||||||
this.eventData
|
this.eventData
|
||||||
)
|
)
|
||||||
sinon.assert.notCalled(this.AnalyticsManager.recordEventForUser)
|
sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground)
|
||||||
sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForUser)
|
sinon.assert.notCalled(
|
||||||
sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForUser)
|
this.AnalyticsManager.setUserPropertyForUserInBackground
|
||||||
sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForUser)
|
)
|
||||||
|
sinon.assert.notCalled(
|
||||||
|
this.AnalyticsManager.setUserPropertyForUserInBackground
|
||||||
|
)
|
||||||
|
sinon.assert.notCalled(
|
||||||
|
this.AnalyticsManager.setUserPropertyForUserInBackground
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -146,8 +146,8 @@ describe('SubscriptionUpdater', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.AnalyticsManager = {
|
this.AnalyticsManager = {
|
||||||
recordEventForUser: sinon.stub().resolves(),
|
recordEventForUserInBackground: sinon.stub().resolves(),
|
||||||
setUserPropertyForUser: sinon.stub(),
|
setUserPropertyForUserInBackground: sinon.stub(),
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Features = {
|
this.Features = {
|
||||||
|
@ -448,7 +448,7 @@ describe('SubscriptionUpdater', function () {
|
||||||
.calledWith(searchOps, insertOperation)
|
.calledWith(searchOps, insertOperation)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.otherUserId,
|
this.otherUserId,
|
||||||
'group-subscription-joined',
|
'group-subscription-joined',
|
||||||
{
|
{
|
||||||
|
@ -477,7 +477,7 @@ describe('SubscriptionUpdater', function () {
|
||||||
this.otherUserId
|
this.otherUserId
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.otherUserId,
|
this.otherUserId,
|
||||||
'group-subscription-plan-code',
|
'group-subscription-plan-code',
|
||||||
'group_subscription'
|
'group_subscription'
|
||||||
|
@ -493,7 +493,7 @@ describe('SubscriptionUpdater', function () {
|
||||||
this.otherUserId
|
this.otherUserId
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.otherUserId,
|
this.otherUserId,
|
||||||
'group-subscription-plan-code',
|
'group-subscription-plan-code',
|
||||||
'better_group_subscription'
|
'better_group_subscription'
|
||||||
|
@ -509,7 +509,7 @@ describe('SubscriptionUpdater', function () {
|
||||||
this.otherUserId
|
this.otherUserId
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.otherUserId,
|
this.otherUserId,
|
||||||
'group-subscription-plan-code',
|
'group-subscription-plan-code',
|
||||||
'better_group_subscription'
|
'better_group_subscription'
|
||||||
|
@ -567,7 +567,7 @@ describe('SubscriptionUpdater', function () {
|
||||||
this.otherUserId
|
this.otherUserId
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.otherUserId,
|
this.otherUserId,
|
||||||
'group-subscription-left',
|
'group-subscription-left',
|
||||||
{
|
{
|
||||||
|
@ -583,7 +583,7 @@ describe('SubscriptionUpdater', function () {
|
||||||
this.otherUserId
|
this.otherUserId
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.otherUserId,
|
this.otherUserId,
|
||||||
'group-subscription-plan-code',
|
'group-subscription-plan-code',
|
||||||
null
|
null
|
||||||
|
@ -635,7 +635,7 @@ describe('SubscriptionUpdater', function () {
|
||||||
this.otherUserId
|
this.otherUserId
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.setUserPropertyForUser,
|
this.AnalyticsManager.setUserPropertyForUserInBackground,
|
||||||
this.otherUserId,
|
this.otherUserId,
|
||||||
'group-subscription-plan-code',
|
'group-subscription-plan-code',
|
||||||
null
|
null
|
||||||
|
@ -682,7 +682,7 @@ describe('SubscriptionUpdater', function () {
|
||||||
this.otherUserId
|
this.otherUserId
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.otherUserId,
|
this.otherUserId,
|
||||||
'group-subscription-left',
|
'group-subscription-left',
|
||||||
{
|
{
|
||||||
|
@ -691,7 +691,7 @@ describe('SubscriptionUpdater', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.AnalyticsManager.recordEventForUser,
|
this.AnalyticsManager.recordEventForUserInBackground,
|
||||||
this.otherUserId,
|
this.otherUserId,
|
||||||
'group-subscription-left',
|
'group-subscription-left',
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,7 +32,7 @@ describe('TokenAccessHandler', function () {
|
||||||
}),
|
}),
|
||||||
crypto: (this.Crypto = require('crypto')),
|
crypto: (this.Crypto = require('crypto')),
|
||||||
'../Analytics/AnalyticsManager': (this.Analytics = {
|
'../Analytics/AnalyticsManager': (this.Analytics = {
|
||||||
recordEventForUser: sinon.stub(),
|
recordEventForUserInBackground: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -127,7 +127,7 @@ describe('TokenAccessHandler', function () {
|
||||||
'tokenAccessReadOnly_refs'
|
'tokenAccessReadOnly_refs'
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.Analytics.recordEventForUser,
|
this.Analytics.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'project-joined',
|
'project-joined',
|
||||||
{ mode: 'read-only' }
|
{ mode: 'read-only' }
|
||||||
|
@ -175,7 +175,7 @@ describe('TokenAccessHandler', function () {
|
||||||
'tokenAccessReadAndWrite_refs'
|
'tokenAccessReadAndWrite_refs'
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.Analytics.recordEventForUser,
|
this.Analytics.recordEventForUserInBackground,
|
||||||
this.userId,
|
this.userId,
|
||||||
'project-joined',
|
'project-joined',
|
||||||
{ mode: 'read-write' }
|
{ mode: 'read-write' }
|
||||||
|
|
|
@ -144,7 +144,7 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
|
|
||||||
describe('EmailHandler', function () {
|
describe('EmailHandler', function () {
|
||||||
beforeEach(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 () {
|
it('should log but not return the error', async function () {
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -219,7 +219,7 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
|
|
||||||
describe('EmailHandler', function () {
|
describe('EmailHandler', function () {
|
||||||
beforeEach(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 () {
|
it('should log but not return the error', async function () {
|
||||||
await expect(
|
await expect(
|
||||||
|
|
|
@ -43,7 +43,7 @@ describe('UserCreator', function () {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
'../Analytics/AnalyticsManager': (this.Analytics = {
|
'../Analytics/AnalyticsManager': (this.Analytics = {
|
||||||
recordEventForUser: sinon.stub(),
|
recordEventForUserInBackground: sinon.stub(),
|
||||||
setUserPropertyForUser: sinon.stub(),
|
setUserPropertyForUser: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
'../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
|
'../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
|
||||||
|
@ -278,7 +278,7 @@ describe('UserCreator', function () {
|
||||||
})
|
})
|
||||||
assert.equal(user.email, this.email)
|
assert.equal(user.email, this.email)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
this.Analytics.recordEventForUser,
|
this.Analytics.recordEventForUserInBackground,
|
||||||
user._id,
|
user._id,
|
||||||
'user-registered'
|
'user-registered'
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,7 +57,7 @@ describe('UserEmailsController', function () {
|
||||||
}
|
}
|
||||||
this.HttpErrorHandler = { conflict: sinon.stub() }
|
this.HttpErrorHandler = { conflict: sinon.stub() }
|
||||||
this.AnalyticsManager = {
|
this.AnalyticsManager = {
|
||||||
recordEventForUser: sinon.stub(),
|
recordEventForUserInBackground: sinon.stub(),
|
||||||
}
|
}
|
||||||
this.UserAuditLogHandler = {
|
this.UserAuditLogHandler = {
|
||||||
addEntry: sinon.stub().yields(),
|
addEntry: sinon.stub().yields(),
|
||||||
|
|
|
@ -40,7 +40,7 @@ describe('UserRegistrationHandler', function () {
|
||||||
subscribe: sinon.stub(),
|
subscribe: sinon.stub(),
|
||||||
}
|
}
|
||||||
this.EmailHandler = {
|
this.EmailHandler = {
|
||||||
promises: { sendEmail: sinon.stub() },
|
promises: { sendEmail: sinon.stub().resolves() },
|
||||||
}
|
}
|
||||||
this.OneTimeTokenHandler = { promises: { getNewToken: sinon.stub() } }
|
this.OneTimeTokenHandler = { promises: { getNewToken: sinon.stub() } }
|
||||||
this.handler = SandboxedModule.require(modulePath, {
|
this.handler = SandboxedModule.require(modulePath, {
|
||||||
|
|
|
@ -65,7 +65,7 @@ describe('UserUpdater', function () {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
this.AnalyticsManager = {
|
this.AnalyticsManager = {
|
||||||
recordEventForUser: sinon.stub(),
|
recordEventForUserInBackground: sinon.stub(),
|
||||||
}
|
}
|
||||||
this.InstitutionsAPI = {
|
this.InstitutionsAPI = {
|
||||||
promises: {
|
promises: {
|
||||||
|
|
Loading…
Reference in a new issue