Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-01-16 18:47:45 -08:00
commit 74ab92012f
8 changed files with 159 additions and 109 deletions

View file

@ -81,7 +81,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
awaiting_levels_adventurer_prefix: "Nós adicionamos cinco níveis por semana." awaiting_levels_adventurer_prefix: "Nós adicionamos cinco níveis por semana."
awaiting_levels_adventurer: "Regista-te como Aventureiro" awaiting_levels_adventurer: "Regista-te como Aventureiro"
awaiting_levels_adventurer_suffix: "para seres o primeiro a jogar níveis novos." awaiting_levels_adventurer_suffix: "para seres o primeiro a jogar níveis novos."
# adjust_volume: "Adjust volume" adjust_volume: "Ajustar volume"
choose_your_level: "Escolhe o Teu Nível" # The rest of this section is the old play view at /play-old and isn't very important. choose_your_level: "Escolhe o Teu Nível" # The rest of this section is the old play view at /play-old and isn't very important.
adventurer_prefix: "Podes saltar para um dos níveis abaixo ou discutir os níveis no " adventurer_prefix: "Podes saltar para um dos níveis abaixo ou discutir os níveis no "
adventurer_forum: "fórum do Aventureiro" adventurer_forum: "fórum do Aventureiro"
@ -480,7 +480,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
versions: versions:
save_version_title: "Guardar Nova Versão" save_version_title: "Guardar Nova Versão"
new_major_version: "Nova Versão Principal" new_major_version: "Nova Versão Principal"
# submitting_patch: "Submitting Patch..." submitting_patch: "A Submeter Atualização..."
cla_prefix: "Para guardares as alterações, precisas de concordar com o nosso" cla_prefix: "Para guardares as alterações, precisas de concordar com o nosso"
cla_url: "CLA" cla_url: "CLA"
cla_suffix: "." cla_suffix: "."
@ -636,8 +636,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
level_tab_thangs_all: "Todos" level_tab_thangs_all: "Todos"
level_tab_thangs_conditions: "Condições Iniciais" level_tab_thangs_conditions: "Condições Iniciais"
level_tab_thangs_add: "Adicionar Thangs" level_tab_thangs_add: "Adicionar Thangs"
# add_components: "Add Components" add_components: "Adicionar Componentes"
# component_configs: "Component Configurations" component_configs: "Configurações dos Componentes"
config_thang: "Clica duas vezes para configurares uma thang" config_thang: "Clica duas vezes para configurares uma thang"
delete: "Eliminar" delete: "Eliminar"
duplicate: "Duplicar" duplicate: "Duplicar"
@ -885,7 +885,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
leaderboard: "Tabela de Classificação" leaderboard: "Tabela de Classificação"
user_schema: "Esquema do Utilizador" user_schema: "Esquema do Utilizador"
user_profile: "Perfil do Utilizador" user_profile: "Perfil do Utilizador"
# patch: "Patch" patch: "Atualização"
patches: "Atualizações" patches: "Atualizações"
patched_model: "Documento Fonte" patched_model: "Documento Fonte"
model: "Modelo" model: "Modelo"

View file

@ -25,4 +25,4 @@
strong(data-i18n="loading_error.unknown") Unknown error. strong(data-i18n="loading_error.unknown") Unknown error.
button.btn.btn-xs.retry-loading-resource(data-i18n="common.retry", data-resource-index=resourceIndex) Retry button.btn.btn-xs.retry-loading-resource(data-i18n="common.retry", data-resource-index=resourceIndex) Retry
button.btn.btn-xs.skip-loading-resource(data-i18n="common.skip", data-resource-index=resourceIndex) Skip button.btn.btn-xs.skip-loading-resource(data-i18n="play_level.skip", data-resource-index=resourceIndex) Skip

View file

