Client Analytics (#9023)

* start to refactor analytics and some mixins

* wip

* wip

* wip

* more analytics

* more analytics

* more anlytics

* fix analytics module

* finish analytics

* fix env

* vue casing
This commit is contained in:
Matteo Pagliazzi
2017-09-08 21:23:58 +02:00
committed by GitHub
parent 6e70f27bc6
commit 1dc1923d7b
26 changed files with 402 additions and 148 deletions

View File

@@ -69,6 +69,7 @@ import AppFooter from './components/appFooter';
import notificationsDisplay from './components/notifications';
import snackbars from './components/snackbars/notifications';
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import BuyModal from './components/shops/buyModal.vue';
import SelectMembersModal from 'client/components/selectMembersModal.vue';
import notifications from 'client/mixins/notifications';
@@ -158,14 +159,27 @@ export default {
this.$store.dispatch('tasks:fetchUserTasks'),
]).then(() => {
this.isUserLoaded = true;
Analytics.setUser();
Analytics.updateUser();
}).catch((err) => {
console.error('Impossible to fetch user. Clean up localStorage and refresh.', err); // eslint-disable-line no-console
});
}
// Manage modals
this.$root.$on('show::modal', (modalId, data) => {
if (data && data.fromRoot) return;
this.$root.$on('show::modal', (modalId, data = {}) => {
if (data.fromRoot) return;
// Track opening of gems modal unless it's been already tracked
// For example the gems button in the menu already tracks the event by itself
if (modalId === 'buy-gems' && data.alreadyTracked !== true) {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Gems > Wallet',
});
}
// Get last modal on stack and hide
let modalStackLength = this.$store.state.modalStack.length;

View File

@@ -60,6 +60,7 @@ import bModal from 'bootstrap-vue/lib/components/modal';
import Avatar from '../avatar';
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import percent from '../../../common/script/libs/percent';
import {maxHealth} from '../../../common/script/index';
@@ -84,6 +85,14 @@ export default {
return `${Math.ceil(this.user.stats.hp)} / ${this.maxHealth}`;
},
},
mounted () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Health Warning',
});
},
methods: {
close () {
this.$root.$emit('hide::modal', 'low-health');

View File

@@ -219,6 +219,7 @@
import axios from 'axios';
import moment from 'moment';
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import gryphon from 'assets/svg/gryphon.svg';
import twitter from 'assets/svg/twitter.svg';
@@ -331,6 +332,12 @@ export default {
this.$root.$emit('show::modal', 'modify-inventory');
},
donate () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Gems > Donate',
});
this.$root.$emit('show::modal', 'buy-gems');
},
},

View File

@@ -224,6 +224,7 @@ div
<script>
import { mapState, mapGetters } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import gemIcon from 'assets/svg/gem.svg';
import goldIcon from 'assets/svg/gold.svg';
import userIcon from 'assets/svg/user.svg';
@@ -289,6 +290,12 @@ export default {
this.$root.$emit('show::modal', 'create-party-modal');
},
showBuyGemsModal () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Gems > Toolbar',
});
this.$root.$emit('show::modal', 'buy-gems');
},
},

View File

