Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-02-26 15:28:30 -08:00
commit 1221265d08
14 changed files with 60 additions and 82 deletions

View file

@ -117,51 +117,15 @@ module.exports = class User extends CocoModel
application.tracker.identify announcesActionAudioGroup: @announcesActionAudioGroup unless me.isAdmin() application.tracker.identify announcesActionAudioGroup: @announcesActionAudioGroup unless me.isAdmin()
@announcesActionAudioGroup @announcesActionAudioGroup
getGemPromptGroup: -> getFourthLevelGroup: ->
# A/B Testing whether extra prompt when low gems leads to more gem purchases return @fourthLevelGroup if @fourthLevelGroup
# TODO: Rename gem purchase event in BuyGemsModal to 'Started gem purchase' after this test is over
return @gemPromptGroup if @gemPromptGroup
group = me.get('testGroupNumber') % 8 group = me.get('testGroupNumber') % 8
@gemPromptGroup = switch group @fourthLevelGroup = switch group
when 0, 1, 2, 3 then 'prompt' when 0, 1, 2, 3 then 'signs-and-portents'
when 4, 5, 6, 7 then 'no-prompt' when 4, 5, 6, 7 then 'forgetful-gemsmith'
@gemPromptGroup = 'prompt' if me.isAdmin() @fourthLevelGroup = 'signs-and-portents' if me.isAdmin()
application.tracker.identify gemPromptGroup: @gemPromptGroup unless me.isAdmin() application.tracker.identify fourthLevelGroup: @fourthLevelGroup unless me.isAdmin()
@gemPromptGroup @fourthLevelGroup
getForeshadowsLevels: ->
return false if $.browser.msie
return @foreshadowsLevels if @foreshadowsLevels?
group = me.get('testGroupNumber') % 16
@foreshadowsLevels = switch group
when 0, 1, 2, 3, 4, 5, 6, 7 then true
when 8, 9, 10, 11, 12, 13, 14, 15 then false
@foreshadowsLevels = true if me.isAdmin()
application.tracker.identify foreshadowsLevels: @foreshadowsLevels unless me.isAdmin()
@foreshadowsLevels
getLeaderboardsGroup: ->
return @leaderboardsGroup if @leaderboardsGroup?
group = me.get('testGroupNumber') % 64
if group < 16
@leaderboardsGroup = 'always'
else if group < 32
@leaderboardsGroup = 'early'
else if group < 48
@leaderboardsGroup = 'late'
else
@leaderboardsGroup = 'never'
@leaderboardsGroup = 'always' if me.isAdmin()
application.tracker.identify leaderboardsGroup: @leaderboardsGroup unless me.isAdmin()
@leaderboardsGroup
getShowsPortal: ->
return @showsPortal if @showsPortal?
group = me.get('testGroupNumber')
@showsPortal = if group < 128 then true else false
@showsPortal = true if me.isAdmin()
application.tracker.identify showsPortal: @showsPortal unless me.isAdmin()
@showsPortal
getVideoTutorialStylesIndex: (numVideos=0)-> getVideoTutorialStylesIndex: (numVideos=0)->
# A/B Testing video tutorial styles # A/B Testing video tutorial styles

View file

@ -402,9 +402,9 @@ $gameControlMargin: 30px
.level-indicator .level-indicator
margin-left: 15px margin-left: 15px
color: #E5C100 color: white
display: inline-block display: inline-block
margin: 0p 2px margin: 0 2px
.player-hero-icon .player-hero-icon
margin-left: 10px margin-left: 10px

View file

@ -83,7 +83,8 @@ else
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements") button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
if me.get('anonymous') === false || me.get('iosIdentifierForVendor') || isIPadApp if me.get('anonymous') === false || me.get('iosIdentifierForVendor') || isIPadApp
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems") button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account") if !me.get('anonymous', true)
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
if me.isAdmin() if me.isAdmin()
button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings") button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
else if me.get('anonymous', true) else if me.get('anonymous', true)

View file