@ -0,0 +1,37 @@
extends /templates/core/modal-base
block modal-header-content
h3 Campaign Analytics
if campaignCompletions.startDay && campaignCompletions.endDay
.input-group.input-group-sm
input.form-control#input-startday(type='text', style='width:100px;', value=campaignCompletions.startDay)
input.form-control#input-endday(type='text', style='width:100px;', value=campaignCompletions.endDay)
button.btn.btn-default.btn-sm#reload-button(style='margin-left:10px;') Reload
block modal-body-content
if campaignCompletions && campaignCompletions.levels
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
thead
tr
td Level
td Started
td Finished
td Playtime (s)
td Completion %
tbody
- for (var i = 0; i < campaignCompletions.levels.length; i++)
tr
td= campaignCompletions.levels[i].level
td= campaignCompletions.levels[i].started
td= campaignCompletions.levels[i].finished
td= campaignCompletions.levels[i].averagePlaytime
if campaignCompletions.top3.indexOf(campaignCompletions.levels[i].level) >= 0
td(style='background-color:lightblue;')= campaignCompletions.levels[i].completionRate
else if campaignCompletions.bottom3.indexOf(campaignCompletions.levels[i].level) >= 0
td(style='background-color:pink;')= campaignCompletions.levels[i].completionRate
else
td= campaignCompletions.levels[i].completionRate
else
div Loading...
block modal-footer

View file

@ -16,6 +16,9 @@ block header
span.glyphicon-home.glyphicon span.glyphicon-home.glyphicon
ul.nav.navbar-nav.navbar-right ul.nav.navbar-nav.navbar-right
li#analytics-button
a
span.glyphicon-stats.glyphicon
if me.isAdmin() if me.isAdmin()
li#save-button li#save-button
a a
@ -40,43 +43,5 @@ block outer_content
#right-column #right-column
#campaign-view #campaign-view
#campaign-level-view.hidden #campaign-level-view.hidden
if campaignCompletions
button.btn.btn-default#analytics-button(title="Analytics", data-toggle="modal" data-target="#analytics-modal") Analytics
.modal.fade#analytics-modal(tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true")
.modal-dialog.modal-lg
.modal-content
.modal-header
button.close(type="button", data-dismiss="modal", aria-label="Close")
span(aria-hidden="true") &times;
h4.modal-title Analytics
.modal-body
if campaignCompletions.startDay
if campaignCompletions.endDay
div #{campaignCompletions.startDay} to #{campaignCompletions.endDay}
else
div #{campaignCompletions.startDay} to yesterday
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
thead
tr
td Level
td Started
td Finished
td Playtime (s)
td Completion %
tbody
- for (var i = 0; i < campaignCompletions.levels.length; i++)
tr
td= campaignCompletions.levels[i].level
td= campaignCompletions.levels[i].started
td= campaignCompletions.levels[i].finished
td= campaignCompletions.levels[i].averagePlaytime
if campaignCompletions.top3.indexOf(campaignCompletions.levels[i].level) >= 0
td(style='background-color:lightblue;')= campaignCompletions.levels[i].completionRate
else if campaignCompletions.bottom3.indexOf(campaignCompletions.levels[i].level) >= 0
td(style='background-color:pink;')= campaignCompletions.levels[i].completionRate
else
td= campaignCompletions.levels[i].completionRate
else
button.btn.btn-default.disabled#analytics-button Analytics Loading...
block footer block footer

View file

@ -1,7 +1,7 @@
extends /templates/core/modal-base extends /templates/core/modal-base
block modal-header-content block modal-header-content
h1 Add Components h1(data-i18n="editor.add_components") Add Components
block modal-body-content block modal-body-content
form form

View file