@@ -290,6 +290,9 @@ export default {
passwordConfirm: this.passwordConfirm,
});
// @TODO do not reload entire page
// problem is that app.vue created hook should be called again
// after user is logged in / just signed up
window.location.href = '/';
},
async login () {
@@ -299,6 +302,9 @@ export default {
password: this.password,
});
// @TODO do not reload entire page
// problem is that app.vue created hook should be called again
// after user is logged in / just signed up
window.location.href = '/';
},
async socialAuth (network) {
@@ -314,6 +320,9 @@ export default {
auth,
});
// @TODO do not reload entire page
// problem is that app.vue created hook should be called again
// after user is logged in / just signed up
window.location.href = '/';
},
handleSubmit () {

View File

@@ -55,6 +55,8 @@
.col-12.text-center.submit-button-wrapper
.alert.alert-warning(v-if='insufficientGemsForTavernChallenge')
You do not have enough gems to create a Tavern challenge
// @TODO if buy gems button is added, add analytics tracking to it
// see https://github.com/HabitRPG/habitica/blob/develop/website/views/options/social/challenges.jade#L134
button.btn.btn-primary(v-once, v-if='creating', @click='createChallenge()') {{$t('createChallengeAddTasks')}}
button.btn.btn-primary(v-once, v-if='!creating', @click='updateChallenge()') {{$t('updateChallenge')}}
.col-12.text-center

View File

@@ -17,7 +17,7 @@ b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
.join-party
h3(v-once) {{$t('wantToJoinPartyTitle')}}
p(v-once) {{$t('wantToJoinPartyDescription')}}
button.btn.btn-primary(v-once, @click='shareUserIdShown = !shareUserIdShown') {{$t('shartUserId')}}
button.btn.btn-primary(v-once, @click='shareUserId()') {{$t('shartUserId')}}
.share-userid-options(v-if="shareUserIdShown")
.option-item(v-once)
.svg-icon(v-html="icons.copy")
@@ -137,6 +137,7 @@ b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
<script>
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import bModal from 'bootstrap-vue/lib/components/modal';
@@ -166,6 +167,15 @@ export default {
...mapState({user: 'user.data'}),
},
methods: {
shareUserId () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Health Warning',
});
this.shareUserIdShown = !this.shareUserIdShown;
},
async createParty () {
let group = {
type: 'party',
@@ -174,9 +184,14 @@ export default {
let party = await this.$store.dispatch('guilds:create', {group});
this.$store.state.party.data = party;
this.user.party._id = party._id;
Analytics.updateUser({
partyID: party._id,
partySize: 1,
});
this.$root.$emit('hide::modal', 'create-party-modal');
this.$router.push('/party');
// @TODO: Analytics.updateUser({'partyID': $scope.group ._id, 'partySize': 1});
},
},
};

View File

@@ -409,6 +409,7 @@ import extend from 'lodash/extend';
import groupUtilities from 'client/mixins/groupsUtilities';
import styleHelper from 'client/mixins/styleHelper';
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import membersModal from './membersModal';
import startQuestModal from './startQuestModal';
import quests from 'common/script/content/quests';
@@ -636,7 +637,7 @@ export default {
},
async sendMessage () {
let response = await this.$store.dispatch('chat:postChat', {
groupId: this.group._id,
group: this.group._id,
message: this.newMessage,
});
this.group.chat.unshift(response.message);
@@ -699,7 +700,13 @@ export default {
this.user.guilds.push(this.group._id);
},
clickLeave () {
// Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Leave Party'});
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Leave Party',
});
// @TODO: Get challenges and ask to keep or remove
if (!confirm('Are you sure you want to leave?')) return;
let keep = true;
@@ -714,12 +721,14 @@ export default {
keepChallenges,
};
if (this.isParty) data.type = 'party';
if (this.isParty) {
data.type = 'party';
Analytics.updateUser({partySize: null, partyID: null});
}
await this.$store.dispatch('guilds:leave', data);
// @TODO: Implement
// Analytics.updateUser({'partySize':null,'partyID':null});
// User.sync().then(function () {
// $rootScope.hardRedirect('/#/options/groups/party');
// });
@@ -743,7 +752,13 @@ export default {
await this.$store.dispatch('guilds:join', {groupId: this.group._id});
},
clickStartQuest () {
// Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'});
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Start a Quest',
});
let hasQuests = find(this.user.items.quests, (quest) => {
return quest > 0;
});

View File

