Instrument user code problems

This commit is contained in:
Matt Lott 2014-08-14 11:55:43 -07:00
parent 719e64e85f
commit 9b79e2ca27
11 changed files with 185 additions and 4 deletions

View 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'

View 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

View file

@ -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

View file

@ -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 ? {}

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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'

View 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)

View 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()

View 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)