All edits are now tracked

intermediate

intermediate
This commit is contained in:
Ruben Vereecken 2014-06-27 21:30:31 +02:00
parent b951205681
commit fce9f0031b
13 changed files with 146 additions and 51 deletions

View file

@ -1,4 +1,4 @@
CocoClass = require 'lib/CocoClass'
CocoClass = require './CocoClass'
namesCache = {}

View file

@ -1,4 +1,4 @@
SystemNameLoader = require 'lib/SystemNameLoader'
SystemNameLoader = require './SystemNameLoader'
###
Good-to-knows:
dataPath: an array of keys that walks you up a JSON object that's being patched
@ -10,7 +10,6 @@ SystemNameLoader = require 'lib/SystemNameLoader'
module.exports.expandDelta = (delta, left, schema) ->
flattenedDeltas = flattenDelta(delta)
(expandFlattenedDelta(fd, left, schema) for fd in flattenedDeltas)
flattenDelta = (delta, dataPath=null, deltaPath=null) ->
# takes a single jsondiffpatch delta and returns an array of objects with
@ -27,9 +26,7 @@ flattenDelta = (delta, dataPath=null, deltaPath=null) ->
results = results.concat flattenDelta(
childDelta, dataPath.concat([dataIndex]), deltaPath.concat([deltaIndex]))
results
expandFlattenedDelta = (delta, left, schema) ->
pandFlattenedDelta = (delta, left, schema) ->
# takes a single flattened delta and converts into an object that can be
# easily formatted into something human readable.

View file

@ -20,6 +20,8 @@ PatchSchema = c.object({title:'Patch', required:['target', 'delta', 'commitMessa
major: { type: 'number', minimum: 0 }
minor: { type: 'number', minimum: 0 }
})
_wasPending: type: 'boolean'
})
c.extendBasicProperties(PatchSchema, 'patch')

View file

@ -160,7 +160,13 @@ UserSchema = c.object {},
data: c.object {description: "Cached LinkedIn data slurped from profile.", additionalProperties: true}
points: {type:'number'}
activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity}
stats: c.object {additionalProperties: true}, # TODO set to false after dev
gamesCompleted: type: 'integer'
articleEdits: type: 'integer'
levelEdits: type: 'integer'
levelSystemEdits: type: 'integer'
levelComponentEdits: type: 'integer'
thangTypeEdits: type: 'integer'
c.extendBasicProperties UserSchema, 'user'

View file

@ -8,16 +8,4 @@ ArticleSchema.plugin(plugins.VersionedPlugin)
ArticleSchema.plugin(plugins.SearchablePlugin, {searchable: ['body', 'name']})
ArticleSchema.plugin(plugins.PatchablePlugin)
# Assumes every article save is a new version
ArticleSchema.pre 'save', (next) ->
return next() unless @get('creator')
User = require '../users/User' # Avoid mutual inclusion cycles
userID = @get('creator').toHexString()
User.update {_id: userID}, {$inc: 'stats.articleEdits': 1}, {}, (err, docs) ->
log.error err if err?
next()
module.exports = mongoose.model('article', ArticleSchema)

View file

@ -21,16 +21,5 @@ LevelSchema.pre 'init', (next) ->
LevelSchema.post 'init', (doc) ->
if _.isString(doc.get('nextLevel'))
doc.set('nextLevel', undefined)
# Assumes every level save is a new level
LevelSchema.pre 'save', (next) ->
return next() unless @get('creator')
User = require '../users/User' # Avoid mutual inclusion cycles
userID = @get('creator').toHexString()
User.update {_id: userID}, {$inc: 'stats.levelEdits': 1}, {}, (err, docs) ->
log.error err if err?
next()
module.exports = Level = mongoose.model('level', LevelSchema)

View file

