Merge branch 'feature/linkedin-profile-import'

Conflicts:
	app/templates/account/profile.jade
	app/views/account/profile_view.coffee
This commit is contained in:
Michael Schmatz 2014-06-08 18:37:33 -07:00
commit 693da57f13
5 changed files with 179 additions and 24 deletions

View file

@ -23,5 +23,12 @@ module.exports = LinkedInHandler = class LinkedInHandler extends CocoClass
.result (profiles) => .result (profiles) =>
cb null, profiles.values[0] cb null, profiles.values[0]
getProfileData: (cb) =>
IN.API.Profile("me")
.fields(["formatted-name","educations","skills","headline","summary","positions","public-profile-url"])
.error(cb)
.result (profiles) =>
cb null, profiles.values[0]
destroy: -> destroy: ->
super() super()

View file

@ -78,7 +78,7 @@ UserSchema = c.object {},
city: c.shortString {title: 'City', description: 'City you want to work in (or live in now), like "San Francisco" or "Lubbock, TX".', default: 'Defaultsville, CA', format: 'city'} city: c.shortString {title: 'City', description: 'City you want to work in (or live in now), like "San Francisco" or "Lubbock, TX".', default: 'Defaultsville, CA', format: 'city'}
country: c.shortString {title: 'Country', description: 'Country you want to work in (or live in now), like "USA" or "France".', default: 'USA', format: 'country'} country: c.shortString {title: 'Country', description: 'Country you want to work in (or live in now), like "USA" or "France".', default: 'USA', format: 'country'}
skills: c.array {title: 'Skills', description: 'Tag relevant developer skills in order of proficiency.', default: ['javascript'], minItems: 1, maxItems: 30, uniqueItems: true}, skills: c.array {title: 'Skills', description: 'Tag relevant developer skills in order of proficiency.', default: ['javascript'], minItems: 1, maxItems: 30, uniqueItems: true},
{type: 'string', minLength: 1, maxLength: 20, description: 'Ex.: "objective-c", "mongodb", "rails", "android", "javascript"', format: 'skill'} {type: 'string', minLength: 1, maxLength: 50, description: 'Ex.: "objective-c", "mongodb", "rails", "android", "javascript"', format: 'skill'}
experience: {type: 'integer', title: 'Years of Experience', minimum: 0, description: 'How many years of professional experience (getting paid) developing software do you have?'} experience: {type: 'integer', title: 'Years of Experience', minimum: 0, description: 'How many years of professional experience (getting paid) developing software do you have?'}
shortDescription: {type: 'string', maxLength: 140, title: 'Short Description', description: 'Who are you, and what are you looking for? 140 characters max.', default: 'Programmer seeking to build great software.'} shortDescription: {type: 'string', maxLength: 140, title: 'Short Description', description: 'Who are you, and what are you looking for? 140 characters max.', default: 'Programmer seeking to build great software.'}
longDescription: {type: 'string', maxLength: 600, title: 'Description', description: 'Describe yourself to potential employers. Keep it short and to the point. We recommend outlining the position that would most interest you. Tasteful markdown okay; 600 characters max.', format: 'markdown', default: '* I write great code.\n* You need great code?\n* Great!'} longDescription: {type: 'string', maxLength: 600, title: 'Description', description: 'Describe yourself to potential employers. Keep it short and to the point. We recommend outlining the position that would most interest you. Tasteful markdown okay; 600 characters max.', format: 'markdown', default: '* I write great code.\n* You need great code?\n* Great!'}

View file

@ -38,6 +38,9 @@
i i
margin-right: 5px margin-right: 5px
.linked-in-button
cursor: default
.sample-profile .sample-profile
position: absolute position: absolute
right: 5px right: 5px

View file

@ -18,6 +18,13 @@ block content
button.btn#toggle-editing button.btn#toggle-editing
i.icon-cog i.icon-cog
span(data-i18n="account_profile.edit_profile") Edit Profile span(data-i18n="account_profile.edit_profile") Edit Profile
if linkedInAuthorized && editing
button.btn.btn-success#importLinkedIn
i.icon-arrow-down
span Import LinkedIn
else if editing
button.btn.linked-in-button
script(type="in/Login" id="linkedInAuthButton" data-onAuth="contractCallback")
if profile && profile.active if profile && profile.active
button.btn.btn-success#toggle-job-profile-active button.btn.btn-success#toggle-job-profile-active
i.icon-eye-open i.icon-eye-open