@ -0,0 +1,101 @@
template = require 'templates/editor/campaign/campaign-analytics-modal'
utils = require 'core/utils'
ModalView = require 'views/core/ModalView'
# TODO: jquery-ui datepicker doesn't work well in this view
# TODO: the date format handling is confusing (yyyy-mm-dd <=> yyyymmdd)
module.exports = class CampaignAnalyticsModal extends ModalView
id: 'campaign-analytics-modal'
template: template
plain: true
events:
'click #reload-button': 'onClickReloadButton'
constructor: (options, @campaignHandle, @campaignCompletions) ->
super options
@getCampaignAnalytics() unless @campaignCompletions?.levels?
getRenderData: ->
c = super()
c.campaignCompletions = @campaignCompletions
c
afterRender: ->
super()
$("#input-startday").datepicker dateFormat: "yy-mm-dd"
$("#input-endday").datepicker dateFormat: "yy-mm-dd"
onClickReloadButton: () =>
startDay = $('#input-startday').val()
endDay = $('#input-endday').val()
delete @campaignCompletions.levels
@render()
@getCampaignAnalytics startDay, endDay
getCampaignAnalytics: (startDay, endDay) =>
# Fetch campaign analytics, unless dates given
startDay = startDay.replace(/-/g, '') if startDay?
endDay = endDay.replace(/-/g, '') if endDay?
startDay ?= utils.getUTCDay -14
endDay ?= utils.getUTCDay -1
success = (data) =>
return if @destroyed
mapFn = (item) ->
item.completionRate = (item.finished / item.started * 100).toFixed(2)
item
@campaignCompletions.levels = _.map data, mapFn, @
sortedLevels = _.cloneDeep @campaignCompletions.levels
sortedLevels = _.filter sortedLevels, ((a) -> a.finished >= 10), @
sortedLevels.sort (a, b) -> b.completionRate - a.completionRate
@campaignCompletions.top3 = _.pluck sortedLevels[0..2], 'level'
sortedLevels.sort (a, b) -> a.completionRate - b.completionRate
@campaignCompletions.bottom3 = _.pluck sortedLevels[0..2], 'level'
@campaignCompletions.startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
@campaignCompletions.endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
@getCampaignAveragePlaytimes startDay, endDay
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'campaign_completions', {
url: '/db/analytics_perday/-/campaign_completions'
data: {startDay: startDay, endDay: endDay, slug: @campaignHandle}
method: 'POST'
success: success
}, 0
request.load()
getCampaignAveragePlaytimes: (startDay, endDay) =>
# Fetch level average playtimes
success = (data) =>
return if @destroyed
levelAverages = {}
for item in data
levelAverages[item.level] ?= []
levelAverages[item.level].push item.average
for level in @campaignCompletions.levels
if levelAverages[level.level]
if levelAverages[level.level].length > 0
total = _.reduce levelAverages[level.level], ((sum, num) -> sum + num)
level.averagePlaytime = (total / levelAverages[level.level].length).toFixed(2)
else
level.averagePlaytime = 0.0
@render()
startDay ?= utils.getUTCDay -14
startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
endDay ?= utils.getUTCDay -1
endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
levelSlugs = _.pluck @campaignCompletions.levels, 'level'
request = @supermodel.addRequestResource 'playtime_averages', {
url: '/db/level/-/playtime_averages'
data: {startDay: startDay, endDay: endDay, slugs: levelSlugs}
method: 'POST'
success: success
}, 0
request.load()

View file

