Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-06-17 13:04:38 -07:00
commit 52d08d7d05
14 changed files with 190 additions and 10 deletions

View file

@ -282,6 +282,7 @@
education_description: "Description"
education_description_help: "Highlight anything about this educational experience. (140 chars; optional)"
our_notes: "Our Notes"
remarks: "Remarks"
projects: "Projects"
projects_header: "Add 3 projects"
projects_header_2: "Projects (Top 3)"
@ -320,6 +321,7 @@
candidate_top_skills: "Top Skills"
candidate_years_experience: "Yrs Exp"
candidate_last_updated: "Last Updated"
candidate_who: "Who"
featured_developers: "Featured Developers"
other_developers: "Other Developers"
inactive_developers: "Inactive Developers"
@ -882,6 +884,7 @@
document: "Document"
sprite_sheet: "Sprite Sheet"
candidate_sessions: "Candidate Sessions"
user_remark: "User Remark"
delta:
added: "Added"

View file

@ -0,0 +1,6 @@
CocoModel = require('./CocoModel')
module.exports = class UserRemark extends CocoModel
@className: "UserRemark"
@schema: require 'schemas/models/user_remark'
urlRoot: "/db/user.remark"

View file

@ -0,0 +1,24 @@
c = require './../schemas'
UserRemarkSchema = c.object {
title: "Remark"
description: "Remarks on a user, point of contact, tasks."
}
_.extend UserRemarkSchema.properties,
user: c.objectId links: [{rel: 'extra', href: "/db/user/{($)}"}]
contact: c.objectId links: [{rel: 'extra', href: "/db/user/{($)}"}]
created: c.date title: 'Created', readOnly: true
history: c.array {title: 'History', description: 'Records of our interactions with the user.'},
c.object {title: 'Record'}, {date: c.date(title: 'Date'), content: {title: 'Content', type: 'string', format: 'markdown'}}
tasks: c.array {title: 'Tasks', description: 'Task entries: when to email the contact about something.'},
c.object {title: 'Task'}, {date: c.date(title: 'Date'), action: {title: 'Action', type: 'string'}}
# denormalization
userName: { title: "Player Name", type: 'string' }
contactName: { title: "Contact Name", type: 'string' } # Not actually our usernames
c.extendBasicProperties UserRemarkSchema, 'user.remark'
module.exports = UserRemarkSchema

View file

@ -193,6 +193,11 @@
width: 100%
height: 100px
#remark-treema
background-color: white
border: 0
padding-top: 0
.right-column
width: $side-width
background-color: $sideBackground

View file

