-
Notifications
You must be signed in to change notification settings - Fork 3
[PROD RELEASE] - Fixes #64
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
3b57c17
82c0277
f869eec
36ddd0a
6af0c7a
e892488
9e675d4
04fd9f4
d05be89
4eb0d7e
20dc33b
3b592b1
eb31c70
c381b35
0b374c2
b4d7423
f8d0eb8
6b60e84
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 |
|---|---|---|
|
|
@@ -77,7 +77,13 @@ const styles = StyleSheet.create({ | |
| statusBar: { | ||
| backgroundColor: '#000000', | ||
| padding: 8, | ||
| marginBottom: 20 | ||
| marginBottom: 10 | ||
| }, | ||
| statusBarSeparator: { | ||
| height: 1, | ||
| backgroundColor: '#AAAAAA', | ||
| marginTop: 10, | ||
| marginBottom: 10 | ||
| }, | ||
| statusBarText: { | ||
| color: '#FFFFFF', | ||
|
|
@@ -174,7 +180,9 @@ const styles = StyleSheet.create({ | |
| }, | ||
| itemSkills: { | ||
| fontSize: 10, | ||
| marginTop: 2, | ||
| marginTop: 4, | ||
| marginBottom: 6, | ||
| lineHeight: 1.4, | ||
| color: '#000000' | ||
| }, | ||
| bulletPoint: { | ||
|
|
@@ -184,16 +192,25 @@ const styles = StyleSheet.create({ | |
| lineHeight: 1.4, | ||
| color: '#000000' | ||
| }, | ||
| // Work description HTML list alignment (ul/ol/li) | ||
| // Work description HTML: tighten paragraph spacing so typed (p) and pasted (br) look consistent | ||
| descriptionListStylesheet: { | ||
| p: { margin: 0, marginBottom: 2 }, | ||
| ul: { paddingLeft: 15, marginTop: 3, marginBottom: 3 }, | ||
| ol: { paddingLeft: 15, marginTop: 3, marginBottom: 3 }, | ||
| li: { marginBottom: 2 } | ||
| }, | ||
| // Certifications | ||
| // Certifications & Courses | ||
| certificationItem: { | ||
| fontSize: 10, | ||
| marginBottom: 3, | ||
| marginBottom: 8, | ||
| lineHeight: 1.5, | ||
| color: '#000000' | ||
| }, | ||
| courseItem: { | ||
| fontSize: 10, | ||
| marginTop: 10, | ||
| marginBottom: 8, | ||
| lineHeight: 1.5, | ||
| color: '#000000' | ||
| }, | ||
| certificationLabel: { | ||
|
|
@@ -337,15 +354,17 @@ function buildProfileTemplate (pdfData) { | |
| { style: styles.handleInfo }, | ||
| `Topcoder Handle: ${member.handle}${member.createdAt ? ` | Member Since ${new Date(member.createdAt).getFullYear()}` : ''}` | ||
| ), | ||
| member.statusBarText ? React.createElement( | ||
| View, | ||
| { style: styles.statusBar }, | ||
| React.createElement( | ||
| Text, | ||
| { style: styles.statusBarText }, | ||
| member.statusBarText | ||
| ) | ||
| ) : null | ||
| member.statusBarText | ||
| ? React.createElement( | ||
| View, | ||
| { style: styles.statusBar }, | ||
| React.createElement( | ||
| Text, | ||
| { style: styles.statusBarText }, | ||
| member.statusBarText | ||
| ) | ||
| ) | ||
| : React.createElement(View, { style: styles.statusBarSeparator }) | ||
|
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. [ |
||
| ) | ||
| ) | ||
|
|
||
|
|
@@ -567,7 +586,7 @@ function buildProfileTemplate (pdfData) { | |
| certContent.push( | ||
| React.createElement( | ||
| Text, | ||
| { key: 'courses', style: [styles.certificationItem, { marginTop: 5 }] }, | ||
| { key: 'courses', style: styles.courseItem }, | ||
| React.createElement(Text, { style: styles.certificationLabel }, 'Courses: '), | ||
| coursesText | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1756,22 +1756,86 @@ async function getMemberSkill (currentUser, handle, skillId) { | |
| if (skill.activity) { | ||
| const fetchPromises = [] | ||
|
|
||
| // Prepare challenge fetch | ||
| // Prepare challenge fetch – group by resource role, last 3 per role by endDate | ||
| const challengeSources = _.get(skill, 'activity.challenge.sources', []) | ||
| if (challengeSources.length > 0) { | ||
| const challengeIds = challengeSources | ||
|
|
||
| fetchPromises.push( | ||
| challengesPrisma.Challenge.findMany({ | ||
| where: { id: { in: challengeIds.slice(0, 3) } }, | ||
| select: { id: true, name: true } | ||
| }).then(dbChallenges => { | ||
| Promise.all([ | ||
| resourcesPrisma.resource.findMany({ | ||
|
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. [ |
||
| where: { | ||
| memberId: String(member.userId), | ||
| challengeId: { in: challengeIds } | ||
| }, | ||
| select: { challengeId: true, resourceRole: { select: { name: true } } } | ||
| }), | ||
| // Get challenge details (including endDate for ordering) from challenges DB | ||
| challengesPrisma.Challenge.findMany({ | ||
| where: { id: { in: challengeIds } }, | ||
| select: { | ||
| id: true, | ||
| name: true, | ||
| endDate: true, | ||
| taskIsTask: true, | ||
| winners: { | ||
| where: { userId: helper.bigIntToNumber(member.userId) }, | ||
| select: { userId: true } | ||
| } | ||
| } | ||
| }) | ||
| ]).then(([resources, dbChallenges]) => { | ||
| const roleMap = new Map() | ||
| resources.forEach(resource => { | ||
| if (!roleMap.has(resource.challengeId)) { | ||
| roleMap.set(resource.challengeId, new Set()) | ||
| } | ||
| roleMap.get(resource.challengeId).add(resource.resourceRole.name) | ||
| }) | ||
| const challengeMap = new Map(dbChallenges.map(c => [c.id, c])) | ||
| skill.activity.challenge = { | ||
| count: challengeIds.length, | ||
| lastSources: challengeIds | ||
| .map(id => challengeMap.get(id)) | ||
| .filter(Boolean) | ||
| const winnerSet = new Set( | ||
| dbChallenges | ||
| .filter(c => c.winners && c.winners.length > 0) | ||
| .map(c => c.id) | ||
| ) | ||
|
|
||
| // Group challenges by role | ||
| const groups = {} | ||
| for (const challengeId of challengeIds) { | ||
| const challenge = challengeMap.get(challengeId) | ||
| if (challenge) { | ||
| const roles = roleMap.get(challengeId) | ||
| const roleNames = roles && roles.size | ||
| ? Array.from(roles).map(role => ( | ||
| role === 'Submitter' && winnerSet.has(challengeId) | ||
| ? 'Winner' | ||
| : role | ||
| )) | ||
| : [challenge?.taskIsTask ? 'Task' : 'Unknown'] | ||
|
|
||
| roleNames.forEach(roleName => { | ||
| if (!groups[roleName]) groups[roleName] = [] | ||
| groups[roleName].push(challenge) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // For each role: sort by endDate desc, keep last 3, include total count | ||
| skill.activity.challenge = Object.fromEntries( | ||
| Object.entries(groups).map(([role, challenges]) => { | ||
| const sorted = challenges.sort((a, b) => | ||
|
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. [ |
||
| new Date(b.endDate || 0) - new Date(a.endDate || 0) | ||
| ) | ||
| return [role, { | ||
| count: sorted.length, | ||
|
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. [💡 |
||
| lastSources: sorted.slice(0, 3).map(c => ({ | ||
| id: c.id, | ||
| name: c.name, | ||
| role | ||
| })) | ||
| }] | ||
| }) | ||
| ) | ||
| }) | ||
| ) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -287,6 +287,21 @@ async function validateWorkAssociatedSkills (skillIds) { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Remove a field if it's provided as an empty/blank string. | ||
| * @param {Object} item object containing data fields | ||
| * @param {String} key field name | ||
| */ | ||
| function omitBlankStringField (item, key) { | ||
| if (!Object.prototype.hasOwnProperty.call(item, key)) { | ||
| return | ||
| } | ||
|
|
||
| if (_.isString(item[key]) && _.trim(item[key]) === '') { | ||
| delete item[key] | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Build prisma data for creating/updating traits | ||
| * @param {Object} data query data | ||
|
|
@@ -344,12 +359,20 @@ function buildTraitPrismaData (data, operatorId, result) { | |
| if (t.timePeriodTo && !t.endDate) { | ||
| t.endDate = new Date(t.timePeriodTo) | ||
| } | ||
| // industry is optional; treat blank values as omitted | ||
| omitBlankStringField(t, 'industry') | ||
| omitBlankStringField(t, 'otherIndustry') | ||
| if (t.industry !== 'Other') { | ||
|
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. [ |
||
| delete t.otherIndustry | ||
| } | ||
| // Remove unknown keys that Prisma model does not accept | ||
| delete t.company | ||
| delete t.timePeriodFrom | ||
| delete t.timePeriodTo | ||
| return t | ||
| }) | ||
| // Keep downstream response/event payloads aligned with normalized DB payload | ||
| item.traits.data = payload | ||
| } | ||
|
|
||
| _.forEach(payload, t => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -214,6 +214,28 @@ describe('member trait service unit tests', () => { | |
| // should.equal(result[0].updatedBy, 'sub2') | ||
| }) | ||
|
|
||
| it('update member traits successfully when industry is blank', async () => { | ||
| await service.updateTraits({ isMachine: true, sub: 'sub2' }, member1.handle, [{ | ||
| traitId: 'work', | ||
| categoryName: 'Work', | ||
| traits: { | ||
| traitId: 'work', | ||
| data: [{ | ||
| industry: ' ', | ||
| companyName: 'JP Morgan 3', | ||
| position: 'Manager 3' | ||
| }] | ||
| } | ||
| }]) | ||
|
|
||
| const traits = await service.getTraits({}, member1.handle, { traitIds: 'work' }) | ||
| should.equal(traits.length, 1) | ||
| should.equal(traits[0].traitId, 'work') | ||
| should.equal(traits[0].traits.data.length, 1) | ||
| should.equal(traits[0].traits.data[0].companyName, 'JP Morgan 3') | ||
| should.not.equal(traits[0].traits.data[0].industry, '') | ||
|
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. [ |
||
| }) | ||
|
|
||
| it('update member traits - trait not found', async () => { | ||
| try { | ||
| await service.updateTraits({ isMachine: true, sub: 'sub1' }, member1.handle, [{ | ||
|
|
||
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.
[💡
maintainability]The use of optional chaining (
?.) infirst.skill.skillEvents?.lengthis a good improvement for safety, but the subsequent_.orderBy(first.skill.skillEvents || [], 'createdAt', 'desc')still uses a fallback to an empty array. Consider removing the fallback since the optional chaining already ensuresskillEventsis either defined orundefined. This will make the code cleaner and avoid unnecessary operations.