Merge branch 'release' into develop

This commit is contained in:
Matteo Pagliazzi 2020-10-28 23:24:42 +01:00
commit ea766251c2
12 changed files with 105 additions and 9 deletions

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "habitica",
"version": "4.166.0",
"version": "4.166.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.166.0",
"version": "4.166.2",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.12.3",

View file

@ -0,0 +1,28 @@
import {
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration/v3';
import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService';
describe('POST /analytics/track/:eventName', () => {
it('requires authentication', async () => {
await expect(requester().post('/analytics/track/event')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingAuthHeaders'),
});
});
it('calls res.analytics', async () => {
const user = await generateUser();
sandbox.spy(analytics, 'track');
const requestWithHeaders = requester(user, { 'x-client': 'habitica-web' });
await requestWithHeaders.post('/analytics/track/eventName', { data: 'example' }, { 'x-client': 'habitica-web' });
expect(analytics.track).to.be.calledOnce;
expect(analytics.track).to.be.calledWith('eventName', sandbox.match({ data: 'example' }));
sandbox.restore();
});
});

View file

@ -41,6 +41,7 @@ function _requestMaker (user, method, additionalSets = {}) {
|| route.indexOf('/amazon') === 0
|| route.indexOf('/stripe') === 0
|| route.indexOf('/qr-code') === 0
|| route.indexOf('/analytics') === 0
) {
url += `${route}`;
} else {

View file

@ -232,7 +232,7 @@ export default {
eventCategory: 'drop-cap-reached',
eventAction: 'click',
eventLabel: 'Drop Cap Reached > Modal > Wiki',
});
}, { trackOnServer: true });
},
toLearnMore () {
Analytics.track({
@ -240,7 +240,7 @@ export default {
eventCategory: 'drop-cap-reached',
eventAction: 'click',
eventLabel: 'Drop Cap Reached > Modal > Subscriptions',
});
}, { trackOnServer: true });
this.close();
this.$router.push('/user/settings/subscription');

View file

@ -36,7 +36,7 @@ export default {
eventCategory: 'drop-cap-reached',
eventAction: 'click',
eventLabel: 'Drop Cap Reached > Notification Click',
});
}, { trackOnServer: true });
},
},
};

View file

@ -183,7 +183,7 @@ export default {
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'User Dropdown > Subscriptions',
});
}, { trackOnServer: true });
this.$router.push({ name: 'subscription' });
},

View file

@ -82,14 +82,21 @@ export function setUser () {
window.ga('set', { userId: user._id });
}
export function track (properties) {
export function track (properties, options = {}) {
// Use nextTick to avoid blocking the UI
Vue.nextTick(() => {
if (_doesNotHaveRequiredFields(properties)) return;
if (_doesNotHaveAllowedHitType(properties)) return;
amplitude.getInstance().logEvent(properties.eventAction, properties);
window.ga('send', properties);
const trackOnServer = options && options.trackOnServer === true;
if (trackOnServer === true) {
// Track an event on the server
const store = getStore();
store.dispatch('analytics:trackEvent', properties);
} else {
amplitude.getInstance().logEvent(properties.eventAction, properties);
window.ga('send', properties);
}
});
}

View file

@ -0,0 +1,7 @@
import axios from 'axios';
export async function trackEvent (store, params) {
const url = `/analytics/track/${params.eventAction}`;
await axios.post(url, params);
}

View file

@ -17,6 +17,7 @@ import * as shops from './shops';
import * as snackbars from './snackbars';
import * as worldState from './worldState';
import * as news from './news';
import * as analytics from './analytics';
// Actions should be named as 'actionName' and can be accessed as 'namespace:actionName'
// Example: fetch in user.js -> 'user:fetch'
@ -39,6 +40,7 @@ const actions = flattenAndNamespace({
snackbars,
worldState,
news,
analytics,
});
export default actions;

View file

@ -148,6 +148,10 @@ module.exports = {
target: DEV_BASE_URL,
changeOrigin: true,
},
'^/analytics': {
target: DEV_BASE_URL,
changeOrigin: true,
},
},
},
};

View file

@ -0,0 +1,47 @@
import {
NotAuthorized,
} from '../../libs/errors';
import {
authWithHeaders,
} from '../../middlewares/auth';
const api = {};
/**
* @apiIgnore Analytics are considered part of the private API
* @api {post} /analytics/track/:eventName Track a generic analytics event
* @apiName AnalyticsTrack
* @apiGroup Analytics
*
* @apiSuccess {Object} data An empty object
* */
api.trackEvent = {
method: 'POST',
url: '/analytics/track/:eventName',
// we authenticate these requests to make sure they actually came from a real user
middlewares: [authWithHeaders()],
async handler (req, res) {
// As of now only web can track events using this route
if (req.headers['x-client'] !== 'habitica-web') {
throw new NotAuthorized('Only habitica.com is allowed to track analytics events.');
}
const { user } = res.locals;
const eventProperties = req.body;
res.analytics.track(req.params.eventName, {
uuid: user._id,
headers: req.headers,
category: 'behaviour',
gaLabel: 'local',
// hitType: 'event', sent from the client
...eventProperties,
});
// 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;