Compare commits

...

21 Commits

Author SHA1 Message Date
Phillip Thelen
40d5172972 fix anonymizing properties 2025-11-24 15:55:07 +01:00
Phillip Thelen
ebb58e4470 send anonymized data if opted out 2025-09-19 11:52:57 +02:00
Phillip Thelen
c3ef26b2f3 remove invitee field from analytics 2025-09-18 11:22:41 +02:00
Phillip Thelen
108bd59296 call correct method to update user data 2025-09-18 11:15:33 +02:00
Kalista Payne
5228ed40d1 fix(lint): remove undef function call 2025-09-17 15:58:44 -05:00
Kalista Payne
becb6e49f0 fix(merge): put back settings mixins 2025-09-17 15:51:43 -05:00
Kalista Payne
aa8f0f0c4e chore(subproj): merge habitica-images 2025-09-17 15:47:04 -05:00
Kalista Payne
d1891f4c43 Merge branch 'develop' into phillip/server-analytics 2025-09-17 15:45:45 -05:00
Phillip Thelen
2381be8c46 remove old import 2025-09-10 17:50:33 +02:00
Phillip Thelen
5c7545f32a cleanup 2025-09-10 17:38:52 +02:00
Phillip Thelen
ffed5a9a97 update package locks 2025-09-10 12:58:39 +02:00
Phillip Thelen
cd58ce2233 remove google analytics 2025-09-10 12:50:41 +02:00
Phillip Thelen
a2b5e3621e refactor amplitude event properties 2025-09-10 12:44:17 +02:00
Phillip Thelen
a06dfc9ed8 allow mobile to send analytics calls 2025-09-05 16:12:23 +02:00
Phillip Thelen
58b0e323a3 anonymize all uuids 2025-09-05 16:10:41 +02:00
Phillip Thelen
9ca60d7551 anonymize user data if they didn’t consent to analytics 2025-09-05 12:55:56 +02:00
Phillip Thelen
6f63583a12 use ip-lookup-api to determine users country 2025-09-05 12:51:54 +02:00
Phillip Thelen
d952239d35 fix imports 2025-09-04 14:37:42 +02:00
Phillip Thelen
2c7f6fd9e3 properly update user properties in events 2025-09-04 12:57:35 +02:00
Phillip Thelen
ddba450630 add new api call to allow client to update amplitude events 2025-09-04 12:57:18 +02:00
Phillip Thelen
187238d39a remove amplitude from client 2025-09-04 12:56:57 +02:00
58 changed files with 8249 additions and 5648 deletions

13394
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@
"@babel/core": "^7.22.10", "@babel/core": "^7.22.10",
"@babel/preset-env": "^7.22.10", "@babel/preset-env": "^7.22.10",
"@babel/register": "^7.22.15", "@babel/register": "^7.22.15",
"@google-analytics/data": "^4.12.1",
"@google-cloud/trace-agent": "^7.1.2", "@google-cloud/trace-agent": "^7.1.2",
"@parse/node-apn": "^5.2.3", "@parse/node-apn": "^5.2.3",
"@slack/webhook": "^6.1.0", "@slack/webhook": "^6.1.0",
@@ -43,6 +42,7 @@
"habitica-markdown": "^3.0.0", "habitica-markdown": "^3.0.0",
"helmet": "^4.6.0", "helmet": "^4.6.0",
"in-app-purchase": "^1.11.3", "in-app-purchase": "^1.11.3",
"ip-location-api": "^4.0.0",
"js2xmlparser": "^5.0.0", "js2xmlparser": "^5.0.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jwks-rsa": "^2.1.5", "jwks-rsa": "^2.1.5",

View File

@@ -12,7 +12,6 @@
"@froxz/vite-plugin-s3": "^1.6.0", "@froxz/vite-plugin-s3": "^1.6.0",
"@vitejs/plugin-vue2": "^2.3.3", "@vitejs/plugin-vue2": "^2.3.3",
"@vue/test-utils": "1.0.0-beta.29", "@vue/test-utils": "1.0.0-beta.29",
"amplitude-js": "^8.21.3",
"assert": "^2.1.0", "assert": "^2.1.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"axios": "^0.28.0", "axios": "^0.28.0",
@@ -68,49 +67,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/@amplitude/analytics-connector": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@amplitude/analytics-connector/-/analytics-connector-1.5.0.tgz",
"integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g=="
},
"node_modules/@amplitude/types": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/@amplitude/types/-/types-1.10.2.tgz",
"integrity": "sha512-I8qenRI7uU6wKNb9LiZrAosSHVoNHziXouKY81CrqxH9xhVTEIJFXeuCV0hbtBr0Al/8ejnGjQRx+S2SvU/pPg==",
"engines": {
"node": ">=10"
}
},
"node_modules/@amplitude/ua-parser-js": {
"version": "0.7.33",
"resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
"integrity": "sha512-wKEtVR4vXuPT9cVEIJkYWnlF++Gx3BdLatPBM+SZ1ztVIvnhdGBZR/mn9x/PzyrMcRlZmyi6L56I2J3doVBnjA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"engines": {
"node": "*"
}
},
"node_modules/@amplitude/utils": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/@amplitude/utils/-/utils-1.10.2.tgz",
"integrity": "sha512-tVsHXu61jITEtRjB7NugQ5cVDd4QDzne8T3ifmZye7TiJeUfVRvqe44gDtf55A+7VqhDhyEIIXTA1iVcDGqlEw==",
"dependencies": {
"@amplitude/types": "^1.10.2",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
@@ -1231,6 +1187,7 @@
"version": "7.23.6", "version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz",
"integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
"dev": true,
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
}, },
@@ -3050,19 +3007,6 @@
"ajv": "^6.9.1" "ajv": "^6.9.1"
} }
}, },
"node_modules/amplitude-js": {
"version": "8.21.9",
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-8.21.9.tgz",
"integrity": "sha512-d0jJH00wbXu7sxKtVwkdSXtVffjqdUrxuACKlnzP7jU5qt9wriXXMgHifdH5Oq+buKmyF8wKL9S02gAykysURA==",
"dependencies": {
"@amplitude/analytics-connector": "^1.4.6",
"@amplitude/ua-parser-js": "0.7.33",
"@amplitude/utils": "^1.10.2",
"@babel/runtime": "^7.21.0",
"blueimp-md5": "^2.19.0",
"query-string": "8.1.0"
}
},
"node_modules/ansi-colors": { "node_modules/ansi-colors": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -3351,11 +3295,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/blueimp-md5": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz",
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
},
"node_modules/bootstrap": { "node_modules/bootstrap": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
@@ -3768,14 +3707,6 @@
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
"dev": true "dev": true
}, },
"node_modules/decode-uri-component": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz",
"integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==",
"engines": {
"node": ">=14.16"
}
},
"node_modules/deep-eql": { "node_modules/deep-eql": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz",
@@ -4987,17 +4918,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/filter-obj": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz",
"integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/find-up": { "node_modules/find-up": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -7283,22 +7203,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/query-string": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-8.1.0.tgz",
"integrity": "sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==",
"dependencies": {
"decode-uri-component": "^0.4.1",
"filter-obj": "^5.1.0",
"split-on-first": "^3.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ramda": { "node_modules/ramda": {
"version": "0.26.1", "version": "0.26.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
@@ -7344,7 +7248,8 @@
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.14.1", "version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true
}, },
"node_modules/regexp.prototype.flags": { "node_modules/regexp.prototype.flags": {
"version": "1.5.1", "version": "1.5.1",
@@ -7858,17 +7763,6 @@
"source-map": "^0.6.0" "source-map": "^0.6.0"
} }
}, },
"node_modules/split-on-first": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz",
"integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/sprintf-js": { "node_modules/sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",

View File

@@ -16,7 +16,6 @@
"@froxz/vite-plugin-s3": "^1.6.0", "@froxz/vite-plugin-s3": "^1.6.0",
"@vitejs/plugin-vue2": "^2.3.3", "@vitejs/plugin-vue2": "^2.3.3",
"@vue/test-utils": "1.0.0-beta.29", "@vue/test-utils": "1.0.0-beta.29",
"amplitude-js": "^8.21.3",
"assert": "^2.1.0", "assert": "^2.1.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"axios": "^0.28.0", "axios": "^0.28.0",

View File

@@ -445,7 +445,7 @@ export default {
hitType: 'event', hitType: 'event',
mirror: newVal, mirror: newVal,
group: this.group._id, group: this.group._id,
}, { trackOnClient: true }); });
const groupsToMirror = this.user.preferences.tasks.mirrorGroupTasks || []; const groupsToMirror = this.user.preferences.tasks.mirrorGroupTasks || [];
if (newVal) { // we're turning copy ON for this group if (newVal) { // we're turning copy ON for this group
groupsToMirror.push(this.group._id); groupsToMirror.push(this.group._id);

View File

@@ -1,8 +1,8 @@
<template> <template>
<div <div
class="banner d-flex align-items-center justify-content-between py-3 px-4"
id="privacy-banner"
v-if="!hidden" v-if="!hidden"
id="privacy-banner"
class="banner d-flex align-items-center justify-content-between py-3 px-4"
> >
<p <p
class="mr-3 mb-0" class="mr-3 mb-0"

View File

@@ -546,7 +546,7 @@ export default {
eventCategory: 'behavior', eventCategory: 'behavior',
demographics: this.upgradedGroup.demographics, demographics: this.upgradedGroup.demographics,
type: this.paymentData.group.type, type: this.paymentData.group.type,
}, { trackOnClient: true }); });
} }
this.paymentData = {}; this.paymentData = {};
this.$root.$emit('bv::hide::modal', 'payments-success-modal'); this.$root.$emit('bv::hide::modal', 'payments-success-modal');

View File

@@ -64,9 +64,11 @@
<li>sexual orientation; and</li> <li>sexual orientation; and</li>
<li>information collected from a known child.</li> <li>information collected from a known child.</li>
</ul> </ul>
<p><strong> <p>
NOTE: Please do not provide us sensitive personal information or sensitive personal data, as those terms are defined under applicable privacy laws, unless we directly request that you do so. If you feel, after careful consideration, that it is necessary to provide us certain sensitive personal information or data, please provide us the minimum amount of such information or data that is necessary. <strong>
</strong></p> NOTE: Please do not provide us sensitive personal information or sensitive personal data, as those terms are defined under applicable privacy laws, unless we directly request that you do so. If you feel, after careful consideration, that it is necessary to provide us certain sensitive personal information or data, please provide us the minimum amount of such information or data that is necessary.
</strong>
</p>
<h3 id="section_1_1"> <h3 id="section_1_1">
1.1 Information You Provide Directly 1.1 Information You Provide Directly
</h3> </h3>
@@ -617,7 +619,7 @@
7. General Audience Services 7. General Audience Services
</h2> </h2>
<p> <p>
The Service is intended for users 18 years or older; you are not permitted to access or use the Service if you are younger than 18. We do not knowingly collect personal information from children under the age of 18 through the Service. We encourage parents and legal guardians to monitor their childrens Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 18 has provided personal information to us, please contact us at <a href='mailto:privacy@habitica.com'>privacy@habitica.com</a>, and we will delete that information from our databases. The Service is intended for users 18 years or older; you are not permitted to access or use the Service if you are younger than 18. We do not knowingly collect personal information from children under the age of 18 through the Service. We encourage parents and legal guardians to monitor their childrens Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 18 has provided personal information to us, please contact us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>, and we will delete that information from our databases.
</p> </p>
<h2 id="section_8"> <h2 id="section_8">
@@ -708,7 +710,7 @@
<p><strong><u>Nevada Residents</u></strong></p> <p><strong><u>Nevada Residents</u></strong></p>
<p> <p>
Nevada residents may opt out of the sale of certain “covered information” collected by operators of websites or online services. We currently do not sell covered information, as “sale” is defined by such law, and do not have plans to do so. In accordance with Nevada law, you may submit to us a verified request instructing us not to sell your covered information by sending an email to <a href='mailto:privacy@habitica.com'>privacy@habitica.com</a>. Nevada residents may opt out of the sale of certain “covered information” collected by operators of websites or online services. We currently do not sell covered information, as “sale” is defined by such law, and do not have plans to do so. In accordance with Nevada law, you may submit to us a verified request instructing us not to sell your covered information by sending an email to <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>.
</p> </p>
<p><strong><u>Notice to United Kingdom/European/Switzerland Residents.</u></strong></p> <p><strong><u>Notice to United Kingdom/European/Switzerland Residents.</u></strong></p>
<p> <p>

View File

@@ -15,8 +15,8 @@
<router-view /> <router-view />
</div> </div>
<div <div
id="bottom-background"
v-if="loginFlow" v-if="loginFlow"
id="bottom-background"
class="bg-purple-300" class="bg-purple-300"
> >
<div class="seamless_mountains_demo_repeat"></div> <div class="seamless_mountains_demo_repeat"></div>
@@ -31,7 +31,10 @@
id="bottom-wrap" id="bottom-wrap"
class="purple-4" class="purple-4"
> >
<div id="bottom-background" v-if="!loginFlow"> <div
v-if="!loginFlow"
id="bottom-background"
>
<div class="seamless_mountains_demo_repeat"></div> <div class="seamless_mountains_demo_repeat"></div>
<div class="midground_foreground_extended2"></div> <div class="midground_foreground_extended2"></div>
</div> </div>

View File

@@ -158,7 +158,7 @@
BY PURCHASING PREMIUM YOU EXPRESSLY UNDERSTAND AND AGREE TO OUR REFUND POLICY: BY PURCHASING PREMIUM YOU EXPRESSLY UNDERSTAND AND AGREE TO OUR REFUND POLICY:
</p> </p>
<p> <p>
YOU CAN REQUEST A REFUND OF YOUR MOST RECENT PAYMENT TO US BY CONTACTING US AT <a href='mailto:admin@habitica.com'>ADMIN@HABITICA.COM</a>. THE AMOUNT OF YOUR REFUND, IF ANY, WILL BE BASED ON (1) THE AMOUNT OF YOUR PURCHASED BUT UNUSED SUBSCRIPTION BENEFITS AND (2) THE TERMS IMPOSED ON US BY OUR PAYMENT PROCESSING VENDORS (E.G., WITH RESPECT TO THE DURATION OF THE REFUND PERIOD). YOU CAN REQUEST A REFUND OF YOUR MOST RECENT PAYMENT TO US BY CONTACTING US AT <a href="mailto:admin@habitica.com">ADMIN@HABITICA.COM</a>. THE AMOUNT OF YOUR REFUND, IF ANY, WILL BE BASED ON (1) THE AMOUNT OF YOUR PURCHASED BUT UNUSED SUBSCRIPTION BENEFITS AND (2) THE TERMS IMPOSED ON US BY OUR PAYMENT PROCESSING VENDORS (E.G., WITH RESPECT TO THE DURATION OF THE REFUND PERIOD).
</p> </p>
<p> <p>
FOR ANY CUSTOMER WHO PURCHASED PREMIUM IN APPLE INC.'s APP STORE ("APP STORE"), PLEASE CONTACT APPLE INC.'s SUPPORT TEAM: <a FOR ANY CUSTOMER WHO PURCHASED PREMIUM IN APPLE INC.'s APP STORE ("APP STORE"), PLEASE CONTACT APPLE INC.'s SUPPORT TEAM: <a

View File

@@ -25,8 +25,8 @@
type="checkbox" type="checkbox"
:checked="isChecked" :checked="isChecked"
:value="value" :value="value"
@change="handleChange"
:disabled="disabled" :disabled="disabled"
@change="handleChange"
> >
<label <label
class="toggle-switch-label" class="toggle-switch-label"

View File

@@ -1,21 +1,11 @@
import forEach from 'lodash/forEach';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import keys from 'lodash/keys'; import keys from 'lodash/keys';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import amplitude from 'amplitude-js';
import { gtag, install } from 'ga-gtag';
import Vue from 'vue'; import Vue from 'vue';
import getStore from '@/store'; import getStore from '@/store';
const AMPLITUDE_KEY = import.meta.env.AMPLITUDE_KEY;
const DEBUG_ENABLED = import.meta.env.DEBUG_ENABLED === 'true';
const GA_ID = import.meta.env.GA_ID;
const IS_PRODUCTION = import.meta.env.NODE_ENV === 'production';
const REQUIRED_FIELDS = ['eventCategory', 'eventAction']; const REQUIRED_FIELDS = ['eventCategory', 'eventAction'];
let analyticsLoading = false;
let analyticsReady = false;
function _getConsentedUser () { function _getConsentedUser () {
const store = getStore(); const store = getStore();
const user = store.state.user.data; const user = store.state.user.data;
@@ -66,49 +56,24 @@ function _gatherUserStats (properties) {
if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId;
} }
export function safeSetup (userId) { export function track (properties) {
if (analyticsLoading || analyticsReady) return;
analyticsLoading = true;
install(GA_ID, {
debug_mode: DEBUG_ENABLED || !IS_PRODUCTION,
user_id: userId,
});
amplitude.getInstance().init(AMPLITUDE_KEY, userId);
analyticsReady = true;
analyticsLoading = false;
}
export function track (properties, options = {}) {
const user = _getConsentedUser(); const user = _getConsentedUser();
if (!user) return; if (!user) return;
safeSetup(user._id);
// Use nextTick to avoid blocking the UI // Use nextTick to avoid blocking the UI
Vue.nextTick(() => { Vue.nextTick(() => {
if (_doesNotHaveRequiredFields(properties)) return; if (_doesNotHaveRequiredFields(properties)) return;
const store = getStore();
const trackOnClient = options && options.trackOnClient === true; store.dispatch('analytics:trackEvent', properties);
// Track events on the server by default
if (trackOnClient === true) {
amplitude.getInstance().logEvent(properties.eventAction, properties);
gtag('event', properties.eventAction, properties);
} else {
const store = getStore();
store.dispatch('analytics:trackEvent', properties);
}
}); });
} }
export function updateUser (properties = {}) { export function updateUser (properties = {}) {
const user = _getConsentedUser(); const user = _getConsentedUser();
if (!user) return; if (!user) return;
safeSetup(user._id);
// Use nextTick to avoid blocking the UI // Use nextTick to avoid blocking the UI
Vue.nextTick(() => { Vue.nextTick(() => {
_gatherUserStats(properties); _gatherUserStats(properties);
gtag('set', 'user_properties', properties); const store = getStore();
forEach(properties, (value, key) => { store.dispatch('analytics:updateUserProperties', properties);
const identify = new amplitude.Identify().set(key, value);
amplitude.getInstance().identify(identify);
});
}); });
} }

View File

@@ -215,7 +215,7 @@ export default {
eventCategory: 'behavior', eventCategory: 'behavior',
demographics: appState.newGroup.demographics, demographics: appState.newGroup.demographics,
type: appState.newGroup.type, type: appState.newGroup.type,
}, { trackOnClient: true }); });
} }
} catch (err) { } catch (err) {
console.error('Error while redirecting to Stripe', err); // eslint-disable-line console.error('Error while redirecting to Stripe', err); // eslint-disable-line

View File

@@ -66,7 +66,7 @@ export default {
uuid: user._id, uuid: user._id,
taskType: task.type, taskType: task.type,
direction, direction,
}, { trackOnClient: true }); });
if (!tasksScoredCount) { if (!tasksScoredCount) {
setLocalSetting(CONSTANTS.keyConstants.TASKS_SCORED_COUNT, 1); setLocalSetting(CONSTANTS.keyConstants.TASKS_SCORED_COUNT, 1);
} else { } else {

View File

@@ -1,7 +1,8 @@
<template> <template>
<tr> <tr>
<td colspan="3" <td
v-if="!mixinData.inlineSettingMixin.modalVisible" v-if="!mixinData.inlineSettingMixin.modalVisible"
colspan="3"
> >
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<h3 <h3
@@ -18,8 +19,9 @@
</a> </a>
</div> </div>
</td> </td>
<td colspan="3" <td
v-if="mixinData.inlineSettingMixin.modalVisible" v-if="mixinData.inlineSettingMixin.modalVisible"
colspan="3"
> >
<h3 <h3
v-once v-once
@@ -59,8 +61,8 @@
{{ $t('performanceAnalytics') }} {{ $t('performanceAnalytics') }}
</label> </label>
<toggle-switch <toggle-switch
class="mb-auto"
v-model="user.preferences.analyticsConsent" v-model="user.preferences.analyticsConsent"
class="mb-auto"
@change="prefToggled()" @change="prefToggled()"
/> />
</div> </div>

View File

@@ -128,7 +128,6 @@ import PrivacyBanner from '@/components/header/banners/privacy';
import AppFooter from '@/components/appFooter'; import AppFooter from '@/components/appFooter';
import notificationsDisplay from '@/components/notifications'; import notificationsDisplay from '@/components/notifications';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import * as Analytics from '@/libs/analytics';
import BuyModal from '@/components/shops/buyModal.vue'; import BuyModal from '@/components/shops/buyModal.vue';
import SelectMembersModal from '@/components/selectMembersModal.vue'; import SelectMembersModal from '@/components/selectMembersModal.vue';
import notifications from '@/mixins/notifications'; import notifications from '@/mixins/notifications';
@@ -280,7 +279,6 @@ export default {
return null; return null;
} }
} }
Analytics.updateUser();
return axios.get( return axios.get(
'/api/v4/i18n/browser-script', '/api/v4/i18n/browser-script',
{ {

View File

@@ -320,7 +320,7 @@ router.beforeEach(async (to, from, next) => {
eventName: 'View Find Members', eventName: 'View Find Members',
eventAction: 'View Find Members', eventAction: 'View Find Members',
eventCategory: 'behavior', eventCategory: 'behavior',
}, { trackOnClient: true }); });
} }
// Redirect old guild urls // Redirect old guild urls

View File

@@ -2,6 +2,10 @@ import axios from 'axios';
export async function trackEvent (store, params) { export async function trackEvent (store, params) {
const url = `/analytics/track/${params.eventAction}`; const url = `/analytics/track/${params.eventAction}`;
await axios.post(url, params);
}
export async function updateUserProperties (store, params) {
const url = '/analytics/update';
await axios.post(url, params); await axios.post(url, params);
} }

View File

@@ -1,8 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { authAsCredentialsState, LOCALSTORAGE_AUTH_KEY } from '@/libs/auth'; import { authAsCredentialsState, LOCALSTORAGE_AUTH_KEY } from '@/libs/auth';
const GA_ID = import.meta.env.GA_ID;
function saveLocalDataAuth (store, apiId, apiToken) { function saveLocalDataAuth (store, apiId, apiToken) {
const credentialsObj = { const credentialsObj = {
auth: { auth: {
@@ -123,9 +121,6 @@ export async function appleAuth (store, params) {
export function logout (store, options = {}) { export function logout (store, options = {}) {
localStorage.clear(); localStorage.clear();
sessionStorage.clear(); sessionStorage.clear();
if (window.gtag) {
window.gtag('config', GA_ID, { user_id: null });
}
const query = options.redirectToLogin === true ? '?redirectToLogin=true' : ''; const query = options.redirectToLogin === true ? '?redirectToLogin=true' : '';
window.location.href = `/logout-server${query}`; window.location.href = `/logout-server${query}`;
} }

View File

@@ -120,7 +120,7 @@ export async function create (store, createdTask) {
hitType: 'event', hitType: 'event',
uuid, uuid,
taskType: taskRes.type, taskType: taskRes.type,
}, { trackOnClient: true }); });
if (!tasksCreatedCount) { if (!tasksCreatedCount) {
setLocalSetting(CONSTANTS.keyConstants.TASKS_CREATED_COUNT, 1); setLocalSetting(CONSTANTS.keyConstants.TASKS_CREATED_COUNT, 1);
} else { } else {

View File

@@ -26,11 +26,9 @@ const envVars = [
'EMAILS_COMMUNITY_MANAGER_EMAIL', 'EMAILS_COMMUNITY_MANAGER_EMAIL',
'EMAILS_TECH_ASSISTANCE_EMAIL', 'EMAILS_TECH_ASSISTANCE_EMAIL',
'EMAILS_PRESS_ENQUIRY_EMAIL', 'EMAILS_PRESS_ENQUIRY_EMAIL',
'GA_ID',
'STRIPE_PUB_KEY', 'STRIPE_PUB_KEY',
'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_ID',
'APPLE_AUTH_CLIENT_ID', 'APPLE_AUTH_CLIENT_ID',
'AMPLITUDE_KEY',
'LOGGLY_CLIENT_TOKEN', 'LOGGLY_CLIENT_TOKEN',
'TRUSTED_DOMAINS', 'TRUSTED_DOMAINS',
'TIME_TRAVEL_ENABLED', 'TIME_TRAVEL_ENABLED',

View File

@@ -3,7 +3,6 @@ import isFunction from 'lodash/isFunction';
import min from 'lodash/min'; import min from 'lodash/min';
import reduce from 'lodash/reduce'; import reduce from 'lodash/reduce';
import filter from 'lodash/filter'; import filter from 'lodash/filter';
import pick from 'lodash/pick';
import pickBy from 'lodash/pickBy'; import pickBy from 'lodash/pickBy';
import size from 'lodash/size'; import size from 'lodash/size';
import moment from 'moment'; import moment from 'moment';
@@ -160,7 +159,7 @@ export default function randomDrop (user, options, req = {}, analytics) {
if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) { if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) {
analytics.track('dropped item', { analytics.track('dropped item', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
itemKey: drop.key, itemKey: drop.key,
category: 'behavior', category: 'behavior',

View File

@@ -1,5 +1,3 @@
import pick from 'lodash/pick';
export function hasCompletedOnboarding (user) { export function hasCompletedOnboarding (user) {
return ( return (
user.achievements.createdTask === true user.achievements.createdTask === true
@@ -21,7 +19,7 @@ export function checkOnboardingStatus (user, req, analytics) {
user.addNotification('ONBOARDING_COMPLETE'); user.addNotification('ONBOARDING_COMPLETE');
if (analytics) { if (analytics) {
analytics.track('onboarding complete', { analytics.track('onboarding complete', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',

View File

@@ -1,7 +1,6 @@
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import get from 'lodash/get'; import get from 'lodash/get';
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import pick from 'lodash/pick';
import i18n from '../../i18n'; import i18n from '../../i18n';
import { import {
NotAuthorized, NotAuthorized,
@@ -114,7 +113,7 @@ export class AbstractBuyOperation {
sendToAnalytics (additionalData = {}) { sendToAnalytics (additionalData = {}) {
// spread-operator produces an "unexpected token" error // spread-operator produces an "unexpected token" error
const analyticsData = merge(additionalData, { const analyticsData = merge(additionalData, {
user: pick(this.user, ['preferences', 'registeredThrough']), user: this.user,
uuid: this.user._id, uuid: this.user._id,
category: 'behavior', category: 'behavior',
headers: this.req.headers, headers: this.req.headers,

View File

@@ -73,7 +73,7 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation { // eslint-d
this.analytics.track( this.analytics.track(
'Enchanted Armoire', 'Enchanted Armoire',
{ {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
itemKey: key, itemKey: key,
category: 'behavior', category: 'behavior',

View File

@@ -1,6 +1,5 @@
import get from 'lodash/get'; import get from 'lodash/get';
import each from 'lodash/each'; import each from 'lodash/each';
import pick from 'lodash/pick';
import i18n from '../../i18n'; import i18n from '../../i18n';
import content from '../../content/index'; import content from '../../content/index';
import { import {
@@ -37,7 +36,7 @@ export default async function buyMysterySet (user, req = {}, analytics) {
if (analytics) { if (analytics) {
analytics.track('buy', { analytics.track('buy', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
itemKey: mysterySet.key, itemKey: mysterySet.key,
itemType: 'Subscriber Gear', itemType: 'Subscriber Gear',

View File

@@ -1,7 +1,6 @@
import get from 'lodash/get'; import get from 'lodash/get';
import includes from 'lodash/includes'; import includes from 'lodash/includes';
import keys from 'lodash/keys'; import keys from 'lodash/keys';
import pick from 'lodash/pick';
import i18n from '../../i18n'; import i18n from '../../i18n';
import content from '../../content/index'; import content from '../../content/index';
import { import {
@@ -96,7 +95,7 @@ export default async function purchaseHourglass (user, req = {}, analytics, quan
if (analytics) { if (analytics) {
analytics.track('buy', { analytics.track('buy', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
itemKey: key, itemKey: key,
itemType: type, itemType: type,

View File

@@ -132,7 +132,7 @@ export default async function purchase (user, req = {}, analytics) {
/* eslint-enable no-await-in-loop */ /* eslint-enable no-await-in-loop */
if (analytics) { if (analytics) {
analytics.track('buy', { analytics.track('buy', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
itemKey: key, itemKey: key,
itemType: type, itemType: type,

View File

@@ -70,7 +70,7 @@ export default async function changeClass (user, req = {}, analytics) {
if (analytics) { if (analytics) {
analytics.track('change class', { analytics.track('change class', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
class: klass, class: klass,
currency: balanceRemoved === 0 ? 'Free' : 'Gems', currency: balanceRemoved === 0 ? 'Free' : 'Gems',

View File

@@ -2,7 +2,6 @@ import forEach from 'lodash/forEach';
import findIndex from 'lodash/findIndex'; import findIndex from 'lodash/findIndex';
import get from 'lodash/get'; import get from 'lodash/get';
import keys from 'lodash/keys'; import keys from 'lodash/keys';
import pick from 'lodash/pick';
import upperFirst from 'lodash/upperFirst'; import upperFirst from 'lodash/upperFirst';
import moment from 'moment'; import moment from 'moment';
import i18n from '../i18n'; import i18n from '../i18n';
@@ -143,7 +142,7 @@ export default function feed (user, req = {}, analytics) {
if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) { if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) {
analytics.track('pet feed', { analytics.track('pet feed', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
foodKey: food.key, foodKey: food.key,
petKey: pet.key, petKey: pet.key,

View File

@@ -2,7 +2,6 @@ import findIndex from 'lodash/findIndex';
import forEach from 'lodash/forEach'; import forEach from 'lodash/forEach';
import get from 'lodash/get'; import get from 'lodash/get';
import keys from 'lodash/keys'; import keys from 'lodash/keys';
import pick from 'lodash/pick';
import upperFirst from 'lodash/upperFirst'; import upperFirst from 'lodash/upperFirst';
import moment from 'moment'; import moment from 'moment';
import i18n from '../i18n'; import i18n from '../i18n';
@@ -154,7 +153,7 @@ export default function hatch (user, req = {}, analytics) {
if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) { if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) {
analytics.track('pet hatch', { analytics.track('pet hatch', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
petKey: pet, petKey: pet,
category: 'behavior', category: 'behavior',

View File

@@ -1,5 +1,4 @@
import each from 'lodash/each'; import each from 'lodash/each';
import pick from 'lodash/pick';
import i18n from '../i18n'; import i18n from '../i18n';
import { capByLevel } from '../statHelpers'; import { capByLevel } from '../statHelpers';
import { MAX_LEVEL } from '../constants'; import { MAX_LEVEL } from '../constants';
@@ -23,7 +22,7 @@ export default async function rebirth (user, tasks = [], req = {}, analytics) {
const analyticsData = { const analyticsData = {
uuid: user._id, uuid: user._id,
user: pick(user, ['preferences', 'registeredThrough']), user,
category: 'behavior', category: 'behavior',
}; };

View File

@@ -1,4 +1,3 @@
import pick from 'lodash/pick';
import content from '../content/index'; import content from '../content/index';
import { mountMasterProgress } from '../count'; import { mountMasterProgress } from '../count';
import i18n from '../i18n'; import i18n from '../i18n';
@@ -44,7 +43,7 @@ export default async function releaseMounts (user, req = {}, analytics) {
if (analytics) { if (analytics) {
analytics.track('release mounts', { analytics.track('release mounts', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
currency: 'Gems', currency: 'Gems',
gemCost: 4, gemCost: 4,

View File

@@ -1,4 +1,3 @@
import pick from 'lodash/pick';
import content from '../content/index'; import content from '../content/index';
import { beastMasterProgress } from '../count'; import { beastMasterProgress } from '../count';
import i18n from '../i18n'; import i18n from '../i18n';
@@ -44,7 +43,7 @@ export default function releasePets (user, req = {}, analytics) {
if (analytics) { if (analytics) {
analytics.track('release pets', { analytics.track('release pets', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
currency: 'Gems', currency: 'Gems',
gemCost: 4, gemCost: 4,

View File

@@ -1,5 +1,4 @@
import each from 'lodash/each'; import each from 'lodash/each';
import pick from 'lodash/pick';
import i18n from '../i18n'; import i18n from '../i18n';
import { import {
NotAuthorized, NotAuthorized,
@@ -24,7 +23,7 @@ export default async function reroll (user, tasks = [], req = {}, analytics) {
if (analytics) { if (analytics) {
analytics.track('Fortify Potion', { analytics.track('Fortify Potion', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
currency: 'Gems', currency: 'Gems',
gemCost: 4, gemCost: 4,

View File

@@ -1,5 +1,4 @@
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import pick from 'lodash/pick';
import reduce from 'lodash/reduce'; import reduce from 'lodash/reduce';
import each from 'lodash/each'; import each from 'lodash/each';
import i18n from '../i18n'; import i18n from '../i18n';
@@ -112,7 +111,7 @@ export default function revive (user, req = {}, analytics) {
if (analytics) { if (analytics) {
analytics.track('Death', { analytics.track('Death', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
lostItem, lostItem,
category: 'behavior', category: 'behavior',

View File

@@ -1,11 +1,9 @@
import pick from 'lodash/pick';
export function sleep (user, req = {}, analytics) { export function sleep (user, req = {}, analytics) {
user.preferences.sleep = !user.preferences.sleep; user.preferences.sleep = !user.preferences.sleep;
if (analytics) { if (analytics) {
analytics.track('sleep', { analytics.track('sleep', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
status: user.preferences.sleep, status: user.preferences.sleep,
category: 'behavior', category: 'behavior',

View File

@@ -1,5 +1,4 @@
import get from 'lodash/get'; import get from 'lodash/get';
import pick from 'lodash/pick';
import setWith from 'lodash/setWith'; import setWith from 'lodash/setWith';
import i18n from '../i18n'; import i18n from '../i18n';
import { NotAuthorized, BadRequest } from '../libs/errors'; import { NotAuthorized, BadRequest } from '../libs/errors';
@@ -318,7 +317,7 @@ export default async function unlock (user, req = {}, analytics) {
if (analytics) { if (analytics) {
analytics.track('buy', { analytics.track('buy', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
itemKey: path, itemKey: path,
itemType: 'customization', itemType: 'customization',

View File

@@ -1,6 +1,5 @@
import validator from 'validator'; import validator from 'validator';
import moment from 'moment'; import moment from 'moment';
import pick from 'lodash/pick';
import sortBy from 'lodash/sortBy'; import sortBy from 'lodash/sortBy';
import nconf from 'nconf'; import nconf from 'nconf';
import { import {
@@ -128,7 +127,7 @@ api.loginLocal = {
await user.save(); await user.save();
res.analytics.track('login', { res.analytics.track('login', {
user: pick(user, ['preferences', 'registeredThrough']), user,
category: 'behavior', category: 'behavior',
type: 'local', type: 'local',
uuid: user._id, uuid: user._id,

View File

@@ -1,7 +1,6 @@
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import escapeRegExp from 'lodash/escapeRegExp'; import escapeRegExp from 'lodash/escapeRegExp';
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import pick from 'lodash/pick';
import reduce from 'lodash/reduce'; import reduce from 'lodash/reduce';
import times from 'lodash/times'; import times from 'lodash/times';
import { authWithHeaders, authWithSession } from '../../middlewares/auth'; import { authWithHeaders, authWithSession } from '../../middlewares/auth';
@@ -291,7 +290,7 @@ api.createChallenge = {
response.group = getChallengeGroupResponse(group); response.group = getChallengeGroupResponse(group);
res.analytics.track('challenge create', { res.analytics.track('challenge create', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -360,7 +359,7 @@ api.joinChallenge = {
response.leader = chalLeader ? chalLeader.toJSON({ minimize: true }) : null; response.leader = chalLeader ? chalLeader.toJSON({ minimize: true }) : null;
res.analytics.track('challenge join', { res.analytics.track('challenge join', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -411,7 +410,7 @@ api.leaveChallenge = {
await challenge.unlinkTasks(user, keep); await challenge.unlinkTasks(user, keep);
res.analytics.track('challenge leave', { res.analytics.track('challenge leave', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -896,7 +895,7 @@ api.deleteChallenge = {
await challenge.closeChal({ broken: 'CHALLENGE_DELETED' }); await challenge.closeChal({ broken: 'CHALLENGE_DELETED' });
res.analytics.track('challenge delete', { res.analytics.track('challenge delete', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -957,7 +956,7 @@ api.selectChallengeWinner = {
await challenge.closeChal({ broken: 'CHALLENGE_CLOSED', winner }); await challenge.closeChal({ broken: 'CHALLENGE_CLOSED', winner });
res.analytics.track('challenge close', { res.analytics.track('challenge close', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',

View File

@@ -1,4 +1,3 @@
import pick from 'lodash/pick';
import moment from 'moment'; import moment from 'moment';
import nconf from 'nconf'; import nconf from 'nconf';
import { authWithHeaders } from '../../middlewares/auth'; import { authWithHeaders } from '../../middlewares/auth';
@@ -187,7 +186,7 @@ api.postChat = {
// Check if account is newer than the minimum age for chat participation // Check if account is newer than the minimum age for chat participation
if (moment().diff(user.auth.timestamps.created, 'minutes') < ACCOUNT_MIN_CHAT_AGE) { if (moment().diff(user.auth.timestamps.created, 'minutes') < ACCOUNT_MIN_CHAT_AGE) {
analytics.track('chat age error', { analytics.track('chat age error', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -239,7 +238,7 @@ api.postChat = {
await Promise.all(toSave); await Promise.all(toSave);
const analyticsObject = { const analyticsObject = {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',

View File

@@ -5,7 +5,6 @@ import findIndex from 'lodash/findIndex';
import includes from 'lodash/includes'; import includes from 'lodash/includes';
import isArray from 'lodash/isArray'; import isArray from 'lodash/isArray';
import mergeWith from 'lodash/mergeWith'; import mergeWith from 'lodash/mergeWith';
import pick from 'lodash/pick';
import uniqBy from 'lodash/uniqBy'; import uniqBy from 'lodash/uniqBy';
import nconf from 'nconf'; import nconf from 'nconf';
import moment from 'moment'; import moment from 'moment';
@@ -169,7 +168,7 @@ api.createGroup = {
}; };
const analyticsObject = { const analyticsObject = {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -220,7 +219,7 @@ api.createGroupPlan = {
const savedGroup = results[1]; const savedGroup = results[1];
res.analytics.track('join group', { res.analytics.track('join group', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -705,7 +704,7 @@ api.joinGroup = {
promises.push(group.save()); promises.push(group.save());
const analyticsObject = { const analyticsObject = {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',

View File

@@ -1,5 +1,4 @@
import escapeRegExp from 'lodash/escapeRegExp'; import escapeRegExp from 'lodash/escapeRegExp';
import pick from 'lodash/pick';
import { authWithHeaders } from '../../middlewares/auth'; import { authWithHeaders } from '../../middlewares/auth';
import { import {
model as User, model as User,
@@ -737,7 +736,7 @@ api.transferGems = {
if (res.analytics) { if (res.analytics) {
res.analytics.track('transfer gems', { res.analytics.track('transfer gems', {
user: pick(sender, ['preferences', 'registeredThrough']), user: sender,
uuid: sender._id, uuid: sender._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',

View File

@@ -1,7 +1,6 @@
import each from 'lodash/each'; import each from 'lodash/each';
import every from 'lodash/every'; import every from 'lodash/every';
import isBoolean from 'lodash/isBoolean'; import isBoolean from 'lodash/isBoolean';
import pick from 'lodash/pick';
import { authWithHeaders } from '../../middlewares/auth'; import { authWithHeaders } from '../../middlewares/auth';
import { getAnalyticsServiceByEnvironment } from '../../libs/analyticsService'; import { getAnalyticsServiceByEnvironment } from '../../libs/analyticsService';
import { import {
@@ -168,7 +167,7 @@ api.inviteToQuest = {
// track that the inviting user has accepted the quest // track that the inviting user has accepted the quest
analytics.track('quest', { analytics.track('quest', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
category: 'behavior', category: 'behavior',
headers: req.headers, headers: req.headers,
@@ -233,7 +232,7 @@ api.acceptQuest = {
// track that a user has accepted the quest // track that a user has accepted the quest
analytics.track('quest', { analytics.track('quest', {
user: pick(user, ['preferences', 'registeredThrough']), user,
category: 'behavior', category: 'behavior',
owner: false, owner: false,
response: 'accept', response: 'accept',
@@ -298,7 +297,7 @@ api.rejectQuest = {
res.respond(200, savedGroup.quest); res.respond(200, savedGroup.quest);
analytics.track('quest', { analytics.track('quest', {
user: pick(user, ['preferences', 'registeredThrough']), user,
category: 'behavior', category: 'behavior',
owner: false, owner: false,
response: 'reject', response: 'reject',
@@ -362,7 +361,7 @@ api.forceStart = {
res.respond(200, savedGroup.quest); res.respond(200, savedGroup.quest);
analytics.track('quest', { analytics.track('quest', {
user: pick(user, ['preferences', 'registeredThrough']), user,
category: 'behavior', category: 'behavior',
owner: user._id === group.quest.leader, owner: user._id === group.quest.leader,
response: 'force-start', response: 'force-start',

View File

@@ -1,7 +1,6 @@
import assign from 'lodash/assign'; import assign from 'lodash/assign';
import find from 'lodash/find'; import find from 'lodash/find';
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import pick from 'lodash/pick';
import moment from 'moment'; import moment from 'moment';
import { authWithHeaders } from '../../middlewares/auth'; import { authWithHeaders } from '../../middlewares/auth';
import { import {
@@ -333,7 +332,7 @@ api.createChallengeTasks = {
tasks.forEach(task => { tasks.forEach(task => {
res.analytics.track('challenge task created', { res.analytics.track('challenge task created', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -703,7 +702,7 @@ api.updateTask = {
if (group) { if (group) {
res.analytics.track('task edit', { res.analytics.track('task edit', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',

View File

@@ -1,4 +1,3 @@
import pick from 'lodash/pick';
import isUUID from 'validator/lib/isUUID'; import isUUID from 'validator/lib/isUUID';
import { authWithHeaders } from '../../../middlewares/auth'; import { authWithHeaders } from '../../../middlewares/auth';
import * as Tasks from '../../../models/task'; import * as Tasks from '../../../models/task';
@@ -64,7 +63,7 @@ api.createGroupTasks = {
tasks.forEach(task => { tasks.forEach(task => {
res.analytics.track('team task created', { res.analytics.track('team task created', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -253,7 +252,7 @@ api.assignTask = {
res.respond(200, task); res.respond(200, task);
res.analytics.track('task assign', { res.analytics.track('task assign', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',

View File

@@ -1,7 +1,6 @@
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import forEach from 'lodash/forEach'; import forEach from 'lodash/forEach';
import isFunction from 'lodash/isFunction'; import isFunction from 'lodash/isFunction';
import pick from 'lodash/pick';
import nconf from 'nconf'; import nconf from 'nconf';
import get from 'lodash/get'; import get from 'lodash/get';
import { authWithHeaders } from '../../middlewares/auth'; import { authWithHeaders } from '../../middlewares/auth';
@@ -326,7 +325,7 @@ api.deleteUser = {
} }
res.analytics.track('account delete', { res.analytics.track('account delete', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',

View File

@@ -1,4 +1,3 @@
import pick from 'lodash/pick';
import { import {
NotAuthorized, NotAuthorized,
} from '../../libs/errors'; } from '../../libs/errors';
@@ -22,16 +21,17 @@ api.trackEvent = {
// we authenticate these requests to make sure they actually came from a real user // we authenticate these requests to make sure they actually came from a real user
middlewares: [authWithHeaders()], middlewares: [authWithHeaders()],
async handler (req, res) { async handler (req, res) {
// As of now only web can track events using this route if (req.headers['x-client'] !== 'habitica-web'
if (req.headers['x-client'] !== 'habitica-web') { && req.headers['x-client'] !== 'habitica-ios'
throw new NotAuthorized('Only habitica.com is allowed to track analytics events.'); && req.headers['x-client'] !== 'habitica-android') {
throw new NotAuthorized('Only official clients are allowed to track analytics events.');
} }
const { user } = res.locals; const { user } = res.locals;
const eventProperties = req.body; const eventProperties = req.body;
res.analytics.track(req.params.eventName, { res.analytics.track(req.params.eventName, {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
headers: req.headers, headers: req.headers,
category: 'behavior', category: 'behavior',
@@ -44,4 +44,31 @@ api.trackEvent = {
}, },
}; };
api.updateUserProperties = {
method: 'POST',
url: '/analytics/update',
// we authenticate these requests to make sure they actually came from a real user
middlewares: [authWithHeaders()],
async handler (req, res) {
if (req.headers['x-client'] !== 'habitica-web'
&& req.headers['x-client'] !== 'habitica-ios'
&& req.headers['x-client'] !== 'habitica-android') {
throw new NotAuthorized('Only official clients are allowed to track analytics events.');
}
const { user } = res.locals;
const properties = req.body;
res.analytics.updateUserData({
user,
uuid: user._id,
properties,
});
// not using res.respond
// because we don't want to send back notifications and other user-related data
res.status(200).send({});
},
};
export default api; export default api;

View File

@@ -2,6 +2,9 @@
import nconf from 'nconf'; import nconf from 'nconf';
import Amplitude from 'amplitude'; import Amplitude from 'amplitude';
import useragent from 'useragent'; import useragent from 'useragent';
import validator from 'validator';
import { lookup } from 'ip-location-api';
import { createHash } from 'crypto';
import { import {
omit, omit,
toArray, toArray,
@@ -27,6 +30,36 @@ if (AMPLITUDE_TOKEN) amplitude = new Amplitude(AMPLITUDE_TOKEN);
const Content = common.content; const Content = common.content;
function _hashUUID (uuid) {
return createHash('sha256').update(uuid).digest('hex');
}
function _anonymizeProperties (properties) {
if (Array.isArray(properties)) {
return properties.map(userProp => {
if (typeof userProp === 'string' && validator.isEmail(userProp)) {
return _hashUUID(userProp);
}
return userProp;
});
}
if (typeof properties === 'object' && properties !== null) {
const anonymizedProps = {};
Object.keys(properties).forEach(key => {
const value = properties[key];
if (typeof value === 'string' && validator.isEmail(value)) {
anonymizedProps[key] = _hashUUID(value);
} else if (typeof value === 'object' && value !== null) {
anonymizedProps[key] = _anonymizeProperties(value);
} else {
anonymizedProps[key] = value;
}
});
return anonymizedProps;
}
return properties;
}
function _lookUpItemName (itemKey) { function _lookUpItemName (itemKey) {
if (!itemKey) return null; if (!itemKey) return null;
@@ -56,7 +89,7 @@ function _lookUpItemName (itemKey) {
return itemName; return itemName;
} }
function _formatUserData (user) { function _formatUserData (user, ipaddress, anonymize = false) {
const properties = {}; const properties = {};
if (user.stats) { if (user.stats) {
@@ -72,7 +105,7 @@ function _formatUserData (user) {
properties.balanceGemAmount = properties.balance * 4; properties.balanceGemAmount = properties.balance * 4;
properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2; properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
properties.verifiedUsername = user.flags && user.flags.verifiedUsername; properties.verifiedUsername = user.flags && user.flags.verifiedUsername;
if (properties.verifiedUsername && user.auth && user.auth.local) { if (properties.verifiedUsername && user.auth && user.auth.local && !anonymize) {
properties.username = user.auth.local.lowerCaseUsername; properties.username = user.auth.local.lowerCaseUsername;
} }
@@ -89,10 +122,12 @@ function _formatUserData (user) {
properties.contributorLevel = user.contributor.level; properties.contributorLevel = user.contributor.level;
} }
if (user.purchased && user.purchased.plan.planId) { if (!anonymize) {
properties.subscription = user.purchased.plan.planId; if (user.purchased && user.purchased.plan.planId) {
} else { properties.subscription = user.purchased.plan.planId;
properties.subscription = null; } else {
properties.subscription = null;
}
} }
if (user._ABtests) { if (user._ABtests) {
@@ -103,6 +138,16 @@ function _formatUserData (user) {
properties.loginIncentives = user.loginIncentives; properties.loginIncentives = user.loginIncentives;
} }
if (ipaddress) {
const location = lookup(ipaddress);
properties.country = location.country;
properties.region = location.region1;
}
if (anonymize) {
return _anonymizeProperties(properties);
}
return properties; return properties;
} }
@@ -139,16 +184,20 @@ function _formatUserAgentForAmplitude (platform, agentString) {
return formattedAgent; return formattedAgent;
} }
function _formatUUIDForAmplitude (uuid) { function _formatUUIDForAmplitude (uuid, anonymize = false) {
if (anonymize) {
return _hashUUID(uuid);
}
return uuid || 'no-user-id-was-provided'; return uuid || 'no-user-id-was-provided';
} }
function _formatDataForAmplitude (data) { function _formatDataForAmplitude (data) {
const consented = data.user && data.user.preferences && data.user.preferences.analyticsConsent;
const event_properties = omit(data, AMPLITUDE_PROPERTIES_TO_SCRUB); const event_properties = omit(data, AMPLITUDE_PROPERTIES_TO_SCRUB);
const platform = _formatPlatformForAmplitude(data.headers && data.headers['x-client']); const platform = _formatPlatformForAmplitude(data.headers && data.headers['x-client']);
const agent = _formatUserAgentForAmplitude(platform, data.headers && data.headers['user-agent']); const agent = _formatUserAgentForAmplitude(platform, data.headers && data.headers['user-agent']);
const ampData = { const ampData = {
user_id: _formatUUIDForAmplitude(data.uuid), user_id: _formatUUIDForAmplitude(data.uuid, !consented),
platform, platform,
os_name: agent.name, os_name: agent.name,
os_version: agent.version, os_version: agent.version,
@@ -156,7 +205,12 @@ function _formatDataForAmplitude (data) {
}; };
if (data.user) { if (data.user) {
ampData.user_properties = _formatUserData(data.user); const ipaddress = data.ipaddress || (data.headers && data.headers['x-forwarded-for']);
ampData.user_properties = _formatUserData(data.user, ipaddress, !consented);
}
if (!consented) {
ampData.event_properties = _anonymizeProperties(ampData.event_properties);
} }
const itemName = _lookUpItemName(data.itemKey); const itemName = _lookUpItemName(data.itemKey);
@@ -215,12 +269,18 @@ function _setOnce (dataToSetOnce, uuid) {
.catch(err => logger.error(err, 'Error while sending data to Amplitude.')); .catch(err => logger.error(err, 'Error while sending data to Amplitude.'));
} }
// There's no error handling directly here because it's handled inside _sendDataTo{Amplitude|Google} function _updateProperties (properties, uuid) {
return amplitude
.identify({
user_id: _formatUUIDForAmplitude(uuid),
user_properties: properties,
})
.catch(err => logger.error(err, 'Error while sending data to Amplitude.'));
}
// There's no error handling directly here because it's handled inside _sendDataToAmplitude
async function track (eventType, data, loggerOnly = false) { async function track (eventType, data, loggerOnly = false) {
const { user } = data; const { user } = data;
if (!user || !user.preferences || !user.preferences.analyticsConsent) {
return null;
}
const promises = [ const promises = [
_sendDataToAmplitude(eventType, data, loggerOnly), _sendDataToAmplitude(eventType, data, loggerOnly),
]; ];
@@ -234,29 +294,37 @@ async function track (eventType, data, loggerOnly = false) {
} }
// There's no error handling directly here because // There's no error handling directly here because
// it's handled inside _sendPurchaseDataTo{Amplitude|Google} // it's handled inside _sendPurchaseDataToAmplitude
async function trackPurchase (data) { async function trackPurchase (data) {
const { user } = data;
if (!user || !user.preferences || !user.preferences.analyticsConsent) {
return null;
}
return Promise.all([ return Promise.all([
_sendPurchaseDataToAmplitude(data), _sendPurchaseDataToAmplitude(data),
]); ]);
} }
async function updateUserData (data) {
const { user, properties } = data;
const toUpdate = {
..._formatUserData(user, data.ipaddress),
...properties,
};
return _updateProperties(toUpdate, user._id);
}
// Stub for non-prod environments // Stub for non-prod environments
const mockAnalyticsService = { const mockAnalyticsService = {
track: () => { }, track: () => { },
trackPurchase: () => { }, trackPurchase: () => { },
updateUserData: () => { },
}; };
// Return the production or mock service based on the current environment // Return the production or mock service based on the current environment
function getServiceByEnvironment () { function getServiceByEnvironment () {
if (nconf.get('IS_PROD') || (nconf.get('DEBUG_ENABLED') && !nconf.get('BASE_URL').includes('localhost'))) { if (nconf.get('IS_PROD') || nconf.get('USE_PROD_ANALYTICS') || (nconf.get('DEBUG_ENABLED') && !nconf.get('BASE_URL').includes('localhost'))) {
return { return {
track, track,
trackPurchase, trackPurchase,
updateUserData,
}; };
} }
return mockAnalyticsService; return mockAnalyticsService;

View File

@@ -1,5 +1,4 @@
import moment from 'moment'; import moment from 'moment';
import pick from 'lodash/pick';
import { import {
BadRequest, BadRequest,
NotAuthorized, NotAuthorized,
@@ -219,7 +218,7 @@ async function registerLocal (req, res, { isV3 = false }) {
if (!existingUser) { if (!existingUser) {
res.analytics.track('register', { res.analytics.track('register', {
user: pick(savedUser, ['preferences', 'registeredThrough']), user: savedUser,
category: 'acquisition', category: 'acquisition',
type: 'local', type: 'local',
uuid: savedUser._id, uuid: savedUser._id,

View File

@@ -1,4 +1,3 @@
import pick from 'lodash/pick';
import passport from 'passport'; import passport from 'passport';
import common from '../../../common'; import common from '../../../common';
import { BadRequest, NotAuthorized, NotFound } from '../errors'; import { BadRequest, NotAuthorized, NotFound } from '../errors';
@@ -158,7 +157,7 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
if (!existingUser) { if (!existingUser) {
res.analytics.track('register', { res.analytics.track('register', {
user: pick(savedUser, ['preferences', 'registeredThrough']), user: savedUser,
uuid: savedUser._id, uuid: savedUser._id,
category: 'acquisition', category: 'acquisition',
type: network, type: network,

View File

@@ -1,6 +1,5 @@
import moment from 'moment'; import moment from 'moment';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import pick from 'lodash/pick';
import nconf from 'nconf'; import nconf from 'nconf';
import { model as User } from '../models/user'; import { model as User } from '../models/user';
import * as Tasks from '../models/task'; import * as Tasks from '../models/task';
@@ -104,7 +103,7 @@ function trackCronAnalytics (analytics, user, _progress, options) {
analytics.track('Cron', { analytics.track('Cron', {
category: 'behavior', category: 'behavior',
uuid: user._id, uuid: user._id,
user: pick(user, ['preferences', 'registeredThrough']), user,
resting: user.preferences.sleep, resting: user.preferences.sleep,
cronCount: user.flags.cronCount, cronCount: user.flags.cronCount,
progressUp: Math.min(_progress.up, 900), progressUp: Math.min(_progress.up, 900),

View File

@@ -1,6 +1,5 @@
import find from 'lodash/find'; import find from 'lodash/find';
import includes from 'lodash/includes'; import includes from 'lodash/includes';
import pick from 'lodash/pick';
import { encrypt } from '../encryption'; import { encrypt } from '../encryption';
import { sendNotification as sendPushNotification } from '../pushNotifications'; import { sendNotification as sendPushNotification } from '../pushNotifications';
@@ -144,11 +143,10 @@ async function inviteByUUID (uuid, group, inviter, req, res) {
} }
const analyticsObject = { const analyticsObject = {
user: pick(inviter, ['preferences', 'registeredThrough']), user: inviter,
uuid: inviter._id, uuid: inviter._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
invitee: uuid,
groupId: group._id, groupId: group._id,
groupType: group.type, groupType: group.type,
headers: req.headers, headers: req.headers,
@@ -209,11 +207,10 @@ async function inviteByEmail (invite, group, inviter, req, res) {
if (!userIsUnsubscribed) sendTxnEmail(invite, `invite-friend${groupLabel}`, variables); if (!userIsUnsubscribed) sendTxnEmail(invite, `invite-friend${groupLabel}`, variables);
const analyticsObject = { const analyticsObject = {
user: pick(inviter, ['preferences', 'registeredThrough']), user: inviter,
uuid: inviter._id, uuid: inviter._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
invitee: 'email',
groupId: group._id, groupId: group._id,
groupType: group.type, groupType: group.type,
headers: req.headers, headers: req.headers,
@@ -247,11 +244,10 @@ async function inviteByUserName (username, group, inviter, req, res) {
} }
const analyticsObject = { const analyticsObject = {
user: pick(inviter, ['preferences', 'registeredThrough']), user: inviter,
uuid: inviter._id, uuid: inviter._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
invitee: userToInvite._id,
groupId: group._id, groupId: group._id,
groupType: group.type, groupType: group.type,
headers: req.headers, headers: req.headers,

View File

@@ -1,5 +1,4 @@
import find from 'lodash/find'; import find from 'lodash/find';
import pick from 'lodash/pick';
import { getAnalyticsServiceByEnvironment } from '../analyticsService'; import { getAnalyticsServiceByEnvironment } from '../analyticsService';
import { getCurrentEventList } from '../worldState'; // eslint-disable-line import/no-cycle import { getCurrentEventList } from '../worldState'; // eslint-disable-line import/no-cycle
import { // eslint-disable-line import/no-cycle import { // eslint-disable-line import/no-cycle
@@ -115,7 +114,7 @@ export async function buyGems (data) {
if (!data.gift) txnEmail(data.user, 'donation'); if (!data.gift) txnEmail(data.user, 'donation');
analytics.trackPurchase({ analytics.trackPurchase({
user: pick(data.user, ['preferences', 'registeredThrough']), user: data.user,
uuid: data.user._id, uuid: data.user._id,
itemPurchased: 'Gems', itemPurchased: 'Gems',
sku: `${data.paymentMethod.toLowerCase()}-checkout`, sku: `${data.paymentMethod.toLowerCase()}-checkout`,

View File

@@ -1,4 +1,3 @@
import pick from 'lodash/pick';
import moment from 'moment'; import moment from 'moment';
import { import {
BadRequest, BadRequest,
@@ -32,7 +31,7 @@ async function buyGryphatrice (data) {
data.user.purchased.txnCount += 1; data.user.purchased.txnCount += 1;
analytics.trackPurchase({ analytics.trackPurchase({
user: pick(data.user, ['preferences', 'registeredThrough']), user: data.user,
uuid: data.user._id, uuid: data.user._id,
itemPurchased: 'Gryphatrice', itemPurchased: 'Gryphatrice',
sku: `${data.paymentMethod.toLowerCase()}-checkout`, sku: `${data.paymentMethod.toLowerCase()}-checkout`,

View File

@@ -3,7 +3,6 @@
import defaults from 'lodash/defaults'; import defaults from 'lodash/defaults';
import each from 'lodash/each'; import each from 'lodash/each';
import find from 'lodash/find'; import find from 'lodash/find';
import pick from 'lodash/pick';
import moment from 'moment'; import moment from 'moment';
import { getAnalyticsServiceByEnvironment } from '../analyticsService'; import { getAnalyticsServiceByEnvironment } from '../analyticsService';
@@ -465,7 +464,7 @@ async function cancelSubscription (data) {
analytics.track(cancelType, { analytics.track(cancelType, {
uuid: data.user._id, uuid: data.user._id,
user: pick(data.user, ['preferences', 'registeredThrough']), user: data.user,
groupId, groupId,
paymentMethod: data.paymentMethod, paymentMethod: data.paymentMethod,
headers: data.headers, headers: data.headers,

View File

@@ -3,7 +3,6 @@ import cloneDeep from 'lodash/cloneDeep';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import forEach from 'lodash/forEach'; import forEach from 'lodash/forEach';
import keys from 'lodash/keys'; import keys from 'lodash/keys';
import pick from 'lodash/pick';
import remove from 'lodash/remove'; import remove from 'lodash/remove';
import validator from 'validator'; import validator from 'validator';
import { import {
@@ -516,7 +515,7 @@ async function scoreTask (user, task, direction, req, res) {
role = 'member'; role = 'member';
} }
res.analytics.track('team task scored', { res.analytics.track('team task scored', {
user: pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',

View File

@@ -117,7 +117,7 @@ export async function update (req, res, { isV3 = false }) {
user.invitations.party = {}; user.invitations.party = {};
user.invitations.parties = []; user.invitations.parties = [];
res.analytics.track('Starts Looking for Party', { res.analytics.track('Starts Looking for Party', {
user: _.pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -201,7 +201,7 @@ export async function update (req, res, { isV3 = false }) {
if (key === 'party.seeking' && val === null) { if (key === 'party.seeking' && val === null) {
user.party.seeking = undefined; user.party.seeking = undefined;
res.analytics.track('Leaves Looking for Party', { res.analytics.track('Leaves Looking for Party', {
user: _.pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',
@@ -292,7 +292,7 @@ export async function reset (req, res, { isV3 = false }) {
]); ]);
res.analytics.track('account reset', { res.analytics.track('account reset', {
user: _.pick(user, ['preferences', 'registeredThrough']), user,
uuid: user._id, uuid: user._id,
hitType: 'event', hitType: 'event',
category: 'behavior', category: 'behavior',