@ -59,8 +59,6 @@ module.exports = class CampaignView extends RootView
@editorMode = options?.editorMode @editorMode = options?.editorMode
if @editorMode if @editorMode
@terrain ?= 'dungeon' @terrain ?= 'dungeon'
else unless me.getShowsPortal()
@terrain ?= 'dungeon'
@levelStatusMap = {} @levelStatusMap = {}
@levelPlayCountMap = {} @levelPlayCountMap = {}
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', {cache: false}, 0).model @sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', {cache: false}, 0).model
@ -160,6 +158,9 @@ module.exports = class CampaignView extends RootView
context = super(context) context = super(context)
context.campaign = @campaign context.campaign = @campaign
context.levels = _.values($.extend true, {}, @campaign?.get('levels') ? {}) context.levels = _.values($.extend true, {}, @campaign?.get('levels') ? {})
if me.level() < 12 and @terrain is 'dungeon' and not @editorMode
reject = if me.getFourthLevelGroup() is 'signs-and-portents' then 'forgetful-gemsmith' else 'signs-and-portents'
context.levels = _.reject context.levels, slug: reject
@annotateLevel level for level in context.levels @annotateLevel level for level in context.levels
count = @countLevels context.levels count = @countLevels context.levels
context.levelsCompleted = count.completed context.levelsCompleted = count.completed
@ -257,6 +258,7 @@ module.exports = class CampaignView extends RootView
level.locked = false if me.isInGodMode() level.locked = false if me.isInGodMode()
level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete'] level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete']
level.disabled = false if me.isInGodMode() level.disabled = false if me.isInGodMode()
level.locked = false
level.color = 'rgb(255, 80, 60)' level.color = 'rgb(255, 80, 60)'
if level.requiresSubscription if level.requiresSubscription
level.color = 'rgb(80, 130, 200)' level.color = 'rgb(80, 130, 200)'
@ -323,7 +325,7 @@ module.exports = class CampaignView extends RootView
@playAmbientSound() @playAmbientSound()
testParticles: -> testParticles: ->
return unless @campaign?.loaded and me.getForeshadowsLevels() return unless @campaign?.loaded and $.browser.chrome # Sometimes this breaks in non-Chrome browsers, according to A/B tests.
@particleMan ?= new ParticleMan() @particleMan ?= new ParticleMan()
@particleMan.removeEmitters() @particleMan.removeEmitters()
@particleMan.attach @$el.find('.map') @particleMan.attach @$el.find('.map')

View file

@ -150,11 +150,7 @@ module.exports = class HeroVictoryModal extends ModalView
# Show the "I'm done" button between 30 - 120 minutes if they definitely came from Hour of Code # Show the "I'm done" button between 30 - 120 minutes if they definitely came from Hour of Code
c.showHourOfCodeDoneButton = me.get('hourOfCode') and showDone c.showHourOfCodeDoneButton = me.get('hourOfCode') and showDone
if @level.get('scoreTypes')?.length c.showLeaderboard = @level.get('scoreTypes')?.length > 0
lg = me.getLeaderboardsGroup()
c.showLeaderboard = lg is 'always'
c.showLeaderboard = true if me.level() >= 3 and lg.group is 'early'
c.showLeaderboard = true if me.level() >= 5 and lg.group is 'late'
return c return c

View file