@@ -375,7 +375,14 @@ export default {
// @TODO: Add proper notifications
alert('Not enough gems');
return;
// @TODO return $rootScope.openModal('buyGems', {track:"Gems > Create Group"});
// @TODO return $rootScope.openModal('buyGems', {track:"Gems > Gems > Create Group"});
// @TODO when modal is implemented, enable analytics
/* Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Health Warning',
}); */
}
if (!this.workingGroup.name || !this.workingGroup.description) {

View File

@@ -55,8 +55,12 @@ import filter from 'lodash/filter';
import map from 'lodash/map';
import bModal from 'bootstrap-vue/lib/components/modal';
import notifications from 'client/mixins/notifications';
import * as Analytics from 'client/libs/analytics';
export default {
components: {
bModal,
},
mixins: [notifications],
props: ['group'],
data () {
@@ -65,8 +69,13 @@ export default {
emails: [],
};
},
components: {
bModal,
mounted () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Invite Friends',
});
},
computed: {
...mapState({user: 'user.data'}),

View File

@@ -159,6 +159,7 @@
<script>
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import bModal from 'bootstrap-vue/lib/components/modal';
import quests from 'common/script/content/quests';
@@ -208,10 +209,15 @@ export default {
this.selectedQuest = quest;
},
async questInit () {
let key = this.selectedQuest;
// Analytics.updateUser({'partyID': party._id, 'partySize': party.memberCount});
let response = await this.$store.dispatch('guilds:inviteToQuest', {groupId: this.group._id, key});
let quest = response.data.data;
Analytics.updateUser({
partyID: this.group._id,
partySize: this.group.memberCount,
});
const key = this.selectedQuest;
const response = await this.$store.dispatch('guilds:inviteToQuest', {groupId: this.group._id, key});
const quest = response.data.data;
this.$store.party.quest = quest;
this.$root.$emit('hide::modal', 'start-quest-modal');
},

View File

@@ -137,6 +137,7 @@ import isEmpty from 'lodash/isEmpty';
import map from 'lodash/map';
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import quests from 'common/script/content/quests';
import notificationsIcon from 'assets/svg/notifications.svg';
@@ -299,7 +300,10 @@ export default {
if (type === 'party') {
// @TODO: pretty sure mutability is wrong. Need to check React docs
// @TODO mutation to store data should only happen through actions
this.user.invitations.parties.splice(index, 1);
Analytics.updateUser({partyID: group.id});
} else {
this.user.invitations.guilds.splice(index, 1);
}

View File

@@ -181,6 +181,7 @@
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import * as Analytics from 'client/libs/analytics';
import svgClose from 'assets/svg/close.svg';
import svgGold from 'assets/svg/gold.svg';
@@ -266,6 +267,14 @@
this.hideDialog();
},
purchaseGems () {
if (this.item.key === 'rebirth_orb') {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Gems > Rebirth',
});
}
this.$root.$emit('show::modal', 'buy-gems');
},
togglePinned () {

View File

@@ -19,6 +19,7 @@
.row.row-margin(style="font-size: 2rem;")
span {{ $t('enterprisePlansDescription') }}
.row.row-margin
// TODO
a.btn.btn-primary.btn-lg.btn-block(:href="'mailto:vicky@habitica.com?subject=' + $t('enterprisePlansEmailSubject')") {{ $t('enterprisePlansButton') }}
br
@@ -28,3 +29,22 @@
.row.row-margin
a.btn.btn-primary.btn-lg.btn-block(href="https://docs.google.com/forms/d/e/1FAIpQLSerMKkaCg3UcgpcMvBJtlNgnF9DNY8sxCebpAT-GHeDAQASPQ/viewform?usp=sf_link") {{ $t('familyPlansButton') }}
</template>
<script>
import * as Analytics from 'client/libs/analytics';
export default {
methods: {
contactUs () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Contact Us (Plans)',
});
window.location.href = `mailto:vicky@habitica.com?subject=${this.$t('enterprisePlansEmailSubject')}`;
},
},
};
</script>

View File

@@ -91,20 +91,29 @@
</style>
<script>
import logo from 'assets/svg/logo.svg';
import logo from 'assets/svg/logo.svg';
import * as Analytics from 'client/libs/analytics';
export default {
data () {
return {
icons: Object.freeze({
logo,
}),
};
export default {
data () {
return {
icons: Object.freeze({
logo,
}),
};
},
methods: {
playButtonClick () {
// @TODO duplicate of code in home.vue
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Play',
});
this.$router.push('/register');
},
methods: {
playButtonClick () {
this.$router.push('/register');
},
},
};
},
};
</script>

View File

@@ -602,6 +602,7 @@
<script>
import AppFooter from 'client/components/appFooter';
import StaticHeader from './header.vue';
import * as Analytics from 'client/libs/analytics';
export default {
components: {
@@ -614,10 +615,21 @@
};
},
mounted () {
// Analytics.track({"hitType":"pageview","eventCategory":"page","eventAction":"landing page","page":"/home"});
Analytics.track({
hitType: 'pageview',
eventCategory: 'page',
eventAction: 'landing page',
page: '/home',
});
},
methods: {
playButtonClick () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Play',
});
this.$router.push('/register');
},
},

View File