@ -1,7 +1,8 @@
mongoose = require('mongoose')
deltas = require '../../app/lib/deltas'
{handlers} = require '../commons/mapping'
PatchSchema = new mongoose.Schema({}, {strict: false})
PatchSchema = new mongoose.Schema({status: String}, {strict: false})
PatchSchema.pre 'save', (next) ->
return next() unless @isNew # patch can't be altered after creation, so only need to check data once
@ -45,4 +46,31 @@ PatchSchema.pre 'save', (next) ->
@targetLoaded = document
document.save (err) -> next(err)
PatchSchema.methods.isTranslationPatch = ->
console.log @get 'delta'
expanded = deltas.expandDelta @get('delta')
console.log 'expanded'
_.some expanded, (delta) -> 'i18n' in expanded.dataPath
PatchSchema.methods.isMiscPatch = ->
expanded = deltas.expandDelta @get 'delta'
_.some expanded, (delta) -> 'i18n' not in expanded.dataPath
# Keep track of when a patch is pending. Accepted patches can be rejected still.
PatchSchema.path('status').set (newVal) ->
@set '_wasPending', @status is 'pending' and newVal isnt 'pending'
newVal
PatchSchema.pre 'save', (next) ->
User = require '../users/User'
userID = @get('creator').toHexString()
if @get('status') is 'accepted'
User.incrementStat userID, 'stats.patchesContributed' # accepted patches
else if @get('status') is 'pending'
User.incrementStat userID, 'stats.patchesSubmitted' # submitted patches
next()
module.exports = mongoose.model('patch', PatchSchema)

View file

@ -48,11 +48,18 @@ PatchHandler = class PatchHandler extends Handler
if newStatus is 'withdrawn'
return @sendUnauthorizedError(res) unless req.user.get('_id').equals patch.get('creator')
# newly accepted
if newStatus is 'accepted' and patch.get '_wasPending'
accepter = req.user.get 'id'
User.incrementStat accepter, 'stats.'
# these require callbacks
patch.update {$set:{status:newStatus}}, {}, ->
target.update {$pull:{patches:patch.get('_id')}}, {}, ->
@sendSuccess(res, null)
patch.set 'status', newStatus
patch.save (err) =>
log.error err if err?
target.update {$pull:{patches:patch.get('_id')}}, {}, ->
@sendSuccess(res, null)
onPostSuccess: (req, doc) ->
log.error "Error sending patch created: could not find the loaded target on the patch object." unless doc.targetLoaded

View file

@ -1,5 +1,6 @@
mongoose = require('mongoose')
textSearch = require('mongoose-text-search')
log = require 'winston'
module.exports.MigrationPlugin = (schema, migrations) ->
# Property name migrations made EZ
@ -256,13 +257,14 @@ module.exports.VersionedPlugin = (schema) ->
)
)
# Assume ever save is a new version, hence an edit
schema.pre 'save', (next) ->
return next() unless @get('creator')
User = require '../users/User' # Avoid mutual inclusion cycles
userID = @get('creator')?.toHexString()
return next() unless userID?
userID = @get('creator').toHexString()
User.update {_id: userID}, {$inc: 'stats.levelEdits': 1}, {}, (err, docs) ->
log.error err if err?
statName = User.statsMapping.edits[@constructor.modelName]
User.incrementStat userID, statName
next()

View file

