Course translations fixes

* Restrict patch handling properly
* Fix , CS 2 description
* i18nCoverage is updated when new translations are auto-accepted
* Course patches are listed on PendingPatchesView properly
* 'Artisan' permission allows editing course translations
This commit is contained in:
Scott Erickson 2016-08-18 13:29:52 -07:00
parent d4af931e05
commit 300c81e72b
8 changed files with 77 additions and 2 deletions

View file

@ -40,6 +40,8 @@ module.exports = class PendingPatchesView extends RootView
"thang/#{patch.slug}"
when 'level_system', 'level_component'
"level/items?#{patch.target.collection}=#{patch.slug}"
when 'course'
"course/#{patch.slug}"
else
console.log "Where do we review a #{patch.target.collection} patch?"
''

View file

@ -24,7 +24,7 @@ var courses =
name: "Computer Science 2",
slug: "computer-science-2",
campaignID: ObjectId("562f88e84df18473073c74e2"),
description: "Introduce Arguments, Variables, If Statements, and Arithmetic.",
description: "Introduces arguments, variables, if statements, and arithmetic.",
duration: NumberInt(5),
free: false,
screenshot: "/images/pages/courses/102_info.png",

View file

@ -0,0 +1,43 @@
TreemaUtils = require '../../bower_components/treema/treema-utils.js'
exports.updateI18NCoverage = (doc) ->
# TODO: Share this code between server and client (client version in CocoModel)
langCodeArrays = []
pathToData = {}
# console.log 'doc schema', doc.schema.statics.jsonSchema, doc.schema
TreemaUtils.walk(doc.toObject(), doc.schema.statics.jsonSchema, null, (path, data, workingSchema) ->
# Store parent data for the next block...
if data?.i18n
pathToData[path] = data
if _.str.endsWith path, 'i18n'
i18n = data
# grab the parent data
parentPath = path[0...-5]
parentData = pathToData[parentPath]
# use it to determine what properties actually need to be translated
props = workingSchema.props or []
props = (prop for prop in props when parentData[prop])
#unless props.length
# console.log 'props is', props, 'path is', path, 'data is', data, 'parentData is', parentData, 'workingSchema is', workingSchema
# langCodeArrays.push _.without _.keys(locale), 'update' # Every language has covered a path with no properties to be translated.
# return
return if 'additionalProperties' of i18n # Workaround for #2630: Programmable is weird
# get a list of lang codes where its object has keys for every prop to be translated
coverage = _.filter(_.keys(i18n), (langCode) ->
translations = i18n[langCode]
_.all((translations[prop] for prop in props))
)
#console.log 'got coverage', coverage, 'for', path, props, workingSchema, parentData
langCodeArrays.push coverage
)
return unless langCodeArrays.length
# language codes that are covered for every i18n object are fully covered
overallCoverage = _.intersection(langCodeArrays...)
doc.set('i18nCoverage', overallCoverage)

View file

@ -14,4 +14,10 @@ CourseHandler = class CourseHandler extends Handler
hasAccess: (req) ->
req.method in @allowedMethods or req.user?.isAdmin()
hasAccessToDocument: (req, document, method=null) ->
method = (method or req.method).toLowerCase()
return true if method is 'get'
return true if req.user?.isAdmin() or req.user?.isArtisan()
return
module.exports = new CourseHandler()

View file

@ -14,6 +14,7 @@ Patch = require '../models/Patch'
tv4 = require('tv4').tv4
slack = require '../slack'
{ isJustFillingTranslations } = require '../commons/deltas'
{ updateI18NCoverage } = require '../commons/i18n'
module.exports =
@ -115,6 +116,7 @@ module.exports =
reasonNotAutoAccepted = 'Adding to existing translations.'
else
course.set(changedCourse)
updateI18NCoverage(course)
yield course.save()
patch = new Patch(req.body)

View file

@ -81,7 +81,9 @@ module.exports.setup = (app) ->
Course = require '../models/Course'
app.get('/db/course', mw.courses.get(Course))
app.put('/db/course/:handle', mw.auth.checkHasPermission(['admin']), mw.rest.put(Course))
app.get('/db/course/names', mw.named.names(Course))
app.post('/db/course/names', mw.named.names(Course))
app.put('/db/course/:handle', mw.auth.checkHasPermission(['admin', 'artisan']), mw.rest.put(Course))
app.get('/db/course/:handle', mw.rest.getByHandle(Course))
app.get('/db/course/:handle/level-solutions', mw.courses.fetchLevelSolutions)
app.get('/db/course/:handle/levels/:levelOriginal/next', mw.courses.fetchNextLevel)

View file

@ -264,6 +264,7 @@ describe 'POST /db/course/:handle/patch', ->
course = yield Course.findById(@course.id)
expect(course.get('i18n').de.description).toBe('German translation!')
expect(course.get('patches')).toBeUndefined()
expect(_.contains(course.get('i18nCoverage'),'de')).toBe(true)
done()
it 'saves the changes immediately if translations are for a new langauge', utils.wrap (done) ->

View file

@ -3,6 +3,7 @@ User = require '../../../server/models/User'
Article = require '../../../server/models/Article'
Patch = require '../../../server/models/Patch'
request = require '../request'
utils = require '../utils'
describe '/db/patch', ->
async = require 'async'
@ -160,3 +161,21 @@ describe '/db/patch', ->
Patch.findOne({}).exec (err, article) ->
expect(article.get('status')).toBe 'accepted'
done()
it 'only allows artisans and admins to set patch status for courses', utils.wrap (done) ->
submitter = yield utils.initUser()
course = yield utils.makeCourse()
patch = new Patch({
delta: { name: 'test' }
target: { collection: 'course', id: course._id, original: course._id }
creator: submitter._id
status: 'pending'
commitMessage: '...'
})
yield patch.save()
anotherUser = yield utils.initUser()
yield utils.loginUser(anotherUser)
json = { status: 'rejected' }
[res, body] = yield request.putAsync({ url: utils.getURL("/db/patch/#{patch.id}/status"), json})
expect(res.statusCode).toBe(403)
done()