@@ -298,6 +298,7 @@ import moment from 'moment';
import axios from 'axios';
import scoreTask from 'common/script/ops/scoreTask';
import Vue from 'vue';
import * as Analytics from 'client/libs/analytics';
import positiveIcon from 'assets/svg/positive.svg';
import negativeIcon from 'assets/svg/negative.svg';
@@ -435,6 +436,7 @@ export default {
if (task.group.approval.required) task.group.approval.requested = true;
Analytics.updateUser();
const response = await axios.post(`/api/v3/tasks/${task._id}/score/${direction}`);
const tmp = response.data.data._tmp || {}; // used to notify drops, critical hits and other bonuses
const crit = tmp.crit;

View File

@@ -0,0 +1,143 @@
import isEqual from 'lodash/isEqual';
import keys from 'lodash/keys';
import pick from 'lodash/pick';
import includes from 'lodash/includes';
import getStore from 'client/store';
import Vue from 'vue';
let REQUIRED_FIELDS = ['hitType', 'eventCategory', 'eventAction'];
let ALLOWED_HIT_TYPES = [
'pageview',
'screenview',
'event',
'transaction',
'item',
'social',
'exception',
'timing',
];
const store = getStore();
function _doesNotHaveRequiredFields (properties) {
if (!isEqual(keys(pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) {
// @TODO: Log with Winston?
// console.log('Analytics tracking calls must include the following properties: ' + JSON.stringify(REQUIRED_FIELDS));
return true;
}
}
function _doesNotHaveAllowedHitType (properties) {
if (!includes(ALLOWED_HIT_TYPES, properties.hitType)) {
// @TODO: Log with Winston?
// console.log('Hit type of Analytics event must be one of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES));
return true;
}
}
function _gatherUserStats (properties) {
const user = store.state.user.data;
const tasks = store.state.tasks.data;
properties.UUID = user._id;
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.balanceGemAmount = properties.balance * 4;
properties.tutorialComplete = user.flags.tour.intro === -2;
properties['Number Of Tasks'] = {
habits: tasks.habits.length,
dailys: tasks.dailys.length,
todos: tasks.todos.length,
rewards: tasks.rewards.length,
};
if (user.contributor.level) properties.contributorLevel = user.contributor.level;
if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId;
}
export function setUser () {
const user = store.state.user.data;
window.amplitude.setUserId(user._id);
window.ga('set', {userId: user._id});
}
export function track (properties) {
// Use nextTick to avoid blocking the UI
Vue.nextTick(() => {
if (_doesNotHaveRequiredFields(properties)) return false;
if (_doesNotHaveAllowedHitType(properties)) return false;
window.amplitude.logEvent(properties.eventAction, properties);
window.ga('send', properties);
});
}
export function updateUser (properties) {
// Use nextTick to avoid blocking the UI
Vue.nextTick(() => {
properties = properties || {};
_gatherUserStats(properties);
window.amplitude.setUserProperties(properties);
window.ga('set', properties);
});
}
export function setup () {
const IS_PRODUCTION = process.env.NODE_ENV === 'production'; // eslint-disable-line no-process-env
const AMPLITUDE_KEY = process.env.AMPLITUDE_KEY; // eslint-disable-line no-process-env
const GA_ID = process.env.GA_ID; // eslint-disable-line no-process-env
// Setup queues until the real scripts are loaded
/* eslint-disable */
// Amplitude
var r = window.amplitude || {};
r._q = [];
function a(window) {r[window] = function() {r._q.push([window].concat(Array.prototype.slice.call(arguments, 0)));}}
var i = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "setGlobalUserProperties"];
for (var o = 0; o < i.length; o++) {a(i[o])}
window.amplitude = r;
amplitude.init(AMPLITUDE_KEY);
// Google Analytics (aka Universal Analytics)
window['GoogleAnalyticsObject'] = 'ga';
window['ga'] = window['ga'] || function() {
(window['ga'].q = window['ga'].q || []).push(arguments)
}, window['ga'].l = 1 * new Date();
ga('create', GA_ID);
/* eslint-enable */
// Load real scripts
if (!IS_PRODUCTION) return;
Vue.nextTick(() => {
// Amplitude
const amplitudeScript = document.createElement('script');
let firstScript = document.getElementsByTagName('script')[0];
amplitudeScript.type = 'text/javascript';
amplitudeScript.async = true;
amplitudeScript.src = 'https://d24n15hnbwhuhn.cloudfront.net/libs/amplitude-2.2.0-min.gz.js';
firstScript.parentNode.insertBefore(amplitudeScript, firstScript);
// Google Analytics
const gaScript = document.createElement('script');
firstScript = document.getElementsByTagName('script')[0];
gaScript.async = 1;
gaScript.src = '//www.google-analytics.com/analytics.js';
firstScript.parentNode.insertBefore(gaScript, firstScript);
});
}

View File

@@ -4,6 +4,7 @@ require('babel-polyfill');
import Vue from 'vue';
import AppComponent from './app';
import { setup as setupAnalytics } from 'client/libs/analytics';
import router from './router';
import getStore from './store';
import StoreModule from './libs/store';
@@ -26,6 +27,8 @@ Vue.config.productionTip = IS_PRODUCTION;
Vue.use(i18n, {i18nData: window && window['habitica-i18n']});
Vue.use(StoreModule);
setupAnalytics();
export default new Vue({
el: '#app',
router,

View File

@@ -1,86 +0,0 @@
import isEqual from 'lodash/isEqual';
import keys from 'lodash/keys';
import pick from 'lodash/pick';
import includes from 'lodash/includes';
let REQUIRED_FIELDS = ['hitType', 'eventCategory', 'eventAction'];
let ALLOWED_HIT_TYPES = [
'pageview',
'screenview',
'event',
'transaction',
'item',
'social',
'exception',
'timing',
];
export default {
methods: {
register (user) {
// @TODO: What is was the timeout for?
window.amplitude.setUserId(user._id);
window.ga('set', {userId: user._id});
},
login (user) {
window.amplitude.setUserId(user._id);
window.ga('set', {userId: user._id});
},
track (properties) {
if (this.doesNotHaveRequiredFields(properties)) return false;
if (this.doesNotHaveAllowedHitType(properties)) return false;
window.amplitude.logEvent(properties.eventAction, properties);
window.ga('send', properties);
},
updateUser (properties, user) {
properties = properties || {};
this.gatherUserStats(user, properties);
window.amplitude.setUserProperties(properties);
window.ga('set', properties);
},
gatherUserStats (user, properties) {
if (user._id) properties.UUID = user._id;
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.balanceGemAmount = properties.balance * 4;
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;
},
doesNotHaveRequiredFields (properties) {
if (!isEqual(keys(pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) {
// @TODO: Log with Winston?
// console.log('Analytics tracking calls must include the following properties: ' + JSON.stringify(REQUIRED_FIELDS));
return true;
}
},
doesNotHaveAllowedHitType (properties) {
if (!includes(ALLOWED_HIT_TYPES, properties.hitType)) {
// @TODO: Log with Winston?
// console.log('Hit type of Analytics event must be one of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES));
return true;
}
},
},
};

View File

@@ -1,5 +1,6 @@
import times from 'lodash/times';
import Intro from 'intro.js/';
import * as Analytics from 'client/libs/analytics';
export default {
data () {
@@ -124,6 +125,7 @@ export default {
},
hoyo (user) {
// @TODO: What is was the timeout for?
// @TODO move to analytics
window.amplitude.setUserId(user._id);
window.ga('set', {userId: user._id});
},
@@ -143,6 +145,15 @@ export default {
opts.steps = opts.steps.concat(this.chapters[chapter][p]);
});
Analytics.track({
hitType: 'event',
eventCategory: 'behavior',
eventAction: 'tutorial',
eventLabel: `${chapter}-web`,
eventValue: page + 1,
complete: true,
});
// @TODO: Do we always need to initialize here?
let intro = Intro.introJs();
intro.setOptions({steps: opts.steps});
@@ -173,9 +184,17 @@ export default {
// // @TODO: Notification.showLoginIncentive(this.user, rewardData, Social.loadWidgets);
// }
// Mark tour complete
// Mark tour complete
ups[`flags.tour.${chapter}`] = -2; // @TODO: Move magic numbers to enum
// @TODO: Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':true})
Analytics.track({
hitType: 'event',
eventCategory: 'behavior',
eventAction: 'tutorial',
eventLabel: `${chapter}-web`,
eventValue: lastKnownStep,
complete: true,
});
// }
this.$store.dispatch('user:set', ups);

View File

@@ -69,7 +69,7 @@ export default {
let isGem = data && data.gift && data.gift.type === 'gems';
let notEnoughGem = isGem && (!data.gift.gems.amount || data.gift.gems.amount === 0);
if (notEnoughGem) {
Notification.error(this.$t('badAmountOfGemsToPurchase'), true);
this.error(this.$t('badAmountOfGemsToPurchase'), true);
return false;
}
return true;

View File

@@ -1,6 +1,7 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import getStore from 'client/store';
import * as Analytics from 'client/libs/analytics';
// import EmptyView from './components/emptyView';
@@ -14,7 +15,7 @@ const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'./
const ContactPage = () => import(/* webpackChunkName: "static" */'./components/static/contact');
const FAQPage = () => import(/* webpackChunkName: "static" */'./components/static/faq');
const FeaturesPage = () => import(/* webpackChunkName: "static" */'./components/static/features');
const FrontPage = () => import(/* webpackChunkName: "static" */'./components/static/front');
const HomePage = () => import(/* webpackChunkName: "static" */'./components/static/home');
const GroupPlansPage = () => import(/* webpackChunkName: "static" */'./components/static/groupPlans');
const MaintenancePage = () => import(/* webpackChunkName: "static" */'./components/static/maintenance');
const MaintenanceInfoPage = () => import(/* webpackChunkName: "static" */'./components/static/maintenanceInfo');
@@ -99,7 +100,7 @@ const router = new VueRouter({
},
// requiresLogin is true by default, isStatic false
routes: [
{ name: 'home', path: '/home', component: FrontPage, meta: {requiresLogin: false} },
{ name: 'home', path: '/home', component: HomePage, meta: {requiresLogin: false} },
{ name: 'register', path: '/register', component: RegisterLogin, meta: {requiresLogin: false} },
{ name: 'login', path: '/login', component: RegisterLogin, meta: {requiresLogin: false} },
{ name: 'tasks', path: '/', component: UserTasks },
@@ -287,6 +288,13 @@ router.beforeEach(function routerGuard (to, from, next) {
return next({name: 'tasks'});
}
Analytics.track({
hitType: 'pageview',
eventCategory: 'navigation',
eventAction: 'navigate',
page: to.name || to.path,
});
next();
});

View File

@@ -21,16 +21,6 @@ export async function register (store, params) {
},
});
localStorage.setItem(LOCALSTORAGE_AUTH_KEY, userLocalData);
// @TODO: I think we just need analytics here
// Auth.runAuth(res.data._id, res.data.apiToken);
// Analytics.register();
// $scope.registrationInProgress = false;
// Alert.authErrorAlert(data, status, headers, config)
// Analytics.login();
// Analytics.updateUser();
store.state.user.data = user;
}
export async function login (store, params) {
@@ -51,17 +41,6 @@ export async function login (store, params) {
});
localStorage.setItem(LOCALSTORAGE_AUTH_KEY, userLocalData);
// @TODO: I think we just need analytics here
// Auth.runAuth(res.data._id, res.data.apiToken);
// Analytics.register();
// $scope.registrationInProgress = false;
// Alert.authErrorAlert(data, status, headers, config)
// Analytics.login();
// Analytics.updateUser();
// @TODO: Update the api to return the user?
// store.state.user.data = user;
}
export async function socialAuth (store, params) {

View File

@@ -1,4 +1,5 @@
import axios from 'axios';
import * as Analytics from 'client/libs/analytics';
export async function getChat (store, payload) {
let response = await axios.get(`/api/v3/groups/${payload.groupId}/chat`);
@@ -7,12 +8,40 @@ export async function getChat (store, payload) {
}
export async function postChat (store, payload) {
let url = `/api/v3/groups/${payload.groupId}/chat`;
const group = payload.group;
let url = `/api/v3/groups/${group._id}/chat`;
if (payload.previousMsg) {
url += `?previousMsg=${payload.previousMsg}`;
}
if (group.type === 'party') {
Analytics.updateUser({
partyID: group.id,
partySize: group.memberCount,
});
}
if (group.privacy === 'public') {
Analytics.track({
hitType: 'event',
eventCategory: 'behavior',
eventAction: 'group chat',
groupType: group.type,
privacy: group.privacy,
groupName: group.name,
});
} else {
Analytics.track({
hitType: 'event',
eventCategory: 'behavior',
eventAction: 'group chat',
groupType: group.type,
privacy: group.privacy,
});
}
let response = await axios.post(url, {
message: payload.message,
});

View File

@@ -1,12 +1,15 @@
import axios from 'axios';
import * as Analytics from 'client/libs/analytics';
// export async function initQuest (store) {
// }
export async function sendAction (store, payload) {
// Analytics.updateUser({
// partyID: party._id,
// partySize: party.memberCount
// });
Analytics.updateUser({
partyID: store.state.party.data._id,
partySize: store.state.party.data.memberCount,
});
let response = await axios.post(`/api/v3/groups/${payload.groupId}/${payload.action}`);
// @TODO: Update user?