Skip to content

Commit 0d011a1

Browse files
committed
Better handling of basic_info trait
1 parent 9dee885 commit 0d011a1

File tree

1 file changed

+190
-5
lines changed

1 file changed

+190
-5
lines changed

src/scripts/migrate-dynamo-data.js

Lines changed: 190 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,182 @@ async function importElasticSearchMember (filename, dateFilter = null) {
22572257
console.log(`Finished reading the file: ${filename}\n`)
22582258
}
22592259

2260+
/**
2261+
* Import only the Dynamo basic_info traits and upsert them.
2262+
* @param {String} filename filename
2263+
* @param {Date|null} [dateFilter=null] optional date filter threshold
2264+
*/
2265+
async function importDynamoBasicInfoTraits (filename, dateFilter = null) {
2266+
const traitFilePath = path.join(MIGRATE_DIR, filename)
2267+
2268+
const lineCount = await countFileLines(traitFilePath)
2269+
console.log(`${filename} has ${lineCount} lines in total`)
2270+
2271+
const rlRead = readline.createInterface({
2272+
input: fs.createReadStream(traitFilePath),
2273+
crlfDelay: Infinity
2274+
})
2275+
2276+
let currentLine = 0
2277+
let count = 0
2278+
let processed = 0
2279+
let skipped = 0
2280+
let errors = 0
2281+
let stringObject = ''
2282+
2283+
const processBasicInfoRecord = async (dataItem) => {
2284+
if (dataItem.traitId !== 'basic_info') {
2285+
return
2286+
}
2287+
2288+
if (!shouldProcessRecord(dataItem, dateFilter)) {
2289+
skipped += 1
2290+
return
2291+
}
2292+
2293+
let traitsPayload = dataItem.traits
2294+
if (typeof traitsPayload === 'string') {
2295+
try {
2296+
traitsPayload = JSON.parse(traitsPayload)
2297+
} catch (err) {
2298+
errors += 1
2299+
logWarn('Skipping basic_info trait due to invalid JSON payload', {
2300+
userId: dataItem.userId,
2301+
error: err?.message
2302+
})
2303+
return
2304+
}
2305+
}
2306+
2307+
const traitEntries = Array.isArray(traitsPayload?.data) ? traitsPayload.data : []
2308+
if (!traitEntries.length) {
2309+
skipped += 1
2310+
return
2311+
}
2312+
2313+
const normalizedEntries = []
2314+
let targetUserId = normalizeUserId(dataItem.userId)
2315+
2316+
for (const rawEntry of traitEntries) {
2317+
const normalizedEntry = pick(rawEntry || {}, TRAIT_BASIC_INFO)
2318+
const normalizedUserId = normalizeUserId(normalizedEntry.userId || targetUserId)
2319+
if (!normalizedUserId) {
2320+
continue
2321+
}
2322+
targetUserId = targetUserId || normalizedUserId
2323+
normalizedEntries.push({
2324+
userId: normalizedUserId,
2325+
country: normalizedEntry.country || '',
2326+
primaryInterestInTopcoder: normalizedEntry.primaryInterestInTopcoder || '',
2327+
tshirtSize: normalizedEntry.tshirtSize || null,
2328+
gender: normalizedEntry.gender || null,
2329+
shortBio: normalizedEntry.shortBio || '',
2330+
birthDate: _convert2Date(normalizedEntry.birthDate),
2331+
currentLocation: normalizedEntry.currentLocation || null,
2332+
createdBy: CREATED_BY
2333+
})
2334+
}
2335+
2336+
if (normalizedEntries.length === 0 || !targetUserId) {
2337+
skipped += 1
2338+
return
2339+
}
2340+
2341+
const payload = {
2342+
userId: targetUserId,
2343+
memberTraits: {
2344+
basicInfo: normalizedEntries.map(entry => ({
2345+
...entry,
2346+
userId: targetUserId
2347+
}))
2348+
}
2349+
}
2350+
2351+
try {
2352+
const updated = await updateMembersWithTraitsAndSkills(payload)
2353+
if (updated) {
2354+
processed += 1
2355+
} else {
2356+
errors += 1
2357+
logWarn('Basic info trait update returned falsy result', { userId: targetUserId })
2358+
}
2359+
} catch (err) {
2360+
errors += 1
2361+
logError('Failed to upsert basic_info trait', { userId: targetUserId, error: err?.message })
2362+
}
2363+
}
2364+
2365+
for await (const line of rlRead) {
2366+
currentLine += 1
2367+
if (currentLine % 10 === 0) {
2368+
const percentage = ((currentLine / lineCount) * 100).toFixed(2)
2369+
process.stdout.clearLine()
2370+
process.stdout.cursorTo(0)
2371+
process.stdout.write(`Migrate Progress: ${percentage}%, read ${count}, processed ${processed}, skipped ${skipped}, errors ${errors}`)
2372+
}
2373+
2374+
let trimmedLine = line.trim()
2375+
if (!trimmedLine || trimmedLine === ',' || trimmedLine === '[' || trimmedLine === ']' || trimmedLine === '],') {
2376+
continue
2377+
}
2378+
2379+
if (trimmedLine.startsWith('[')) {
2380+
trimmedLine = trimmedLine.substring(1).trim()
2381+
if (!trimmedLine) {
2382+
continue
2383+
}
2384+
}
2385+
if (trimmedLine.endsWith(']')) {
2386+
trimmedLine = trimmedLine.substring(0, trimmedLine.length - 1).trim()
2387+
if (!trimmedLine) {
2388+
continue
2389+
}
2390+
}
2391+
2392+
if (!stringObject) {
2393+
stringObject = trimmedLine
2394+
} else {
2395+
stringObject += trimmedLine
2396+
}
2397+
2398+
let jsonCandidate = stringObject
2399+
if (jsonCandidate.endsWith(',')) {
2400+
jsonCandidate = jsonCandidate.slice(0, -1)
2401+
}
2402+
2403+
let dataItem
2404+
try {
2405+
dataItem = JSON.parse(jsonCandidate)
2406+
} catch (err) {
2407+
continue
2408+
}
2409+
2410+
stringObject = ''
2411+
count += 1
2412+
await processBasicInfoRecord(dataItem)
2413+
}
2414+
2415+
if (stringObject) {
2416+
let jsonCandidate = stringObject
2417+
if (jsonCandidate.endsWith(',')) {
2418+
jsonCandidate = jsonCandidate.slice(0, -1)
2419+
}
2420+
if (jsonCandidate) {
2421+
try {
2422+
const dataItem = JSON.parse(jsonCandidate)
2423+
count += 1
2424+
await processBasicInfoRecord(dataItem)
2425+
} catch (err) {
2426+
errors += 1
2427+
logWarn('Skipping trailing basic_info trait due to invalid JSON payload', { error: err?.message })
2428+
}
2429+
}
2430+
}
2431+
2432+
console.log(`\nProcessed ${processed} basic_info traits, skipped ${skipped}, errors ${errors}`)
2433+
console.log(`Finished reading the file: ${filename}\n`)
2434+
}
2435+
22602436
/**
22612437
* Update member status values from ElasticSearch snapshot
22622438
* @param {String} filename filename
@@ -2412,9 +2588,12 @@ async function fixMemberUpdateData (memberItem, dbItem) {
24122588
}
24132589
} else if (memberItem.traits.traitId === 'basic_info') {
24142590
const traitData = pick(memberItem.traits.data[0], TRAIT_BASIC_INFO)
2415-
if (traitData.userId && traitData.country && traitData.primaryInterestInTopcoder && traitData.shortBio) {
2591+
if (traitData.userId) {
24162592
memberItemUpdate.memberTraits.basicInfo = [{
24172593
...traitData,
2594+
country: traitData.country || '',
2595+
primaryInterestInTopcoder: traitData.primaryInterestInTopcoder || '',
2596+
shortBio: traitData.shortBio || '',
24182597
birthDate: _convert2Date(traitData.birthDate)
24192598
}]
24202599
}
@@ -3714,7 +3893,7 @@ async function runMigrationStep (step, dateFilter, askQuestion) {
37143893
migrationRuntimeState.dateFilter = dateFilter
37153894
destructiveApprovals.delete(step)
37163895

3717-
const shouldRunIntegrityCheck = ['1', '2', '3', '4', '5', '7'].includes(step)
3896+
const shouldRunIntegrityCheck = ['1', '2', '3', '4', '5', '7', '8'].includes(step)
37183897

37193898
try {
37203899
if (shouldRunIntegrityCheck) {
@@ -3842,6 +4021,11 @@ async function runMigrationStep (step, dateFilter, askQuestion) {
38424021
await updateMemberStatusFromElasticSearch(memberElasticsearchFilename, dateFilter)
38434022
break
38444023
}
4024+
case '8': {
4025+
const memberTraitFilename = 'MemberProfileTrait.json'
4026+
await importDynamoBasicInfoTraits(memberTraitFilename, dateFilter)
4027+
break
4028+
}
38454029
default:
38464030
throw new Error(`Unsupported step "${step}"`)
38474031
}
@@ -3878,6 +4062,7 @@ async function main () {
38784062
console.log('5. Import Dynamo MemberStatHistory')
38794063
console.log('6. Import Distribution Stats')
38804064
console.log('7. Update ElasticSearch Member Status')
4065+
console.log('8. Import Dynamo Basic Info Traits')
38814066
console.log('')
38824067
console.log('Destructive clears require --full-reset or ALLOW_DESTRUCTIVE=true and an explicit confirmation. Incremental runs retain existing data by default.')
38834068

@@ -3890,8 +4075,8 @@ async function main () {
38904075

38914076
let selectedStep = null
38924077
try {
3893-
selectedStep = (await askQuestion('Please select your step to run (0-7): ')).trim()
3894-
const validSteps = new Set(['0', '1', '2', '3', '4', '5', '6', '7'])
4078+
selectedStep = (await askQuestion('Please select your step to run (0-8): ')).trim()
4079+
const validSteps = new Set(['0', '1', '2', '3', '4', '5', '6', '7', '8'])
38954080
if (!validSteps.has(selectedStep)) {
38964081
console.log('Unsupported step selected. Script is finished.')
38974082
return
@@ -3905,7 +4090,7 @@ async function main () {
39054090
}
39064091

39074092
let dateFilter = null
3908-
if (['1', '2', '3', '4', '5', '7'].includes(selectedStep)) {
4093+
if (['1', '2', '3', '4', '5', '7', '8'].includes(selectedStep)) {
39094094
const dateFilterInput = (await askQuestion('Enter date filter (YYYY-MM-DD UTC; timestamp-less records will be skipped, or press Enter to skip): ')).trim()
39104095
if (dateFilterInput) {
39114096
const parsed = parseDateFilter(dateFilterInput)

0 commit comments

Comments
 (0)