mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-05-03 09:23:41 -04:00
Instrument user code problems
This commit is contained in:
parent
719e64e85f
commit
9b79e2ca27
11 changed files with 185 additions and 4 deletions
app
models
schemas/models
views/play/level/tome
server
commons
routes
user_code_problems
test/app/views/play/level/tome
6
app/models/UserCodeProblem.coffee
Normal file
6
app/models/UserCodeProblem.coffee
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
CocoModel = require './CocoModel'
|
||||||
|
|
||||||
|
module.exports = class UserCodeProblem extends CocoModel
|
||||||
|
@className: 'UserCodeProblem'
|
||||||
|
@schema: require 'schemas/models/user_code_problem'
|
||||||
|
urlRoot: '/db/user.code.problem'
|
25
app/schemas/models/user_code_problem.coffee
Normal file
25
app/schemas/models/user_code_problem.coffee
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
c = require './../schemas'
|
||||||
|
|
||||||
|
UserCodeProblemSchema = c.object {
|
||||||
|
title: 'User Code Problem'
|
||||||
|
description: 'Data for a problem in user code.'
|
||||||
|
}
|
||||||
|
|
||||||
|
_.extend UserCodeProblemSchema.properties,
|
||||||
|
creator: c.objectId(links: [{rel: 'extra', href: '/db/user/{($)}'}])
|
||||||
|
created: c.date({title: 'Created', readOnly: true})
|
||||||
|
|
||||||
|
code: String
|
||||||
|
codeSnippet: String
|
||||||
|
errHint: String
|
||||||
|
errId: String
|
||||||
|
errLevel: String
|
||||||
|
errMessage: String
|
||||||
|
errRange: []
|
||||||
|
errType: String
|
||||||
|
language: String
|
||||||
|
levelID: String
|
||||||
|
|
||||||
|
c.extendBasicProperties UserCodeProblemSchema, 'user.code.problem'
|
||||||
|
|
||||||
|
module.exports = UserCodeProblemSchema
|
|
@ -1,20 +1,23 @@
|
||||||
ProblemAlertView = require './ProblemAlertView'
|
ProblemAlertView = require './ProblemAlertView'
|
||||||
Range = ace.require('ace/range').Range
|
Range = ace.require('ace/range').Range
|
||||||
|
UserCodeProblem = require 'models/UserCodeProblem'
|
||||||
|
|
||||||
module.exports = class Problem
|
module.exports = class Problem
|
||||||
annotation: null
|
annotation: null
|
||||||
alertView: null
|
alertView: null
|
||||||
markerRange: null
|
markerRange: null
|
||||||
constructor: (@aether, @aetherProblem, @ace, withAlert=false, withRange=false) ->
|
constructor: (@aether, @aetherProblem, @ace, withAlert=false, isCast=false, @levelID) ->
|
||||||
@buildAnnotation()
|
@buildAnnotation()
|
||||||
@buildAlertView() if withAlert
|
@buildAlertView() if withAlert
|
||||||
@buildMarkerRange() if withRange
|
@buildMarkerRange() if isCast
|
||||||
|
@saveUserCodeProblem() if isCast
|
||||||
|
|
||||||
destroy: ->
|
destroy: ->
|
||||||
unless @alertView?.destroyed
|
unless @alertView?.destroyed
|
||||||
@alertView?.$el?.remove()
|
@alertView?.$el?.remove()
|
||||||
@alertView?.destroy()
|
@alertView?.destroy()
|
||||||
@removeMarkerRange()
|
@removeMarkerRange()
|
||||||
|
@userCodeProblem.off() if @userCodeProblem
|
||||||
|
|
||||||
buildAnnotation: ->
|
buildAnnotation: ->
|
||||||
return unless @aetherProblem.range
|
return unless @aetherProblem.range
|
||||||
|
@ -46,3 +49,21 @@ module.exports = class Problem
|
||||||
@ace.getSession().removeMarker @markerRange.id
|
@ace.getSession().removeMarker @markerRange.id
|
||||||
@markerRange.start.detach()
|
@markerRange.start.detach()
|
||||||
@markerRange.end.detach()
|
@markerRange.end.detach()
|
||||||
|
|
||||||
|
saveUserCodeProblem: () ->
|
||||||
|
@userCodeProblem = new UserCodeProblem()
|
||||||
|
@userCodeProblem.set 'code', @aether.raw
|
||||||
|
if @aetherProblem.range
|
||||||
|
rawLines = @aether.raw.split '\n'
|
||||||
|
errorLines = rawLines.slice @aetherProblem.range[0].row, @aetherProblem.range[1].row + 1
|
||||||
|
@userCodeProblem.set 'codeSnippet', errorLines.join '\n'
|
||||||
|
@userCodeProblem.set 'errHint', @aetherProblem.hint if @aetherProblem.hint
|
||||||
|
@userCodeProblem.set 'errId', @aetherProblem.id if @aetherProblem.id
|
||||||
|
@userCodeProblem.set 'errLevel', @aetherProblem.level if @aetherProblem.level
|
||||||
|
@userCodeProblem.set 'errMessage', @aetherProblem.message if @aetherProblem.message
|
||||||
|
@userCodeProblem.set 'errRange', @aetherProblem.range if @aetherProblem.range
|
||||||
|
@userCodeProblem.set 'errType', @aetherProblem.type if @aetherProblem.type
|
||||||
|
@userCodeProblem.set 'language', @aether.language.id if @aether.language?.id
|
||||||
|
@userCodeProblem.set 'levelID', @levelID if @levelID
|
||||||
|
@userCodeProblem.save()
|
||||||
|
null
|
|
@ -19,6 +19,7 @@ module.exports = class Spell
|
||||||
@supermodel = options.supermodel
|
@supermodel = options.supermodel
|
||||||
@skipProtectAPI = options.skipProtectAPI
|
@skipProtectAPI = options.skipProtectAPI
|
||||||
@worker = options.worker
|
@worker = options.worker
|
||||||
|
@levelID = options.levelID
|
||||||
|
|
||||||
p = options.programmableMethod
|
p = options.programmableMethod
|
||||||
@languages = p.languages ? {}
|
@languages = p.languages ? {}
|
||||||
|
|
|
@ -414,7 +414,7 @@ module.exports = class SpellView extends CocoView
|
||||||
for aetherProblem, problemIndex in aether.getAllProblems()
|
for aetherProblem, problemIndex in aether.getAllProblems()
|
||||||
continue if key = aetherProblem.userInfo?.key and key of seenProblemKeys
|
continue if key = aetherProblem.userInfo?.key and key of seenProblemKeys
|
||||||
seenProblemKeys[key] = true if key
|
seenProblemKeys[key] = true if key
|
||||||
@problems.push problem = new Problem aether, aetherProblem, @ace, isCast and problemIndex is 0, isCast
|
@problems.push problem = new Problem aether, aetherProblem, @ace, isCast and problemIndex is 0, isCast, @spell.levelID
|
||||||
annotations.push problem.annotation if problem.annotation
|
annotations.push problem.annotation if problem.annotation
|
||||||
@aceSession.setAnnotations annotations
|
@aceSession.setAnnotations annotations
|
||||||
@highlightCurrentLine aether.flow unless _.isEmpty aether.flow
|
@highlightCurrentLine aether.flow unless _.isEmpty aether.flow
|
||||||
|
|
|
@ -132,6 +132,7 @@ module.exports = class TomeView extends CocoView
|
||||||
worker: @worker
|
worker: @worker
|
||||||
language: language
|
language: language
|
||||||
spectateView: @options.spectateView
|
spectateView: @options.spectateView
|
||||||
|
levelID: @options.levelID
|
||||||
|
|
||||||
for thangID, spellKeys of @thangSpells
|
for thangID, spellKeys of @thangSpells
|
||||||
thang = world.getThangByID thangID
|
thang = world.getThangByID thangID
|
||||||
|
|
|
@ -8,6 +8,7 @@ module.exports.handlers =
|
||||||
'patch': 'patches/patch_handler'
|
'patch': 'patches/patch_handler'
|
||||||
'thang_type': 'levels/thangs/thang_type_handler'
|
'thang_type': 'levels/thangs/thang_type_handler'
|
||||||
'user': 'users/user_handler'
|
'user': 'users/user_handler'
|
||||||
|
'user_code_problem': 'user_code_problems/user_code_problem_handler'
|
||||||
'user_remark': 'users/remarks/user_remark_handler'
|
'user_remark': 'users/remarks/user_remark_handler'
|
||||||
'mail_sent': 'mail/sent/mail_sent_handler'
|
'mail_sent': 'mail/sent/mail_sent_handler'
|
||||||
'achievement': 'achievements/achievement_handler'
|
'achievement': 'achievements/achievement_handler'
|
||||||
|
|
|
@ -28,7 +28,7 @@ module.exports.setup = (app) ->
|
||||||
return errors.unauthorized(res, 'Must have an identity to do anything with the db. Do you have cookies enabled?') unless req.user
|
return errors.unauthorized(res, 'Must have an identity to do anything with the db. Do you have cookies enabled?') unless req.user
|
||||||
|
|
||||||
try
|
try
|
||||||
moduleName = module.replace '.', '_'
|
moduleName = module.replace new RegExp('\\.', 'g'), '_'
|
||||||
name = handlers[moduleName]
|
name = handlers[moduleName]
|
||||||
handler = require('../' + name)
|
handler = require('../' + name)
|
||||||
return handler.getLatestVersion(req, res, parts[1], parts[3]) if parts[2] is 'version'
|
return handler.getLatestVersion(req, res, parts[1], parts[3]) if parts[2] is 'version'
|
||||||
|
|
10
server/user_code_problems/UserCodeProblem.coffee
Normal file
10
server/user_code_problems/UserCodeProblem.coffee
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
mongoose = require 'mongoose'
|
||||||
|
plugins = require '../plugins/plugins'
|
||||||
|
|
||||||
|
UserCodeProblemSchema = new mongoose.Schema({
|
||||||
|
created:
|
||||||
|
type: Date
|
||||||
|
'default': Date.now
|
||||||
|
}, {strict: false})
|
||||||
|
|
||||||
|
module.exports = UserCodeProblem = mongoose.model('user.code.problem', UserCodeProblemSchema)
|
25
server/user_code_problems/user_code_problem_handler.coffee
Normal file
25
server/user_code_problems/user_code_problem_handler.coffee
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
UserCodeProblem = require './UserCodeProblem'
|
||||||
|
Handler = require '../commons/Handler'
|
||||||
|
|
||||||
|
class UserCodeProblemHandler extends Handler
|
||||||
|
modelClass: UserCodeProblem
|
||||||
|
jsonSchema: require '../../app/schemas/models/user_code_problem'
|
||||||
|
editableProperties: [
|
||||||
|
'code'
|
||||||
|
'codeSnippet'
|
||||||
|
'errHint'
|
||||||
|
'errId'
|
||||||
|
'errLevel'
|
||||||
|
'errMessage'
|
||||||
|
'errRange'
|
||||||
|
'errType'
|
||||||
|
'language'
|
||||||
|
'levelID'
|
||||||
|
]
|
||||||
|
|
||||||
|
makeNewInstance: (req) ->
|
||||||
|
ucp = super(req)
|
||||||
|
ucp.set('creator', req.user._id)
|
||||||
|
ucp
|
||||||
|
|
||||||
|
module.exports = new UserCodeProblemHandler()
|
91
test/app/views/play/level/tome/Problem.spec.coffee
Normal file
91
test/app/views/play/level/tome/Problem.spec.coffee
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
Problem = require 'views/play/level/tome/Problem'
|
||||||
|
|
||||||
|
describe 'Problem', ->
|
||||||
|
# boilerplate problem params
|
||||||
|
ace = {
|
||||||
|
getSession: -> {
|
||||||
|
getDocument: -> {
|
||||||
|
createAnchor: ->
|
||||||
|
}
|
||||||
|
addMarker: ->
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aether = {
|
||||||
|
raw: "this.say('hi');\nthis.sad('bye');"
|
||||||
|
language: { id: 'javascript' }
|
||||||
|
}
|
||||||
|
aetherProblem = {
|
||||||
|
hint: 'did you mean say instead of sad?'
|
||||||
|
id: 'unknown_ReferenceError'
|
||||||
|
level: 'error'
|
||||||
|
message: 'Line 1: tmp2[tmp3] is not a function'
|
||||||
|
range: [
|
||||||
|
{ row: 1 }
|
||||||
|
{ row: 1 }
|
||||||
|
]
|
||||||
|
type: 'runtime'
|
||||||
|
}
|
||||||
|
levelID = 'awesome'
|
||||||
|
|
||||||
|
it 'save user code problem', ->
|
||||||
|
new Problem aether, aetherProblem, ace, false, true, levelID
|
||||||
|
expect(jasmine.Ajax.requests.count()).toBe(1)
|
||||||
|
|
||||||
|
request = jasmine.Ajax.requests.mostRecent()
|
||||||
|
expect(request.url).toEqual("/db/user.code.problem")
|
||||||
|
|
||||||
|
params = JSON.parse(request.params)
|
||||||
|
expect(params.code).toEqual(aether.raw)
|
||||||
|
expect(params.codeSnippet).toEqual("this.sad('bye');")
|
||||||
|
expect(params.errHint).toEqual(aetherProblem.hint)
|
||||||
|
expect(params.errId).toEqual(aetherProblem.id)
|
||||||
|
expect(params.errLevel).toEqual(aetherProblem.level)
|
||||||
|
expect(params.errMessage).toEqual(aetherProblem.message)
|
||||||
|
expect(params.errRange).toEqual(aetherProblem.range)
|
||||||
|
expect(params.errType).toEqual(aetherProblem.type)
|
||||||
|
expect(params.language).toEqual(aether.language.id)
|
||||||
|
expect(params.levelID).toEqual(levelID)
|
||||||
|
|
||||||
|
it 'save user code problem no range', ->
|
||||||
|
aetherProblem.range = null
|
||||||
|
new Problem aether, aetherProblem, ace, false, true, levelID
|
||||||
|
expect(jasmine.Ajax.requests.count()).toBe(1)
|
||||||
|
|
||||||
|
request = jasmine.Ajax.requests.mostRecent()
|
||||||
|
expect(request.url).toEqual("/db/user.code.problem")
|
||||||
|
|
||||||
|
params = JSON.parse(request.params)
|
||||||
|
expect(params.code).toEqual(aether.raw)
|
||||||
|
expect(params.errHint).toEqual(aetherProblem.hint)
|
||||||
|
expect(params.errId).toEqual(aetherProblem.id)
|
||||||
|
expect(params.errLevel).toEqual(aetherProblem.level)
|
||||||
|
expect(params.errMessage).toEqual(aetherProblem.message)
|
||||||
|
expect(params.errType).toEqual(aetherProblem.type)
|
||||||
|
expect(params.language).toEqual(aether.language.id)
|
||||||
|
expect(params.levelID).toEqual(levelID)
|
||||||
|
|
||||||
|
# Difference when no range
|
||||||
|
expect(params.codeSnippet).toBeUndefined()
|
||||||
|
expect(params.errRange).toBeUndefined()
|
||||||
|
|
||||||
|
it 'save user code problem multi-line snippet', ->
|
||||||
|
aether.raw = "this.say('hi');\nthis.sad\n('bye');"
|
||||||
|
aetherProblem.range = [ { row: 1 }, { row: 2 } ]
|
||||||
|
|
||||||
|
new Problem aether, aetherProblem, ace, false, true, levelID
|
||||||
|
expect(jasmine.Ajax.requests.count()).toBe(1)
|
||||||
|
|
||||||
|
request = jasmine.Ajax.requests.mostRecent()
|
||||||
|
expect(request.url).toEqual("/db/user.code.problem")
|
||||||
|
|
||||||
|
params = JSON.parse(request.params)
|
||||||
|
expect(params.code).toEqual(aether.raw)
|
||||||
|
expect(params.codeSnippet).toEqual("this.sad\n('bye');")
|
||||||
|
expect(params.errHint).toEqual(aetherProblem.hint)
|
||||||
|
expect(params.errId).toEqual(aetherProblem.id)
|
||||||
|
expect(params.errLevel).toEqual(aetherProblem.level)
|
||||||
|
expect(params.errMessage).toEqual(aetherProblem.message)
|
||||||
|
expect(params.errRange).toEqual(aetherProblem.range)
|
||||||
|
expect(params.errType).toEqual(aetherProblem.type)
|
||||||
|
expect(params.language).toEqual(aether.language.id)
|
||||||
|
expect(params.levelID).toEqual(levelID)
|
Loading…
Add table
Add a link
Reference in a new issue