@ -563,9 +563,6 @@ module.exports = class InventoryModal extends ModalView
return @openModalView authModal return @openModalView authModal
askToBuyGems: (unlockButton) -> askToBuyGems: (unlockButton) ->
if me.getGemPromptGroup() is 'no-prompt'
return @askToSignUp() if me.get('anonymous')
return @openModalView new BuyGemsModal()
@$el.find('.unlock-button').popover 'destroy' @$el.find('.unlock-button').popover 'destroy'
popoverTemplate = buyGemsPromptTemplate {} popoverTemplate = buyGemsPromptTemplate {}
unlockButton.popover( unlockButton.popover(

View file

@ -65,8 +65,7 @@ module.exports = class BuyGemsModal extends ModalView
Backbone.Mediator.publish 'buy-gems-modal:purchase-initiated', { productID: productID } Backbone.Mediator.publish 'buy-gems-modal:purchase-initiated', { productID: productID }
else else
# TODO: rename this event to 'Started gem purchase' after gemPrompt A/B test is over application.tracker?.trackEvent 'Started gem purchase', { productID: productID }
application.tracker?.trackEvent 'Started purchase', { productID: productID }
stripeHandler.open({ stripeHandler.open({
description: $.t(product.i18n) description: $.t(product.i18n)
amount: product.amount amount: product.amount

View file

@ -265,9 +265,6 @@ module.exports = class PlayHeroesModal extends ModalView
return @openModalView authModal return @openModalView authModal
askToBuyGems: (unlockButton) -> askToBuyGems: (unlockButton) ->
if me.getGemPromptGroup() is 'no-prompt'
return @askToSignUp() if me.get('anonymous')
return @openModalView new BuyGemsModal()
@$el.find('.unlock-button').popover 'destroy' @$el.find('.unlock-button').popover 'destroy'
popoverTemplate = buyGemsPromptTemplate {} popoverTemplate = buyGemsPromptTemplate {}
unlockButton.popover( unlockButton.popover(

View file

@ -216,9 +216,6 @@ module.exports = class PlayItemsModal extends ModalView
return @openModalView authModal return @openModalView authModal
askToBuyGems: (unlockButton) -> askToBuyGems: (unlockButton) ->
if me.getGemPromptGroup() is 'no-prompt'
return @askToSignUp() if me.get('anonymous')
return @openModalView new BuyGemsModal()
@$el.find('.unlock-button').popover 'destroy' @$el.find('.unlock-button').popover 'destroy'
popoverTemplate = buyGemsPromptTemplate {} popoverTemplate = buyGemsPromptTemplate {}
unlockButton.popover( unlockButton.popover(

View file

@ -1,6 +1,8 @@
// foreshadowsLevels A/B Results // foreshadowsLevels A/B Results
// Test started 2015-01-29 // Test started 2015-01-29, ended 2015-02-26
// Fixed some particle problems on 2015-02-02 // Fixed some particle problems on 2015-02-02
// Fixed more particle problems in old browsers on 2015-02-19
// Final results: tests showed no difference in completion rates, but slight drops in level start rates for non-Chrome browsers, so we stopped doing it outside of Chrome.
// Usage: // Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password> // mongo <address>:<port>/<database> <script file> -u <username> -p <password>
@ -9,18 +11,18 @@ load('abTestHelpers.js');
var scriptStartTime = new Date(); var scriptStartTime = new Date();
try { try {
var startDay = '2015-02-03' var startDay = '2015-02-20';
log("Today is " + new Date().toISOString().substr(0, 10)); log("Today is " + new Date().toISOString().substr(0, 10));
log("Start day is " + startDay); log("Start day is " + startDay);
var eventFunnel = ['Started Level', 'Saw Victory']; var eventFunnel = ['Started Level', 'Saw Victory'];
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith']; var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith', 'kounter-kithwise', 'kithgard-gates', 'rich-forager', 'village-guard'];
// getForeshadowsLevels // getForeshadowsLevels
var testGroupFn = function (testGroupNumber) { var testGroupFn = function (testGroupNumber) {
var group = testGroupNumber % 16 var group = testGroupNumber % 16
return group >= 0 && group <= 7; return group >= 0 && group <= 7;
} };
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs); var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs);

View file

@ -1,16 +1,18 @@
// gemPromptGroup A/B Results // gemPromptGroup A/B Results
// Test started 2014-11-24 // Test started 2014-11-24, ended 2015-02-26
// Final results:
// no-prompt 3789 gem shop shown, 62 purchased
// prompt 2658 gem shop shown, 78 purchased
// Decided prompt was better, so now always prompt. (Yay being nice.)
// Usage: // Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password> // mongo <address>:<port>/<database> <script file> -u <username> -p <password>
// TODO: Why is no-prompt group 50% larger?
load('abTestHelpers.js'); load('abTestHelpers.js');
var scriptStartTime = new Date(); var scriptStartTime = new Date();
try { try {
var startDay = '2014-11-24' var startDay = '2014-11-24';
// startDay = '2015-01-15' // startDay = '2015-01-15'
log("Today is " + new Date().toISOString().substr(0, 10)); log("Today is " + new Date().toISOString().substr(0, 10));
log("Start day is " + startDay); log("Start day is " + startDay);
@ -21,7 +23,7 @@ try {
var testGroupFn = function (testGroupNumber) { var testGroupFn = function (testGroupNumber) {
var group = testGroupNumber % 8 var group = testGroupNumber % 8
return group >= 0 && group <= 3 ? 'prompt' : 'no-prompt'; return group >= 0 && group <= 3 ? 'prompt' : 'no-prompt';
} };
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn); var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn);

View file

@ -1,5 +1,6 @@
// leaderboardsGroup A/B Results // leaderboardsGroup A/B Results
// Test started 2015-01-30 // Test started 2015-01-30, ended 2015-02-26
// Final results: no differences in level starts or completions. Perhaps they affect playtime or purchases, but harder to say. At least they don't hurt, so wejust turned them on for everyone always.
// Usage: // Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password> // mongo <address>:<port>/<database> <script file> -u <username> -p <password>
@ -8,12 +9,12 @@ load('abTestHelpers.js');
var scriptStartTime = new Date(); var scriptStartTime = new Date();
try { try {
var startDay = '2015-01-30' var startDay = '2015-02-07';
log("Today is " + new Date().toISOString().substr(0, 10)); log("Today is " + new Date().toISOString().substr(0, 10));
log("Start day is " + startDay); log("Start day is " + startDay);
var eventFunnel = ['Started Level', 'Saw Victory']; var eventFunnel = ['Started Level', 'Saw Victory'];
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith']; var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith', 'kounter-kithwise', 'kithgard-gates', 'rich-forager', 'village-guard'];
// getLeaderboardsGroup // getLeaderboardsGroup
var testGroupFn = function (testGroupNumber) { var testGroupFn = function (testGroupNumber) {
@ -22,7 +23,7 @@ try {
if (group < 32) return 'early'; if (group < 32) return 'early';
if (group < 48) return 'late'; if (group < 48) return 'late';
return 'never'; return 'never';
} };
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs); var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs);

View file

@ -1,5 +1,25 @@
// showsPortal A/B Results // showsPortal A/B Results
// Test started 2015-02-05 // Test started 2015-02-05, ended 2015-02-26
// Final results: seems to help people get to the later levels, and at least definitely doesn't hurt:
// dungeons-of-kithgard false 64605 49956 77.33
// dungeons-of-kithgard true 65380 50590 77.38
// gems-in-the-deep false 45339 40788 89.96
// gems-in-the-deep true 45922 41455 90.27
// kithgard-gates false 7415 6904 93.11
// kithgard-gates true 7783 7249 93.14
// kounter-kithwise true 95 92 96.84
// kounter-kithwise false 86 77 89.53
// rich-forager false 1067 822 77.04
// rich-forager true 1111 834 75.07
// shadow-guard false 38089 35239 92.52
// shadow-guard true 38774 35975 92.78
// the-mighty-sand-yak true 505 400 79.21
// the-mighty-sand-yak false 425 329 77.41
//
// Group totals:
// false 157026 134115 85.41
// true 159570 136595 85.60
// Usage: // Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password> // mongo <address>:<port>/<database> <script file> -u <username> -p <password>
@ -13,7 +33,7 @@ try {
log("Start day is " + startDay); log("Start day is " + startDay);
var eventFunnel = ['Started Level', 'Saw Victory']; var eventFunnel = ['Started Level', 'Saw Victory'];
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith']; var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'kounter-kithwise', 'kithgard-gates', 'rich-forager', 'the-mighty-sand-yak'];
// getShowsPortal // getShowsPortal
var testGroupFn = function (testGroupNumber) { var testGroupFn = function (testGroupNumber) {

View file

@ -345,11 +345,11 @@ LevelHandler = class LevelHandler extends Handler
@sendSuccess res, data @sendSuccess res, data
hasAccessToDocument: (req, document, method=null) -> hasAccessToDocument: (req, document, method=null) ->
return true if req.user?.isArtisan()
method ?= req.method method ?= req.method
return true if method is null or method is 'get' return true if method is null or method is 'get'
super(req, document, method) super(req, document, method)
getLevelPlaytimesBySlugs: (req, res) -> getLevelPlaytimesBySlugs: (req, res) ->
# Returns an array of per-day level average playtimes # Returns an array of per-day level average playtimes
# Parameters: # Parameters: