mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-04 21:01:06 -05:00
235 lines
8.6 KiB
CoffeeScript
235 lines
8.6 KiB
CoffeeScript
{backboneFailure, genericFailure} = require 'core/errors'
|
|
errors = require 'core/errors'
|
|
RootView = require 'views/core/RootView'
|
|
template = require 'templates/admin'
|
|
AdministerUserModal = require 'views/admin/AdministerUserModal'
|
|
forms = require 'core/forms'
|
|
|
|
Campaigns = require 'collections/Campaigns'
|
|
Classroom = require 'models/Classroom'
|
|
CocoCollection = require 'collections/CocoCollection'
|
|
Course = require 'models/Course'
|
|
Courses = require 'collections/Courses'
|
|
LevelSessions = require 'collections/LevelSessions'
|
|
User = require 'models/User'
|
|
Users = require 'collections/Users'
|
|
|
|
module.exports = class MainAdminView extends RootView
|
|
id: 'admin-view'
|
|
template: template
|
|
lastUserSearchValue: ''
|
|
|
|
events:
|
|
'submit #espionage-form': 'onSubmitEspionageForm'
|
|
'submit #user-search-form': 'onSubmitUserSearchForm'
|
|
'click #stop-spying-btn': 'onClickStopSpyingButton'
|
|
'click #increment-button': 'incrementUserAttribute'
|
|
'click .user-spy-button': 'onClickUserSpyButton'
|
|
'click #user-search-result': 'onClickUserSearchResult'
|
|
'click #create-free-sub-btn': 'onClickFreeSubLink'
|
|
'click #terminal-create': 'onClickTerminalSubLink'
|
|
'click .classroom-progress-csv': 'onClickExportProgress'
|
|
|
|
getTitle: -> return $.i18n.t('account_settings.admin')
|
|
|
|
initialize: ->
|
|
if window.amActually
|
|
@amActually = new User({_id: window.amActually})
|
|
@amActually.fetch()
|
|
@supermodel.trackModel(@amActually)
|
|
super()
|
|
|
|
onClickStopSpyingButton: ->
|
|
button = @$('#stop-spying-btn')
|
|
forms.disableSubmit(button)
|
|
me.stopSpying({
|
|
success: -> document.location.reload()
|
|
error: ->
|
|
forms.enableSubmit(button)
|
|
errors.showNotyNetworkError(arguments...)
|
|
})
|
|
|
|
onSubmitEspionageForm: (e) ->
|
|
e.preventDefault()
|
|
button = @$('#enter-espionage-mode')
|
|
userNameOrEmail = @$el.find('#espionage-name-or-email').val().toLowerCase()
|
|
forms.disableSubmit(button)
|
|
me.spy(userNameOrEmail, {
|
|
success: -> window.location.reload()
|
|
error: ->
|
|
forms.enableSubmit(button)
|
|
errors.showNotyNetworkError(arguments...)
|
|
})
|
|
|
|
onClickUserSpyButton: (e) ->
|
|
e.stopPropagation()
|
|
userID = $(e.target).closest('tr').data('user-id')
|
|
button = $(e.currentTarget)
|
|
forms.disableSubmit(button)
|
|
me.spy(userID, {
|
|
success: -> window.location.reload()
|
|
error: ->
|
|
forms.enableSubmit(button)
|
|
errors.showNotyNetworkError(arguments...)
|
|
})
|
|
|
|
onSubmitUserSearchForm: (e) ->
|
|
e.preventDefault()
|
|
searchValue = @$el.find('#user-search').val()
|
|
return if searchValue is @lastUserSearchValue
|
|
return @onSearchRequestSuccess [] unless @lastUserSearchValue = searchValue.toLowerCase()
|
|
forms.disableSubmit(@$('#user-search-button'))
|
|
$.ajax
|
|
type: 'POST',
|
|
url: '/db/user/-/admin_search'
|
|
data: {search: @lastUserSearchValue}
|
|
success: @onSearchRequestSuccess
|
|
error: @onSearchRequestFailure
|
|
|
|
onSearchRequestSuccess: (users) =>
|
|
forms.enableSubmit(@$('#user-search-button'))
|
|
result = ''
|
|
if users.length
|
|
result = ("<tr data-user-id='#{user._id}'><td><code>#{user._id}</code></td><td>#{_.escape(user.name or 'Anonymous')}</td><td>#{_.escape(user.email)}</td><td><button class='user-spy-button'>Spy</button></td></tr>" for user in users)
|
|
result = "<table class=\"table\">#{result.join('\n')}</table>"
|
|
@$el.find('#user-search-result').html(result)
|
|
|
|
onSearchRequestFailure: (jqxhr, status, error) =>
|
|
return if @destroyed
|
|
forms.enableSubmit(@$('#user-search-button'))
|
|
console.warn "There was an error looking up #{@lastUserSearchValue}:", error
|
|
|
|
incrementUserAttribute: (e) ->
|
|
val = $('#increment-field').val()
|
|
me.set(val, me.get(val) + 1)
|
|
me.save()
|
|
|
|
onClickUserSearchResult: (e) ->
|
|
userID = $(e.target).closest('tr').data('user-id')
|
|
@openModalView new AdministerUserModal({}, userID) if userID
|
|
|
|
onClickFreeSubLink: (e) =>
|
|
delete @freeSubLink
|
|
return unless me.isAdmin()
|
|
options =
|
|
url: '/db/prepaid/-/create'
|
|
data: {type: 'subscription', maxRedeemers: 1}
|
|
method: 'POST'
|
|
options.success = (model, response, options) =>
|
|
# TODO: Don't hardcode domain.
|
|
if application.isProduction()
|
|
@freeSubLink = "https://codecombat.com/account/subscription?_ppc=#{model.code}"
|
|
else
|
|
@freeSubLink = "http://localhost:3000/account/subscription?_ppc=#{model.code}"
|
|
@render?()
|
|
options.error = (model, response, options) =>
|
|
console.error 'Failed to create prepaid', response
|
|
@supermodel.addRequestResource('create_prepaid', options, 0).load()
|
|
|
|
onClickTerminalSubLink: (e) =>
|
|
@freeSubLink = ''
|
|
return unless me.isAdmin()
|
|
|
|
options =
|
|
url: '/db/prepaid/-/create'
|
|
method: 'POST'
|
|
data:
|
|
type: 'terminal_subscription'
|
|
maxRedeemers: parseInt($("#users").val())
|
|
months: parseInt($("#months").val())
|
|
|
|
options.success = (model, response, options) =>
|
|
# TODO: Don't hardcode domain.
|
|
if application.isProduction()
|
|
@freeSubLink = "https://codecombat.com/account/prepaid?_ppc=#{model.code}"
|
|
else
|
|
@freeSubLink = "http://localhost:3000/account/prepaid?_ppc=#{model.code}"
|
|
@render?()
|
|
options.error = (model, response, options) =>
|
|
console.error 'Failed to create prepaid', response
|
|
@supermodel.addRequestResource('create_prepaid', options, 0).load()
|
|
|
|
onClickExportProgress: ->
|
|
$('.classroom-progress-csv').prop('disabled', true)
|
|
classCode = $('.classroom-progress-class-code').val()
|
|
classroom = null
|
|
courses = null
|
|
courseLevels = []
|
|
sessions = null
|
|
users = null
|
|
userMap = {}
|
|
Promise.resolve(new Classroom().fetchByCode(classCode))
|
|
.then (model) =>
|
|
classroom = new Classroom({ _id: model.data._id })
|
|
Promise.resolve(classroom.fetch())
|
|
.then (model) =>
|
|
courses = new Courses()
|
|
Promise.resolve(courses.fetch())
|
|
.then (models) =>
|
|
for course, index in classroom.get('courses')
|
|
for level in course.levels
|
|
courseLevels.push
|
|
courseIndex: index + 1
|
|
levelID: level.original
|
|
slug: level.slug
|
|
courseSlug: courses.get(course._id).get('slug')
|
|
users = new Users()
|
|
Promise.resolve($.when(users.fetchForClassroom(classroom)...))
|
|
.then (models) =>
|
|
userMap[user.id] = user for user in users.models
|
|
sessions = new LevelSessions()
|
|
Promise.resolve($.when(sessions.fetchForAllClassroomMembers(classroom)...))
|
|
.then (models) =>
|
|
userLevelPlaytimeMap = {}
|
|
for session in sessions.models
|
|
continue unless session.get('state')?.complete
|
|
levelID = session.get('level').original
|
|
userID = session.get('creator')
|
|
userLevelPlaytimeMap[userID] ?= {}
|
|
userLevelPlaytimeMap[userID][levelID] ?= {}
|
|
userLevelPlaytimeMap[userID][levelID] = session.get('playtime')
|
|
|
|
userPlaytimes = []
|
|
for userID, user of userMap
|
|
playtimes = [user.get('name') ? 'Anonymous']
|
|
for level in courseLevels
|
|
if userLevelPlaytimeMap[userID]?[level.levelID]?
|
|
rawSeconds = parseInt(userLevelPlaytimeMap[userID][level.levelID])
|
|
hours = Math.floor(rawSeconds / 60 / 60)
|
|
minutes = Math.floor(rawSeconds / 60 - hours * 60)
|
|
seconds = Math.round(rawSeconds - hours * 60 - minutes * 60)
|
|
hours = "0#{hours}" if hours < 10
|
|
minutes = "0#{minutes}" if minutes < 10
|
|
seconds = "0#{seconds}" if seconds < 10
|
|
playtimes.push "#{hours}:#{minutes}:#{seconds}"
|
|
else
|
|
playtimes.push 'Incomplete'
|
|
userPlaytimes.push(playtimes)
|
|
|
|
columnLabels = "Username"
|
|
currentLevel = 1
|
|
courseLabelIndexes = CS: 1, GD: 0, WD: 0
|
|
lastCourseIndex = 1
|
|
lastCourseLabel = 'CS1'
|
|
for level in courseLevels
|
|
unless level.courseIndex is lastCourseIndex
|
|
currentLevel = 1
|
|
lastCourseIndex = level.courseIndex
|
|
acronym = switch
|
|
when /game-dev/.test(level.courseSlug) then 'GD'
|
|
when /web-dev/.test(level.courseSlug) then 'WD'
|
|
else 'CS'
|
|
lastCourseLabel = acronym + ++courseLabelIndexes[acronym]
|
|
columnLabels += ",#{lastCourseLabel}.#{currentLevel++} #{level.slug}"
|
|
csvContent = "data:text/csv;charset=utf-8,#{columnLabels}\n"
|
|
for studentRow in userPlaytimes
|
|
csvContent += studentRow.join(',') + "\n"
|
|
csvContent = csvContent.substring(0, csvContent.length - 1)
|
|
encodedUri = encodeURI(csvContent)
|
|
window.open(encodedUri)
|
|
$('.classroom-progress-csv').prop('disabled', false)
|
|
|
|
.catch (error) ->
|
|
$('.classroom-progress-csv').prop('disabled', false)
|
|
console.error error
|
|
throw error
|