View file

@ -1,6 +1,7 @@
View = require 'views/kinds/RootView' View = require 'views/kinds/RootView'
template = require 'templates/account/profile' template = require 'templates/account/profile'
User = require 'models/User' User = require 'models/User'
{me} = require 'lib/auth'
JobProfileContactView = require 'views/modal/job_profile_contact_modal' JobProfileContactView = require 'views/modal/job_profile_contact_modal'
JobProfileView = require 'views/account/job_profile_view' JobProfileView = require 'views/account/job_profile_view'
forms = require 'lib/forms' forms = require 'lib/forms'
@ -8,9 +9,12 @@ forms = require 'lib/forms'
module.exports = class ProfileView extends View module.exports = class ProfileView extends View
id: "profile-view" id: "profile-view"
template: template template: template
subscriptions:
'linkedin-loaded': 'onLinkedInLoaded'
events: events:
'click #toggle-editing': 'toggleEditing' 'click #toggle-editing': 'toggleEditing'
'click #importLinkedIn': 'importLinkedIn'
'click #toggle-job-profile-active': 'toggleJobProfileActive' 'click #toggle-job-profile-active': 'toggleJobProfileActive'
'click #toggle-job-profile-approved': 'toggleJobProfileApproved' 'click #toggle-job-profile-approved': 'toggleJobProfileApproved'
'click #save-notes-button': 'onJobProfileNotesChanged' 'click #save-notes-button': 'onJobProfileNotesChanged'
@ -27,6 +31,13 @@ module.exports = class ProfileView extends View
constructor: (options, @userID) -> constructor: (options, @userID) ->
@userID ?= me.id @userID ?= me.id
@onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
@linkedInLoaded = Boolean(IN.parse)
@waitingForLinkedIn = false
window.contractCallback = =>
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
@render()
super options super options
if User.isObjectID @userID if User.isObjectID @userID
@finishInit() @finishInit()
@ -51,9 +62,134 @@ module.exports = class ProfileView extends View
else else
@user = User.getByID(@userID) @user = User.getByID(@userID)
onLinkedInLoaded: =>
@linkedinLoaded = true
if @waitingForLinkedIn
@renderLinkedInButton()
renderLinkedInButton: =>
IN?.parse()
afterInsert: ->
super()
linkedInButtonParentElement = document.getElementById("linkedInAuthButton")
if linkedInButtonParentElement
if @linkedinLoaded
@renderLinkedInButton()
else
@waitingForLinkedIn = true
importLinkedIn: =>
overwriteConfirm = confirm("Importing LinkedIn data will overwrite your current work experience, skills, name, descriptions, and education. Continue?")
unless overwriteConfirm then return
application.linkedinHandler.getProfileData (err, profileData) =>
console.log profileData
@processLinkedInProfileData profileData
jobProfileSchema: -> @user.schema().properties.jobProfile.properties
processLinkedInProfileData: (p) ->
#handle formatted-name
currentJobProfile = @user.get('jobProfile')
oldJobProfile = _.cloneDeep(currentJobProfile)
jobProfileSchema = @user.schema().properties.jobProfile.properties
if p["formattedName"]? and p["formattedName"] isnt "private"
nameMaxLength = jobProfileSchema.name.maxLength
currentJobProfile.name = p["formattedName"].slice(0,nameMaxLength)
if p["skills"]?["values"].length
skillNames = []
skillMaxLength = jobProfileSchema.skills.items.maxLength
for skill in p.skills.values
skillNames.push skill.skill.name.slice(0,skillMaxLength)
currentJobProfile.skills = skillNames
if p["headline"]
shortDescriptionMaxLength = jobProfileSchema.shortDescription.maxLength
currentJobProfile.shortDescription = p["headline"].slice(0,shortDescriptionMaxLength)
if p["summary"]
longDescriptionMaxLength = jobProfileSchema.longDescription.maxLength
currentJobProfile.longDescription = p.summary.slice(0,longDescriptionMaxLength)
if p["positions"]?["values"]?.length
newWorks = []
workSchema = jobProfileSchema.work.items.properties
for position in p["positions"]["values"]
workObj = {}
descriptionMaxLength = workSchema.description.maxLength
workObj.description = position.summary?.slice(0,descriptionMaxLength)
workObj.description ?= ""
if position.startDate?.year?
workObj.duration = "#{position.startDate.year} - "
if (not position.endDate?.year) or (position.endDate?.year and position.endDate?.year > (new Date().getFullYear()))
workObj.duration += "present"
else
workObj.duration += position.endDate.year
else
workObj.duration = ""
durationMaxLength = workSchema.duration.maxLength
workObj.duration = workObj.duration.slice(0,durationMaxLength)
employerMaxLength = workSchema.employer.maxLength
workObj.employer = position.company?.name ? ""
workObj.employer = workObj.employer.slice(0,employerMaxLength)
workObj.role = position.title ? ""
roleMaxLength = workSchema.role.maxLength
workObj.role = workObj.role.slice(0,roleMaxLength)
newWorks.push workObj
currentJobProfile.work = newWorks
if p["educations"]?["values"]?.length
newEducation = []
eduSchema = jobProfileSchema.education.items.properties
for education in p["educations"]["values"]
educationObject = {}
educationObject.degree = education.degree ? "Studied"
if education.startDate?.year?
educationObject.duration = "#{education.startDate.year} - "
if (not education.endDate?.year) or (education.endDate?.year and education.endDate?.year > (new Date().getFullYear()))
educationObject.duration += "present"
if educationObject.degree is "Studied"
educationObject.degree = "Studying"
else
educationObject.duration += education.endDate.year
else
educationObject.duration = ""
if education.fieldOfStudy
if educationObject.degree is "Studied" or educationObject.degree is "Studying"
educationObject.degree += " #{education.fieldOfStudy}"
else
educationObject.degree += " in #{education.fieldOfStudy}"
educationObject.degree = educationObject.degree.slice(0,eduSchema.degree.maxLength)
educationObject.duration = educationObject.duration.slice(0,eduSchema.duration.maxLength)
educationObject.school = education.schoolName ? ""
educationObject.school = educationObject.school.slice(0,eduSchema.school.maxLength)
educationObject.description = ""
newEducation.push educationObject
currentJobProfile.education = newEducation
if p["publicProfileUrl"]
#search for linkedin link
links = currentJobProfile.links
alreadyHasLinkedIn = false
for link in links
if link.link.toLowerCase().indexOf("linkedin") > -1
alreadyHasLinkedIn = true
break
unless alreadyHasLinkedIn
newLink =
link: p["publicProfileUrl"]
name: "LinkedIn"
currentJobProfile.links.push newLink
@user.set('jobProfile',currentJobProfile)
validationErrors = @user.validate()
if validationErrors
@user.set('jobProfile',oldJobProfile)
return alert("Please notify team@codecombat.com! There were validation errors from the LinkedIn import: #{JSON.stringify validationErrors}")
else
@render()
getRenderData: -> getRenderData: ->
context = super() context = super()
context.userID = @userID context.userID = @userID
context.linkedInAuthorized = @authorizedWithLinkedIn
context.jobProfileSchema = me.schema().properties.jobProfile context.jobProfileSchema = me.schema().properties.jobProfile
if @user and not jobProfile = @user.get 'jobProfile' if @user and not jobProfile = @user.get 'jobProfile'
jobProfile = {} jobProfile = {}
@ -105,6 +241,8 @@ module.exports = class ProfileView extends View
toggleEditing: -> toggleEditing: ->
@editing = not @editing @editing = not @editing
@render() @render()
_.delay @renderLinkedInButton, 1000
@saveEdits()
toggleJobProfileApproved: -> toggleJobProfileApproved: ->
return unless me.isAdmin() return unless me.isAdmin()