@ -7,9 +7,10 @@ CampaignView = require 'views/play/CampaignView'
CocoCollection = require 'collections/CocoCollection' CocoCollection = require 'collections/CocoCollection'
treemaExt = require 'core/treema-ext' treemaExt = require 'core/treema-ext'
utils = require 'core/utils' utils = require 'core/utils'
SaveCampaignModal = require './SaveCampaignModal'
RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection' RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection'
CampaignAnalyticsModal = require './CampaignAnalyticsModal'
CampaignLevelView = require './CampaignLevelView' CampaignLevelView = require './CampaignLevelView'
SaveCampaignModal = require './SaveCampaignModal'
achievementProject = ['related', 'rewards', 'name', 'slug'] achievementProject = ['related', 'rewards', 'name', 'slug']
thangTypeProject = ['name', 'original'] thangTypeProject = ['name', 'original']
@ -20,6 +21,7 @@ module.exports = class CampaignEditorView extends RootView
className: 'editor' className: 'editor'
events: events:
'click #analytics-button': 'onClickAnalyticsButton'
'click #save-button': 'onClickSaveButton' 'click #save-button': 'onClickSaveButton'
constructor: (options, @campaignHandle) -> constructor: (options, @campaignHandle) ->
@ -27,6 +29,9 @@ module.exports = class CampaignEditorView extends RootView
@campaign = new Campaign({_id:@campaignHandle}) @campaign = new Campaign({_id:@campaignHandle})
@supermodel.loadModel(@campaign, 'campaign') @supermodel.loadModel(@campaign, 'campaign')
# Save reference to data used by anlytics modal so it persists across modal open/closes.
@campaignAnalytics = {}
@levels = new CocoCollection([], { @levels = new CocoCollection([], {
model: Level model: Level
url: "/db/campaign/#{@campaignHandle}/levels" url: "/db/campaign/#{@campaignHandle}/levels"
@ -47,8 +52,6 @@ module.exports = class CampaignEditorView extends RootView
@listenToOnce @levels, 'sync', @onFundamentalLoaded @listenToOnce @levels, 'sync', @onFundamentalLoaded
@listenToOnce @achievements, 'sync', @onFundamentalLoaded @listenToOnce @achievements, 'sync', @onFundamentalLoaded
_.delay @getCampaignAnalytics, 500
loadThangTypeNames: -> loadThangTypeNames: ->
# Load the names of the ThangTypes that this level's Treema nodes might want to display. # Load the names of the ThangTypes that this level's Treema nodes might want to display.
originals = [] originals = []
@ -132,9 +135,11 @@ module.exports = class CampaignEditorView extends RootView
getRenderData: -> getRenderData: ->
c = super() c = super()
c.campaign = @campaign c.campaign = @campaign
c.campaignCompletions = @campaignCompletions
c c
onClickAnalyticsButton: ->
@openModalView new CampaignAnalyticsModal {}, @campaignHandle, @campaignAnalytics
onClickSaveButton: -> onClickSaveButton: ->
@toSave.set @toSave.filter (m) -> m.hasLocalChanges() @toSave.set @toSave.filter (m) -> m.hasLocalChanges()
@openModalView new SaveCampaignModal({}, @toSave) @openModalView new SaveCampaignModal({}, @toSave)
@ -240,65 +245,6 @@ module.exports = class CampaignEditorView extends RootView
if achievement.hasLocalChanges() if achievement.hasLocalChanges()
@toSave.add achievement @toSave.add achievement
getCampaignAnalytics: =>
# Fetch last 14 days of campaign analytics
startDay = utils.getUTCDay -14
endDay = utils.getUTCDay -1
success = (data) =>
return if @destroyed
mapFn = (item) ->
item.completionRate = (item.finished / item.started * 100).toFixed(2)
item
@campaignCompletions = levels: _.map data, mapFn, @
sortedLevels = _.cloneDeep @campaignCompletions.levels
sortedLevels = _.filter sortedLevels, ((a) -> a.completionRate > 1.0), @
sortedLevels.sort (a, b) -> b.completionRate - a.completionRate
@campaignCompletions.top3 = _.pluck sortedLevels[0..2], 'level'
sortedLevels.sort (a, b) -> a.completionRate - b.completionRate
@campaignCompletions.bottom3 = _.pluck sortedLevels[0..2], 'level'
@campaignCompletions.startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
@campaignCompletions.endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
@getCampaignAveragePlaytimes()
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'campaign_completions', {
url: '/db/analytics_perday/-/campaign_completions'
data: {startDay: startDay, slug: @campaignHandle}
method: 'POST'
success: success
}, 0
request.load()
getCampaignAveragePlaytimes: =>
# Fetch last 14 days of level average playtimes
success = (data) =>
return if @destroyed
levelAverages = {}
for item in data
levelAverages[item.level] ?= []
levelAverages[item.level].push item.average
for level in @campaignCompletions.levels
if levelAverages[level.level]
if levelAverages[level.level].length > 0
total = _.reduce levelAverages[level.level], ((sum, num) -> sum + num)
level.averagePlaytime = (total / levelAverages[level.level].length).toFixed(2)
else
level.averagePlaytime = 0.0
@render()
levelSlugs = _.pluck @campaignCompletions.levels, 'level'
startDay = utils.getUTCDay -14
startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
request = @supermodel.addRequestResource 'playtime_averages', {
url: '/db/level/-/playtime_averages'
data: {startDay: startDay, slugs: levelSlugs}
method: 'POST'
success: success
}, 0
request.load()
class LevelsNode extends TreemaObjectNode class LevelsNode extends TreemaObjectNode
valueClass: 'treema-levels' valueClass: 'treema-levels'

View file

@ -151,7 +151,8 @@ module.exports = class Spell
writable = @permissions.readwrite.length > 0 writable = @permissions.readwrite.length > 0
skipProtectAPI = @skipProtectAPI or not writable skipProtectAPI = @skipProtectAPI or not writable
problemContext = @createProblemContext thang problemContext = @createProblemContext thang
aetherOptions = createAetherOptions functionName: @name, codeLanguage: @language, functionParameters: @parameters, skipProtectAPI: skipProtectAPI, includeFlow: @levelType in ['hero', 'hero-ladder', 'hero-coop'], problemContext: problemContext includeFlow = (@levelType in ['hero', 'hero-ladder', 'hero-coop']) and not skipProtectAPI
aetherOptions = createAetherOptions functionName: @name, codeLanguage: @language, functionParameters: @parameters, skipProtectAPI: skipProtectAPI, includeFlow: includeFlow, problemContext: problemContext
aether = new Aether aetherOptions aether = new Aether aetherOptions
if @worker if @worker
workerMessage = workerMessage =