@ -169,6 +169,10 @@ block content
button#contact-candidate.btn.btn-large.btn-inverse.flat-button
span(data-i18n="account_profile.contact") Contact
| #{profile.name.split(' ')[0]}
if me.isAdmin()
select#admin-contact.form-control
for contact in adminContacts
option(value=contact.id, selected=remark && remark.get('contact') == contact.id)= contact.name
if !editing && sessions.length
h3(data-i18n="account_profile.player_code") Player Code
@ -191,9 +195,12 @@ block content
if editing && !profile.name
h3.edit-label(data-i18n="account_profile.name_header") Fill in your name
else if profile.name
h3= profile.name
h3= profile.name + (me.isAdmin() ? ' (' + user.get('name') + ')' : '')
else
h3(data-i18n="account_profile.name_anonymous") Anonymous Developer
h3
span(data-i18n="account_profile.name_anonymous") Anonymous Developer
if me.isAdmin()
span (#{user.get('name')})
form.editable-form
.editable-icon.glyphicon.glyphicon-remove
@ -396,6 +403,10 @@ block content
else
div!= marked(notes)
if me.isAdmin()
h3(data-i18n="account_profile.remarks") Remarks
#remark-treema
.right-column.full-height-column
.sub-column
#projects-container.editable-section

View file

@ -84,6 +84,8 @@ block content
th(data-i18n="employers.candidate_top_skills") Top Skills
th(data-i18n="employers.candidate_years_experience") Yrs Exp
th(data-i18n="employers.candidate_last_updated") Last Updated
if me.isAdmin()
th(data-i18n="employers.candidate_who") Who
if me.isAdmin() && area.id == 'inactive-candidates'
th ✓?
@ -95,7 +97,10 @@ block content
td
if authorized
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
if profile.name
p= profile.name
else if me.isAdmin()
p (#{candidate.get('name')})
else
img(src="/images/pages/contribute/archmage.png", alt="", title="Sign up as an employer to see our candidates", width=50)
p Developer ##{index + 1 + (area.id == 'featured-candidates' ? 0 : featuredCandidates.length)}
@ -111,6 +116,8 @@ block content
span
td= profile.experience
td(data-profile-age=(new Date() - new Date(profile.updated)) / 86400 / 1000)= moment(profile.updated).fromNow()
if me.isAdmin()
td= remarks[candidate.id] ? remarks[candidate.id].get('contactName') : ''
if me.isAdmin() && area.id == 'inactive-candidates'
if candidate.get('jobProfileApproved')
td ✓

View file

@ -6,6 +6,8 @@ locale = require 'locale/locale'
class DateTimeTreema extends TreemaNode.nodeMap.string
valueClass: 'treema-date-time'
buildValueForDisplay: (el) -> el.text(moment(@data).format('llll'))
buildValueForEditing: (valEl) ->
@buildValueForEditingSimply valEl, null, 'date'
class VersionTreema extends TreemaNode
valueClass: 'treema-version'

View file

@ -19,10 +19,10 @@ module.exports = class JobProfileView extends CocoView
buildJobProfileTreema: ->
visibleSettings = @editableSettings.concat @readOnlySettings
data = _.pick (me.get('jobProfile') ? {}), (value, key) => key in visibleSettings
data = _.pick (me.get('jobProfile') ? {}), (value, key) -> key in visibleSettings
data.name ?= (me.get('firstName') + ' ' + me.get('lastName')).trim() if me.get('firstName')
schema = _.cloneDeep me.schema().properties.jobProfile
schema.properties = _.pick schema.properties, (value, key) => key in visibleSettings
schema.properties = _.pick schema.properties, (value, key) -> key in visibleSettings
schema.required = _.intersection schema.required, visibleSettings
for prop in @readOnlySettings
schema.properties[prop].readOnly = true

View file

@ -6,6 +6,7 @@ CocoCollection = require 'collections/CocoCollection'
{me} = require 'lib/auth'
JobProfileContactView = require 'views/modal/job_profile_contact_modal'
JobProfileView = require 'views/account/job_profile_view'
UserRemark = require 'models/UserRemark'
forms = require 'lib/forms'
class LevelSessionsCollection extends CocoCollection
@ -14,6 +15,14 @@ class LevelSessionsCollection extends CocoCollection
constructor: (@userID) ->
super()
adminContacts = [
{id: "", name: "Assign a Contact"}
{id: "512ef4805a67a8c507000001", name: "Nick"}
{id: "5162fab9c92b4c751e000274", name: "Scott"}
{id: "51eb2714fa058cb20d0006ef", name: "Michael"}
{id: "51538fdb812dd9af02000001", name: "George"}
]
module.exports = class ProfileView extends View
id: "profile-view"
template: template
@ -36,12 +45,14 @@ module.exports = class ProfileView extends View
'change .editable-profile .editable-array input': 'onEditArray'
'keyup .editable-profile .editable-array input': 'onEditArray'
'click .editable-profile a': 'onClickLinkWhileEditing'
'change #admin-contact': 'onAdminContactChanged'
constructor: (options, @userID) ->
@userID ?= me.id
@onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000
@onRemarkChanged = _.debounce @onRemarkChanged, 1000
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
@linkedInLoaded = Boolean(IN.parse)
@linkedInLoaded = Boolean(IN?.parse)
@waitingForLinkedIn = false
window.contractCallback = =>
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
@ -70,6 +81,22 @@ module.exports = class ProfileView extends View
else
@user = User.getByID(@userID)
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'candidate_sessions').model
if me.isAdmin()
# Mimicking how the VictoryModal fetches LevelFeedback
@remark = new UserRemark()
@remark.setURL "/db/user/#{@userID}/remark"
@remark.fetch()
@listenToOnce @remark, 'sync', @onRemarkLoaded
@listenToOnce @remark, 'error', @onRemarkNotFound
onRemarkLoaded: ->
@remark.setURL "/db/user.remark/#{@remark.id}"
@render()
onRemarkNotFound: ->
@remark = new UserRemark() # hmm, why do we create a new one here?
@remark.set 'user', @userID
@remark.set 'userName', name if name = @user.get('name')
onLinkedInLoaded: =>
@linkedinLoaded = true
@ -229,6 +256,8 @@ module.exports = class ProfileView extends View
context.sessions.sort (a, b) -> (b.playtime ? 0) - (a.playtime ? 0)
else
context.sessions = []
context.adminContacts = adminContacts
context.remark = @remark
context
afterRender: ->
@ -249,6 +278,31 @@ module.exports = class ProfileView extends View
_.delay ->
justSavedSection.removeClass "just-saved", duration: 1500, easing: 'easeOutQuad'
, 500
if me.isAdmin()
visibleSettings = ['history', 'tasks']
data = _.pick (@remark.attributes), (value, key) -> key in visibleSettings
data.history ?= []
data.tasks ?= []
schema = _.cloneDeep @remark.schema()
schema.properties = _.pick schema.properties, (value, key) => key in visibleSettings
schema.required = _.intersection (schema.required ? []), visibleSettings
treemaOptions =
filePath: "db/user/#{@userID}"
schema: schema
data: data
aceUseWrapMode: true
callbacks: {change: @onRemarkChanged}
@remarkTreema = @$el.find('#remark-treema').treema treemaOptions
@remarkTreema.build()
@remarkTreema.open(3)
onRemarkChanged: (e) =>
return unless @remarkTreema.isValid()
for key in ['history', 'tasks']
val = _.filter(@remarkTreema.get(key), (entry) -> entry?.content or entry?.action)
entry.date ?= (new Date()).toISOString() for entry in val if key is 'history'
@remark.set key, val
@saveRemark()
initializeAutocomplete: (container) ->
(container ? @$el).find('input[data-autocomplete]').each ->
@ -455,6 +509,26 @@ module.exports = class ProfileView extends View
onClickLinkWhileEditing: (e) ->
e.preventDefault()
onAdminContactChanged: (e) ->
newContact = @$el.find('#admin-contact').val()
newContactName = if newContact then _.find(adminContacts, id: newContact).name else ''
@remark.set 'contact', newContact
@remark.set 'contactName', newContactName
@saveRemark()
saveRemark: ->
@remark.set 'user', @user.id
@remark.set 'userName', @user.get('name')
if errors = @remark.validate()
return console.error "UserRemark", @remark, "failed validation with errors:", errors
res = @remark.save()
res.error =>
return if @destroyed
console.error "UserRemark", @remark, "failed to save with error:", res.responseText
res.success (model, response, options) =>
return if @destroyed
console.log "Saved UserRemark", @remark, "with response", response
updateProgress: (highlightNext) ->
return unless @user
completed = 0

View file

@ -2,6 +2,7 @@ View = require 'views/kinds/RootView'
template = require 'templates/employers'
app = require 'application'
User = require 'models/User'
UserRemark = require 'models/UserRemark'
{me} = require 'lib/auth'
CocoCollection = require 'collections/CocoCollection'
EmployerSignupView = require 'views/modal/employer_signup_modal'
@ -10,6 +11,10 @@ class CandidatesCollection extends CocoCollection
url: '/db/user/x/candidates'
model: User
class UserRemarksCollection extends CocoCollection
url: '/db/user.remark?project=contact,contactName,user'
model: UserRemark
module.exports = class EmployersView extends View
id: "employers-view"
template: template
@ -37,6 +42,8 @@ module.exports = class EmployersView extends View
ctx.inactiveCandidates = _.reject ctx.candidates, (c) -> c.get('jobProfile').active
ctx.featuredCandidates = _.filter ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.otherCandidates = _.reject ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.remarks = {}
ctx.remarks[remark.get('user')] = remark for remark in @remarks.models
ctx.moment = moment
ctx._ = _
ctx
@ -48,11 +55,13 @@ module.exports = class EmployersView extends View
getCandidates: ->
@candidates = new CandidatesCollection()
@candidates.fetch()
@remarks = new UserRemarksCollection()
@remarks.fetch()
# Re-render when we have fetched them, but don't wait and show a progress bar while loading.
@listenToOnce @candidates, 'all', @renderCandidatesAndSetupScrolling
@listenToOnce @remarks, 'all', @renderCandidatesAndSetupScrolling
renderCandidatesAndSetupScrolling: =>
@render()
$(".nano").nanoScroller()
if window.history?.state?.lastViewedCandidateID
@ -179,7 +188,7 @@ module.exports = class EmployersView extends View
"Last 4 weeks": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 28
7:
8:
"": filterSelectExactMatch
"": filterSelectExactMatch

View file

@ -8,6 +8,7 @@ module.exports.handlers =
'patch': 'patches/patch_handler'
'thang_type': 'levels/thangs/thang_type_handler'
'user': 'users/user_handler'
'user_remark': 'users/remarks/user_remark_handler'
'achievement': 'achievements/achievement_handler'
'earned_achievement': 'achievements/earned_achievement_handler'

View file

@ -0,0 +1,11 @@
mongoose = require('mongoose')
plugins = require('../../plugins/plugins')
jsonschema = require('../../../app/schemas/models/user_remark')
UserRemarkSchema = new mongoose.Schema({
created:
type: Date
'default': Date.now
}, {strict: false})
module.exports = UserRemark = mongoose.model('user.remark', UserRemarkSchema)

View file

@ -0,0 +1,12 @@
UserRemark = require('./UserRemark')
Handler = require('../../commons/Handler')
class UserRemarkHandler extends Handler
modelClass: UserRemark
editableProperties: ['user', 'contact', 'history', 'tasks', 'userName', 'contactName']
jsonSchema: require '../../../app/schemas/models/user_remark'
hasAccess: (req) ->
req.user?.isAdmin()
module.exports = new UserRemarkHandler()

View file

@ -11,6 +11,7 @@ log = require 'winston'
LevelSession = require('../levels/sessions/LevelSession')
LevelSessionHandler = require '../levels/sessions/level_session_handler'
EarnedAchievement = require '../achievements/EarnedAchievement'
UserRemark = require './remarks/UserRemark'
serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset']
privateProperties = [
@ -197,6 +198,7 @@ UserHandler = class UserHandler extends Handler
return @getMySimulatorLeaderboardRank(req, res, args[0]) if args[1] is 'simulator_leaderboard_rank'
return @getEarnedAchievements(req, res, args[0]) if args[1] is 'achievements'
return @trackActivity(req, res, args[0], args[2], args[3]) if args[1] is 'track' and args[2]
return @getRemark(req, res, args[0]) if args[1] is 'remark'
return @sendNotFoundError(res)
super(arguments...)
@ -313,7 +315,7 @@ UserHandler = class UserHandler extends Handler
#query.jobProfileApproved = true unless req.user.isAdmin() # We split into featured and other now.
query['jobProfile.active'] = true unless req.user.isAdmin()
selection = 'jobProfile jobProfileApproved photoURL'
selection += ' email' if authorized
selection += ' email name' if authorized
User.find(query).select(selection).exec (err, documents) =>
return @sendDatabaseError(res, err) if err
candidates = (candidate for candidate in documents when @employerCanViewCandidate req.user, candidate.toObject())
@ -321,7 +323,7 @@ UserHandler = class UserHandler extends Handler
@sendSuccess(res, candidates)
formatCandidate: (authorized, document) ->
fields = if authorized then ['jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['jobProfile', 'jobProfileApproved']
fields = if authorized then ['name', 'jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['jobProfile', 'jobProfileApproved']
obj = _.pick document.toObject(), fields
obj.photoURL ||= obj.jobProfile.photoURL if authorized
subfields = ['country', 'city', 'lookingFor', 'jobTitle', 'skills', 'experience', 'updated', 'active']
@ -363,4 +365,17 @@ UserHandler = class UserHandler extends Handler
hash.update(user.get('_id') + '')
hash.digest('hex')
getRemark: (req, res, userID) ->
return @sendUnauthorizedError(res) unless req.user.isAdmin()
query = user: userID
projection = null
if req.query.project
projection = {}
projection[field] = 1 for field in req.query.project.split(',')
UserRemark.findOne(query).select(projection).exec (err, remark) =>
return @sendDatabaseError res, err if err
return @sendNotFoundError res unless remark?
@sendSuccess res, remark
module.exports = new UserHandler()