Files
habitica/website/server/libs/analyticsService.js
Sabe Jones 25b0ff38c4 Login Incentives (#8230)
* feat(incentives): login bennies WIP

* feat(content): incentive prize content WIP

* fix(content): placeholders pass tests

* WIP(content): Bard instrument placeholder

* feat(content): Incentives build

* chore(sprites): compile
and fix some strings

* WIP(incentives): quests and backgrounds

* fix(quests): correct buy/launch handling

* [WIP] Incentives rewarding (#8226)

* Added login incentive rewards

* Updated incentive rewards

* Added incentive modal and updated notification structure

* Added analytics to sleeping

* Added login incentives to user analytics

* Fixed unit tests and ensured that prizes are incremented and not replaced

* Updated style of daily login incentive modal

* Added rewards modal

* Added translations

* Added loigin incentive ui elements to profile

* Updated login incentives structure and abstracted to common.content

* Added dynamic display for login incentives on profile

* Added purple potion image

* Updated daily login modal

* Fixed progress calculation

* Added bard gear

* Updated login incentive rewards

* Fixed styles and text

* Added multiple read for notifications

* Fixed lint issues

* Fixed styles and added 50 limit

* Updated quest keys

* Added login incentives reward page

* Fixed tests

* Fixed linting and tests

* Read named notifications route. Add image for backgrounds

* Fixed style issues and added tranlsations to login incentive notification

* Hided abiltiy to purchase incentive backgrounds and added message to detail how to unlock

* Updated awarded message

* Fixed text and updated progress counter to display better

* Fixed purple potion reward text

* Fixed check in backgrouns reward text

* fix(quest): pass tests

* Added display of multiple rewards

* Updated modal styles

* Fixed neagtive 50 issue

* Remvoed total count from daily login incentives modal

* Fixed magic paw display

* fix(awards): give bunnies again

* WIP(incentives): more progress on BG shop

* fix(incentives): actually award backgrounds

* fix(incentives): more BG fixy

* fix(backgrounds): don't gem-buy checkin bgs

* Added dust bunny notification

* fix(incentives): don't redisplay bunny award

* chore(news): Bailey
and different promo sprite
2016-11-23 19:34:09 -06:00

293 lines
6.8 KiB
JavaScript

/* eslint-disable camelcase */
import nconf from 'nconf';
import Amplitude from 'amplitude';
import Bluebird from 'bluebird';
import googleAnalytics from 'universal-analytics';
import useragent from 'useragent';
import {
each,
omit,
} from 'lodash';
import { content as Content } from '../../common';
const AMPLIUDE_TOKEN = nconf.get('AMPLITUDE_KEY');
const GA_TOKEN = nconf.get('GA_ID');
const GA_POSSIBLE_LABELS = ['gaLabel', 'itemKey'];
const GA_POSSIBLE_VALUES = ['gaValue', 'gemCost', 'goldCost'];
const AMPLITUDE_PROPERTIES_TO_SCRUB = ['uuid', 'user', 'purchaseValue', 'gaLabel', 'gaValue', 'headers'];
const PLATFORM_MAP = Object.freeze({
'habitica-web': 'Web',
'habitica-ios': 'iOS',
'habitica-android': 'Android',
});
let amplitude = new Amplitude(AMPLIUDE_TOKEN);
let ga = googleAnalytics(GA_TOKEN);
let _lookUpItemName = (itemKey) => {
if (!itemKey) return;
let gear = Content.gear.flat[itemKey];
let egg = Content.eggs[itemKey];
let food = Content.food[itemKey];
let hatchingPotion = Content.hatchingPotions[itemKey];
let quest = Content.quests[itemKey];
let spell = Content.special[itemKey];
let itemName;
if (gear) {
itemName = gear.text();
} else if (egg) {
itemName = `${egg.text()} Egg`;
} else if (food) {
itemName = food.text();
} else if (hatchingPotion) {
itemName = `${hatchingPotion.text()} Hatching Potion`;
} else if (quest) {
itemName = quest.text();
} else if (spell) {
itemName = spell.text();
}
return itemName;
};
let _formatUserData = (user) => {
let properties = {};
if (user.stats) {
properties.Class = user.stats.class;
properties.Experience = Math.floor(user.stats.exp);
properties.Gold = Math.floor(user.stats.gp);
properties.Health = Math.ceil(user.stats.hp);
properties.Level = user.stats.lvl;
properties.Mana = Math.floor(user.stats.mp);
}
properties.balance = user.balance;
properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
if (user.habits && user.dailys && user.todos && user.rewards) {
properties['Number Of Tasks'] = {
habits: user.habits.length,
dailys: user.dailys.length,
todos: user.todos.length,
rewards: user.rewards.length,
};
}
if (user.contributor && user.contributor.level) {
properties.contributorLevel = user.contributor.level;
}
if (user.purchased && user.purchased.plan.planId) {
properties.subscription = user.purchased.plan.planId;
}
if (user._ABtest) {
properties.ABtest = user._ABtest;
}
if (user.registeredThrough) {
properties.registeredPlatform = user.registeredThrough;
}
if (user.loginIncentives) {
properties.loginIncentives = user.loginIncentives;
}
return properties;
};
let _formatPlatformForAmplitude = (platform) => {
if (!platform) {
return 'Unknown';
}
if (platform in PLATFORM_MAP) {
return PLATFORM_MAP[platform];
}
return '3rd Party';
};
let _formatUserAgentForAmplitude = (platform, agentString) => {
if (!agentString) {
return 'Unknown';
}
let agent = useragent.lookup(agentString).toJSON();
let formattedAgent = {};
if (platform === 'iOS' || platform === 'Android') {
formattedAgent.name = agent.os.family;
formattedAgent.version = `${agent.os.major}.${agent.os.minor}.${agent.os.patch}`;
if (platform === 'Android' && formattedAgent.name === 'Other') {
formattedAgent.name = 'Android';
}
} else {
formattedAgent.name = agent.family;
formattedAgent.version = agent.major;
}
return formattedAgent;
};
let _formatDataForAmplitude = (data) => {
let event_properties = omit(data, AMPLITUDE_PROPERTIES_TO_SCRUB);
let platform = _formatPlatformForAmplitude(data.headers && data.headers['x-client']);
let agent = _formatUserAgentForAmplitude(platform, data.headers && data.headers['user-agent']);
let ampData = {
user_id: data.uuid || 'no-user-id-was-provided',
platform,
os_name: agent.name,
os_version: agent.version,
event_properties,
};
if (data.user) {
ampData.user_properties = _formatUserData(data.user);
}
let itemName = _lookUpItemName(data.itemKey);
if (itemName) {
ampData.event_properties.itemName = itemName;
}
return ampData;
};
let _sendDataToAmplitude = (eventType, data) => {
let amplitudeData = _formatDataForAmplitude(data);
amplitudeData.event_type = eventType;
return new Bluebird((resolve, reject) => {
amplitude.track(amplitudeData)
.then(resolve)
.catch(() => reject('Error while sending data to Amplitude.'));
});
};
let _generateLabelForGoogleAnalytics = (data) => {
let label;
each(GA_POSSIBLE_LABELS, (key) => {
if (data[key]) {
label = data[key];
return false; // exit each early
}
});
return label;
};
let _generateValueForGoogleAnalytics = (data) => {
let value;
each(GA_POSSIBLE_VALUES, (key) => {
if (data[key]) {
value = data[key];
return false; // exit each early
}
});
return value;
};
let _sendDataToGoogle = (eventType, data) => {
let eventData = {
ec: data.gaCategory || data.category || 'behavior',
ea: eventType,
};
let label = _generateLabelForGoogleAnalytics(data);
if (label) {
eventData.el = label;
}
let value = _generateValueForGoogleAnalytics(data);
if (value) {
eventData.ev = value;
}
return new Bluebird((resolve, reject) => {
ga.event(eventData, (err) => {
if (err) return reject(err);
resolve();
});
});
};
let _sendPurchaseDataToAmplitude = (data) => {
let amplitudeData = _formatDataForAmplitude(data);
amplitudeData.event_type = 'purchase';
amplitudeData.revenue = data.purchaseValue;
return new Bluebird((resolve, reject) => {
amplitude.track(amplitudeData)
.then(resolve)
.catch(reject);
});
};
let _sendPurchaseDataToGoogle = (data) => {
let label = data.paymentMethod;
let type = data.purchaseType;
let price = data.purchaseValue;
let qty = data.quantity;
let sku = data.sku;
let itemKey = data.itemPurchased;
let variation = type;
if (data.gift) variation += ' - Gift';
let eventData = {
ec: 'commerce',
ea: type,
el: label,
ev: price,
};
return new Bluebird((resolve) => {
ga.event(eventData).send();
ga.transaction(data.uuid, price)
.item(price, qty, sku, itemKey, variation)
.send();
resolve();
});
};
function track (eventType, data) {
return Bluebird.all([
_sendDataToAmplitude(eventType, data),
_sendDataToGoogle(eventType, data),
]);
}
function trackPurchase (data) {
return Bluebird.all([
_sendPurchaseDataToAmplitude(data),
_sendPurchaseDataToGoogle(data),
]);
}
// Stub for non-prod environments
let mockAnalyticsService = {
track: () => { },
trackPurchase: () => { },
};
module.exports = {
track,
trackPurchase,
mockAnalyticsService,
};