-
Notifications
You must be signed in to change notification settings - Fork 2
[PROD RELEASE] - Updates & fixes #93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6438727
ef6d5f7
d4e2229
8ed25ee
93de405
ce53399
acf59b6
88cbd1a
60e745f
36afaba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -283,13 +283,26 @@ function createSkillsSubsection (title, verified, notVerified) { | |
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Create one category line in same style as verified/not verified: bullet label + skills on same line | ||
| */ | ||
| function createCategorySkillsBlock (categoryName, skillNames) { | ||
| if (!skillNames || skillNames.length === 0) return null | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| return React.createElement( | ||
| Text, | ||
| { key: `category-${categoryName}`, style: styles.skillsList }, | ||
| React.createElement(Text, { style: styles.skillsLabel }, `• ${categoryName}: `), | ||
| skillNames.join(', ') | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Build the PDF template for member profile | ||
| * @param {Object} pdfData the aggregated PDF data | ||
| * @returns {Object} React element tree | ||
| */ | ||
| function buildProfileTemplate (pdfData) { | ||
| const { member, workExperience, education, languages, basicInfo, skills, topcoderActivity, certifications, courses } = pdfData | ||
| const { member, workExperience, education, languages, basicInfo, skills, skillsByCategory, topcoderActivity, certifications, courses } = pdfData | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
|
|
||
| const children = [] | ||
|
|
||
|
|
@@ -387,30 +400,32 @@ function buildProfileTemplate (pdfData) { | |
| ) | ||
| } | ||
|
|
||
| // Technical Skills Section | ||
| const hasSkills = skills.principal.verified.length > 0 || skills.principal.notVerified.length > 0 || | ||
| skills.additional.verified.length > 0 || skills.additional.notVerified.length > 0 | ||
| if (hasSkills) { | ||
| const skillsContent = [ | ||
| createSectionHeader('TECHNICAL SKILLS') | ||
| ] | ||
|
|
||
| const principalSubsection = createSkillsSubsection( | ||
| 'Principal Skills:', | ||
| skills.principal.verified, | ||
| skills.principal.notVerified | ||
| ) | ||
| if (principalSubsection) { | ||
| skillsContent.push(principalSubsection) | ||
| const hasPrincipalSkills = skills && skills.principal && (skills.principal.verified.length > 0 || skills.principal.notVerified.length > 0) | ||
| const hasAdditionalByCategory = skillsByCategory && skillsByCategory.length > 0 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| if (hasPrincipalSkills || hasAdditionalByCategory) { | ||
| const skillsContent = [createSectionHeader('TECHNICAL SKILLS')] | ||
|
|
||
| if (hasPrincipalSkills) { | ||
| const principalSubsection = createSkillsSubsection( | ||
| 'Principal Skills:', | ||
| skills.principal.verified, | ||
| skills.principal.notVerified | ||
| ) | ||
| if (principalSubsection) skillsContent.push(principalSubsection) | ||
| } | ||
|
|
||
| const additionalSubsection = createSkillsSubsection( | ||
| 'Additional Skills:', | ||
| skills.additional.verified, | ||
| skills.additional.notVerified | ||
| ) | ||
| if (additionalSubsection) { | ||
| skillsContent.push(additionalSubsection) | ||
| if (hasAdditionalByCategory) { | ||
| const additionalItems = skillsByCategory | ||
| .map(item => createCategorySkillsBlock(item.categoryName, item.skills)) | ||
| .filter(Boolean) | ||
| skillsContent.push( | ||
| React.createElement( | ||
| View, | ||
| { key: 'additional-skills-subsection', style: styles.skillsSubsection }, | ||
| React.createElement(Text, { style: styles.skillsSubsectionTitle }, 'Additional Skills:'), | ||
| ...additionalItems | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| children.push( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -489,7 +489,7 @@ async function getProfileCompleteness (currentUser, handle, query) { | |
| const memberTraits = await memberTraitService.getTraits(currentUser, handle, {}) | ||
| // Avoid getting the member stats, since we don't need them here, and performance is | ||
| // better without them | ||
| const memberFields = { 'fields': 'userId,handle,handleLower,photoURL,description,skills,verified,availableForGigs,availableForGigsLastUpdateDate,lastProfileConfirmationDate,updatedAt,addresses' } | ||
| const memberFields = { 'fields': 'userId,handle,handleLower,photoURL,description,skills,verified,lastProfileConfirmationDate,updatedAt,addresses' } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| const member = await getMemberData(handle, memberFields) | ||
|
|
||
| // Used for calculating the percentComplete | ||
|
|
@@ -508,7 +508,7 @@ async function getProfileCompleteness (currentUser, handle, query) { | |
| // TODO: Turn this back on once we have verification flow implemented elsewhere | ||
| // data.verified = false | ||
| data.skills = false | ||
| data.gigAvailability = false | ||
| data.engagementAvailability = false | ||
| data.bio = false | ||
| data.workHistory = false | ||
| data.education = false | ||
|
|
@@ -517,19 +517,13 @@ async function getProfileCompleteness (currentUser, handle, query) { | |
| const totalItems = Object.keys(data).length | ||
|
|
||
| data.skillsLastUpdateDate = undefined | ||
| data.gigAvailabilityLastUpdateDate = undefined | ||
| data.engagementAvailabilityLastUpdateDate = undefined | ||
| data.workHistoryLastUpdateDate = undefined | ||
| data.educationLastUpdateDate = undefined | ||
| data.locationLastUpdateDate = undefined | ||
| data.profileLastUpdateDate = new Date(member.updatedAt).toISOString() | ||
| data.lastProfileConfirmationDate = member.lastProfileConfirmationDate ? new Date(member.lastProfileConfirmationDate).toISOString() : undefined | ||
|
|
||
| if (member.availableForGigs != null) { | ||
| completeItems += 1 | ||
| data.gigAvailability = true | ||
| data.gigAvailabilityLastUpdateDate = member.availableForGigsLastUpdateDate || undefined | ||
| } | ||
|
|
||
| _.forEach(memberTraits, (item) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| if (item.traitId === 'education' && item.traits.data.length > 0 && !data.education) { | ||
| completeItems += 1 | ||
|
|
@@ -542,6 +536,20 @@ async function getProfileCompleteness (currentUser, handle, query) { | |
| data.workHistory = true | ||
| data.workHistoryLastUpdateDate = new Date(item.updatedAt).toISOString() | ||
| } | ||
|
|
||
| if (item.traitId === 'personalization' && item.traits.data.length > 0 && !data.engagementAvailability) { | ||
| const openToWorkTrait = item.traits.data.find(r => Object.keys(r).includes('openToWork')) || {}; | ||
| const openToWorkData = openToWorkTrait.openToWork || {}; | ||
|
|
||
| if (openToWorkData && ( | ||
| !openToWorkData.availability || | ||
| (openToWorkData.preferredRoles && openToWorkData.preferredRoles.length) | ||
| )) { | ||
| completeItems += 1 | ||
| data.engagementAvailability = true | ||
| data.engagementAvailabilityLastUpdateDate = new Date(item.updatedAt).toISOString() | ||
| } | ||
| } | ||
| }) | ||
| // Push on the incomplete traits for picking a random toast to show | ||
| if (!data.education) { | ||
|
|
@@ -550,8 +558,8 @@ async function getProfileCompleteness (currentUser, handle, query) { | |
| if (!data.workHistory) { | ||
| showToast.push('workHistory') | ||
| } | ||
| if (!data.gigAvailability) { | ||
| showToast.push('gigAvailability') | ||
| if (!data.engagementAvailability) { | ||
| showToast.push('engagementAvailability') | ||
| } | ||
|
|
||
| // TODO: Do we use the short bio or the "description" field of the member object? | ||
|
|
@@ -1783,30 +1791,33 @@ async function aggregatePDFData (currentUser, handle) { | |
| // Fetch skills from standardized-skills-api | ||
| const skills = await getMemberSkills(memberData.userId) | ||
|
|
||
| // Separate skills by display mode and verification status | ||
| // Principal skills: same as before (verified / not verified lists) | ||
| const principalSkills = { verified: [], notVerified: [] } | ||
| const additionalSkills = { verified: [], notVerified: [] } | ||
|
|
||
| skills.forEach(skill => { | ||
| const isPrincipal = _.get(skill, 'displayMode.name') === 'principal' | ||
| if (_.get(skill, 'displayMode.name') !== 'principal') return | ||
| const isVerified = _.some(_.get(skill, 'levels', []), level => level.name === 'verified') | ||
| const skillName = skill.name | ||
|
|
||
| if (isPrincipal) { | ||
| if (isVerified) { | ||
| principalSkills.verified.push(skillName) | ||
| } else { | ||
| principalSkills.notVerified.push(skillName) | ||
| } | ||
| if (isVerified) { | ||
| principalSkills.verified.push(skillName) | ||
| } else { | ||
| if (isVerified) { | ||
| additionalSkills.verified.push(skillName) | ||
| } else { | ||
| additionalSkills.notVerified.push(skillName) | ||
| } | ||
| principalSkills.notVerified.push(skillName) | ||
| } | ||
| }) | ||
|
|
||
| // Additional skills: group by category, sort by name, take up to limit per category (env PDF_SKILLS_PER_CATEGORY, default 5) | ||
| const additionalSkills = skills.filter(skill => _.get(skill, 'displayMode.name') !== 'principal') | ||
| const skillsPerCategoryLimit = Math.max(1, parseInt(config.PDF_SKILLS_PER_CATEGORY, 10) || 5) | ||
| const categoryKey = (skill) => (skill.category && skill.category.name) ? skill.category.name : 'Other' | ||
| const byCategory = _.groupBy(additionalSkills, categoryKey) | ||
| const skillsByCategory = _.map(byCategory, (skillList, categoryName) => { | ||
| const names = _.map(skillList, 'name') | ||
| .filter(Boolean) | ||
| .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })) | ||
| .slice(0, skillsPerCategoryLimit) | ||
| return { categoryName, skills: names } | ||
| }).filter(item => item.skills.length > 0) | ||
| skillsByCategory.sort((a, b) => a.categoryName.localeCompare(b.categoryName, undefined, { sensitivity: 'base' })) | ||
|
|
||
| const specialRoles = [] | ||
| const roleMap = { | ||
| 'copilot': 'Copilot', | ||
|
|
@@ -1913,10 +1924,8 @@ async function aggregatePDFData (currentUser, handle) { | |
| shortBio: shortBio | ||
| }, | ||
| // Skills | ||
| skills: { | ||
| principal: principalSkills, | ||
| additional: additionalSkills | ||
| }, | ||
| skills: { principal: principalSkills }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| skillsByCategory, | ||
| // Topcoder activity | ||
| topcoderActivity: { | ||
| specialRole: specialRoles.length > 0 ? `Topcoder Special Role: ${specialRoles.join(', ')}` : null, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[⚠️
correctness]Consider using
parseIntwith a radix of 10 instead ofNumberfor parsingprocess.env.PDF_SKILLS_PER_CATEGORY. This ensures that the conversion is always done in base 10, which can prevent unexpected results if the environment variable is prefixed with a non-decimal number.