From b4a0fe146e4263264bd7200c854b057ada9fd54a Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Mon, 5 Jan 2015 13:42:14 -0800 Subject: [PATCH] Add common user code problems to campaign editor In level view, showing data from last 30 days, with top 20 issues by count. --- .../editor/campaign/campaign-level-view.jade | 28 ++++++++- .../editor/campaign/CampaignLevelView.coffee | 23 ++++++++ .../user_code_problem_handler.coffee | 58 +++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/app/templates/editor/campaign/campaign-level-view.jade b/app/templates/editor/campaign/campaign-level-view.jade index ecdcd84b5..87418c981 100644 --- a/app/templates/editor/campaign/campaign-level-view.jade +++ b/app/templates/editor/campaign/campaign-level-view.jade @@ -38,9 +38,31 @@ td= levelPlaytimes[i].average.toFixed(2) else div Loading... - - - + + h4 Common Problems + if commonProblems + if commonProblems.startDay + if commonProblems.endDay + div(style='font-size:10pt') #{commonProblems.startDay} to #{commonProblems.endDay} + else + div(style='font-size:10pt') #{commonProblems.startDay} to today + table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt') + thead + tr + td Language + td Error Message + td Error Hint + td Count + tbody + - for (var i = 0; i < commonProblems.length && i < 20; i++) + tr + td= commonProblems[i].language + td= commonProblems[i].message + td= commonProblems[i].hint + td= commonProblems[i].count + else + div Loading... + if level.get('tasks') .tasks h3 Tasks (read only) diff --git a/app/views/editor/campaign/CampaignLevelView.coffee b/app/views/editor/campaign/CampaignLevelView.coffee index 56e3df97e..aaaa219ed 100644 --- a/app/views/editor/campaign/CampaignLevelView.coffee +++ b/app/views/editor/campaign/CampaignLevelView.coffee @@ -15,12 +15,14 @@ module.exports = class CampaignLevelView extends CocoView @listenToOnce @fullLevel, 'sync', => @render?() @levelSlug = @level.get('slug') + @getCommonLevelProblems() @getLevelCompletions() @getLevelPlaytimes() getRenderData: -> c = super() c.level = if @fullLevel.loaded then @fullLevel else @level + c.commonProblems = @commonProblems c.levelCompletions = @levelCompletions c.levelPlaytimes = @levelPlaytimes c @@ -29,6 +31,27 @@ module.exports = class CampaignLevelView extends CocoView @$el.addClass('hidden') @trigger 'hidden' + getCommonLevelProblems: -> + # Fetch last 30 days of common level problems + startDay = new Date() + startDay.setDate(startDay.getUTCDate() - 29) + startDay = startDay.getUTCFullYear() + '-' + (startDay.getUTCMonth() + 1) + '-' + startDay.getUTCDate() + + success = (data) => + return if @destroyed + @commonProblems = data + @commonProblems.startDay = startDay + @render() + + # TODO: Why do we need this url dash? + request = @supermodel.addRequestResource 'common_problems', { + url: '/db/user_code_problem/-/common_problems' + data: {startDay: startDay, slug: @levelSlug} + method: 'POST' + success: success + }, 0 + request.load() + getLevelCompletions: -> # Fetch last 7 days of level completion counts success = (data) => diff --git a/server/user_code_problems/user_code_problem_handler.coffee b/server/user_code_problems/user_code_problem_handler.coffee index a4424b6f9..268458c92 100644 --- a/server/user_code_problems/user_code_problem_handler.coffee +++ b/server/user_code_problems/user_code_problem_handler.coffee @@ -23,4 +23,62 @@ class UserCodeProblemHandler extends Handler ucp.set('creator', req.user._id) ucp + getByRelationship: (req, res, args...) -> + return @getCommonLevelProblemsBySlug(req, res) if args[1] is 'common_problems' + super(arguments...) + + getCommonLevelProblemsBySlug: (req, res) -> + # Returns an ordered array of common user code problems with: language, message, hint, count + # Parameters: + # slug - level slug + # startDay - Inclusive, optional, e.g. '2014-12-14' + # endDay - Exclusive, optional, e.g. '2014-12-16' + + levelSlug = req.query.slug or req.body.slug + startDay = req.query.startDay or req.body.startDay + endDay = req.query.endDay or req.body.endDay + + return @sendSuccess res, [] unless levelSlug? + + # Cache results for 1 day + @commonLevelProblemsCache ?= {} + @commonLevelProblemsCachedSince ?= new Date() + if (new Date()) - @commonLevelProblemsCachedSince > 86400 * 1000 # Dumb cache expiration + @commonLevelProblemsCache = {} + @commonLevelProblemsCachedSince = new Date() + cacheKey = levelSlug + cacheKey += 's' + startDay if startDay? + cacheKey += 'e' + endDay if endDay? + return @sendSuccess res, commonProblems if commonProblems = @commonLevelProblemsCache[cacheKey] + + # Build query + match = if startDay? or endDay? then {$match: {$and: []}} else {$match: {}} + match["$match"]["$and"].push created: {$gte: new Date(startDay + "T00:00:00.000Z")} if startDay? + match["$match"]["$and"].push created: {$lt: new Date(endDay + "T00:00:00.000Z")} if endDay? + group = {"$group": {"_id": {"errMessage": "$errMessageNoLineInfo", "errHint": "$errHint", "language": "$language", "levelID": "$levelID"}, "count": {"$sum": 1}}} + sort = { $sort : { "_id.levelID": 1, count : -1, "_id.language": 1 } } + query = UserCodeProblem.aggregate match, group, sort + + query.exec (err, data) => + if err? then return @sendDatabaseError res, err + + # Build per-level common problem lists + commonProblems = {} + for item in data + levelID = item._id.levelID + commonProblems[levelID] ?= [] + commonProblems[levelID].push + language: item._id.language + message: item._id.errMessage + hint: item._id.errHint + count: item.count + + # Cache all the levels + for levelID of commonProblems + cacheKey = levelID + cacheKey += 's' + startDay if startDay? + cacheKey += 'e' + endDay if endDay? + @commonLevelProblemsCache[cacheKey] = commonProblems[levelID] + @sendSuccess res, commonProblems[levelSlug] + module.exports = new UserCodeProblemHandler()