@ -108,6 +108,19 @@ UserSchema.statics.updateMailChimp = (doc, callback) ->
mc?.lists.subscribe params, onSuccess, onFailure
UserSchema.statics.statsMapping =
edits:
article: 'stats.articleEdits'
level: 'stats.levelEdits'
'level.component': 'stats.levelComponentEdits'
'level.system': 'stats.levelSystemEdits'
'thang.type': 'stats.thangTypeEdits'
UserSchema.statics.incrementStat = (id, statName, done, inc=1) ->
update = $inc: {}
update.$inc[statName] = inc
@update {_id:id}, update, {}, (err) ->
done err if done?
UserSchema.pre('save', (next) ->
@set('emailLower', @get('email')?.toLowerCase())

View file

@ -375,7 +375,9 @@ UserHandler = class UserHandler extends Handler
return @sendNotFoundError res unless remark?
@sendSuccess res, remark
countEdits = (model, statKey, done) ->
countEdits = (model, done) ->
statKey = User.statsMapping.edits[model.modelName]
return done(new Error 'Could not resolve statKey for model') unless statKey?
User.find {}, (err, users) ->
async.eachSeries users, ((user, doneWithUser) ->
userID = user.get('_id').toHexString()
@ -409,23 +411,23 @@ UserHandler = class UserHandler extends Handler
articleEdits: (done) ->
Article = require '../articles/Article'
countEdits Article, 'stats.articleEdits', done
countEdits Article, done
levelEdits: (done) ->
Level = require '../levels/Level'
countEdits Level, 'stats.levelEdits', done
countEdits Level, done
levelComponentEdits: (done) ->
LevelComponent = require '../levels/components/LevelComponent'
countEdits LevelComponent, 'stats.levelComponentEdits', done
countEdits LevelComponent, done
levelSystemEdits: (done) ->
LevelSystem = require '../levels/systems/LevelSystem'
countEdits LevelSystem, 'stats.levelSystemEdits', done
countEdits LevelSystem, done
thangTypeEdits: (done) ->
ThangType = require '../levels/thangs/ThangType'
countEdits ThangType, 'stats.thangTypeEdits', done
countEdits ThangType, done
recalculate: (req, res, statName) ->

View file

@ -272,6 +272,10 @@ describe 'GET /db/user', ->
describe 'Statistics', ->
LevelSession = require '../../../server/levels/sessions/LevelSession'
Article = require '../../../server/articles/Article'
Level = require '../../../server/levels/Level'
LevelSystem = require '../../../server/levels/systems/LevelSystem'
LevelComponent = require '../../../server/levels/components/LevelComponent'
ThangType = require '../../../server/levels/thangs/ThangType'
User = require '../../../server/users/User'
UserHandler = require '../../../server/users/user_handler'
@ -315,7 +319,7 @@ describe 'Statistics', ->
url = getURL('/db/article')
loginAdmin (carl) ->
expect(carl.get 'stats.articleEdits').toBeUndefined()
expect(carl.get User.statsMapping.edits.article).toBeUndefined()
article.creator = carl.get 'id'
# Create major version 1.0
@ -326,7 +330,7 @@ describe 'Statistics', ->
User.findById carl.get('id'), (err, guy) ->
expect(err).toBeNull()
expect(guy.get 'stats.articleEdits').toBe 1
expect(guy.get User.statsMapping.edits.article).toBe 1
# Create minor version 1.1
request.post {uri:url, json: article}, (err, res, body) ->
@ -334,7 +338,7 @@ describe 'Statistics', ->
User.findById carl.get('id'), (err, guy) ->
expect(err).toBeNull()
expect(guy.get 'stats.articleEdits').toBe 2
expect(guy.get User.statsMapping.edits.article).toBe 2
done()
@ -342,16 +346,52 @@ describe 'Statistics', ->
loginAdmin (carl) ->
User.findByIdAndUpdate carl.get('id'), {$unset:'stats.articleEdits': ''}, (err, guy) ->
expect(err).toBeNull()
expect(guy.get 'stats.articleEdits').toBeUndefined()
expect(guy.get User.statsMapping.edits.article).toBeUndefined()
UserHandler.statHandlers.articleEdits ->
User.findById carl.get('id'), (err, guy) ->
expect(err).toBeNull()
expect(guy.get 'stats.articleEdits').toBe 2
expect(guy.get User.statsMapping.edits.article).toBe 2
done()
it 'keeps track of level edits', (done) ->
level = new Level
name: "King's Peak 3"
description: 'Climb a mountain.'
permissions: simplePermissions
scripts: []
thangs: []
loginAdmin (carl) ->
expect(carl.get User.statsMapping.edits.level).toBeUndefined()
level.creator = carl.get 'id'
level.save (err) ->
expect(err).toBeNull()
User.findById carl.get('id'), (err, guy) ->
expect(err).toBeNull()
expect(guy.get 'id').toBe carl.get 'id'
expect(guy.get User.statsMapping.edits.level).toBe 1
done()
it 'recalculates level edits', (done) ->
unittest.getAdmin (jose) ->
User.findByIdAndUpdate jose.get('id'), {$unset:'stats.levelEdits':''}, (err, guy) ->
expect(err).toBeNull()
expect(guy.get User.statsMapping.edits.level).toBeUndefined()
UserHandler.statHandlers.levelEdits ->
User.findById jose.get('id'), (err, guy) ->
expect(err).toBeNull()
expect(guy.get User.statsMapping.edits.level).toBe 1
done()
it 'cleans up', (done) ->
clearModels [LevelSession, Article], (err) ->
clearModels [LevelSession, Article, Level, LevelSystem, LevelComponent, ThangType], (err) ->
expect(err).toBeNull()
done()

View file

@ -0,0 +1,21 @@
require '../common'
describe 'schema methods', ->
patch = new Patch
delta:
scripts: 0: i18n: 'aaahw yeahh'
_t: 'a'
it 'is translation patch', ->
expect(patch.isTranslationPatch()).toBeTruthy()
patch.set 'delta.i18n', undefined
expect(patch.isTranslationPatch()).toBeFalsy()
it 'is miscellaneous patch', ->
expect(patch.isMiscPatch()).toBeTruthy()
patch.set 'delta.thangs', undefined
expect(patch.isMiscPatch()).toBeFalsy()