-
Notifications
You must be signed in to change notification settings - Fork 731
fix: member attributes should have non empty values and a filled default #3449
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
Merged
+321
−18
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
3f76e55
fix: use platform instead of default for incorrectly formatted attrib…
themarolt df455de
chore: script to fix the corrupted attributes
themarolt 62eb163
chore: fixed small issues
themarolt d343400
chore: fixed small issues
themarolt 174826b
chore: fixed small issues
themarolt 10cd7f2
chore: fixed small issues
themarolt 01b07a5
chore: fixed small issues
themarolt cd2e527
chore: fixed small issues
themarolt 31bfc8a
chore: fixed small issues
themarolt 2ad563f
chore: fixed small issues
themarolt fd4f319
chore: fixed small issues
themarolt b7d09d8
chore: fixed small issues
themarolt eb16c58
chore: fixed small issues
themarolt c080a4d
chore: fixed small issues
themarolt 6033af5
chore: fixed small issues
themarolt d99c730
chore: fixed small issues
themarolt 6e6f8c7
chore: fixed small issues
themarolt 8068e9b
chore: fixed small issues
themarolt 7989f15
chore: fixed small issues
themarolt 8c802ba
chore: fixed small issues
themarolt f1b502c
fix: lint
themarolt bd5d1ca
fix: include manuallyChangedFields
themarolt b444f81
fix: comments
themarolt 6a1d5ec
fix: ignore default platform
themarolt b2520da
Merge branch 'main' into member-attribute-fix-CM-705
themarolt faeeceb
fix: bugfix
themarolt 808425f
fix: bugfix
themarolt 8775bd9
Merge branch 'main' into member-attribute-fix-CM-705
themarolt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
293 changes: 293 additions & 0 deletions
293
services/apps/data_sink_worker/src/bin/fix-member-attributes.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,293 @@ | ||
| import isEqual from 'lodash.isequal' | ||
| import mergeWith from 'lodash.mergewith' | ||
|
|
||
| import { connQx, updateMember } from '@crowd/data-access-layer' | ||
| import { | ||
| DbConnOrTx, | ||
| DbStore, | ||
| WRITE_DB_CONFIG, | ||
| getDbConnection, | ||
| } from '@crowd/data-access-layer/src/database' | ||
| import { getServiceLogger } from '@crowd/logging' | ||
| import { REDIS_CONFIG, getRedisClient } from '@crowd/redis' | ||
|
|
||
| import MemberAttributeService from '../service/memberAttribute.service' | ||
|
|
||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
|
||
| const BATCH_SIZE = process.env.TEST_RUN ? 1 : 5000 | ||
|
|
||
| const log = getServiceLogger() | ||
|
|
||
| async function getMemberIds( | ||
| db: DbConnOrTx, | ||
| lastId?: string, | ||
| ): Promise<{ id: string; attributes: any; manuallyChangedFields: any }[]> { | ||
| try { | ||
| log.debug({ lastId }, 'Querying for members with attribute issues') | ||
|
|
||
| const results = await db.any( | ||
| ` | ||
| with relevant_members as (with member_with_attributes as (select id, | ||
| "createdAt", | ||
| jsonb_object_keys(attributes) as attr_key, | ||
| attributes -> jsonb_object_keys(attributes) as attr_value | ||
| from members | ||
| where "deletedAt" is null | ||
| and attributes is not null | ||
| and attributes != 'null'::jsonb | ||
| and attributes != '{}'::jsonb) | ||
| select distinct id | ||
| from member_with_attributes | ||
| where jsonb_typeof(attr_value) = 'object' | ||
| and coalesce(attr_value ->> 'default', '') = '' | ||
| and exists (select 1 | ||
| from jsonb_each_text(attr_value) as kv | ||
| where kv.key != 'default' | ||
| and coalesce(kv.value, '') != '') | ||
| ${lastId ? `and id < '${lastId}'` : ''} | ||
| order by id desc | ||
| limit ${BATCH_SIZE}) | ||
| select m.id, m.attributes, m."manuallyChangedFields" | ||
| from members m | ||
| inner join | ||
| relevant_members rm on rm.id = m.id | ||
| order by m.id desc; | ||
| `, | ||
| { lastId }, | ||
| ) | ||
|
|
||
| log.debug( | ||
| { | ||
| resultCount: results.length, | ||
| lastId, | ||
| firstId: results.length > 0 ? results[0].id : null, | ||
| lastResultId: results.length > 0 ? results[results.length - 1].id : null, | ||
| }, | ||
| 'Query completed', | ||
| ) | ||
|
|
||
| return results | ||
| } catch (error) { | ||
| log.error( | ||
| { | ||
| error: error.message, | ||
| lastId, | ||
| stack: error.stack, | ||
| }, | ||
| 'Failed to query member IDs', | ||
| ) | ||
| throw error | ||
| } | ||
| } | ||
|
|
||
| setImmediate(async () => { | ||
| let dbClient: DbConnOrTx | undefined | ||
| let redisClient: any | undefined | ||
| let exitCode = 0 | ||
|
|
||
| try { | ||
| log.info('Starting member attributes fix script') | ||
|
|
||
| // Initialize connections | ||
| log.info('Connecting to database...') | ||
| dbClient = await getDbConnection(WRITE_DB_CONFIG()) | ||
| log.info('Database connection established') | ||
|
|
||
| log.info('Connecting to Redis...') | ||
| redisClient = await getRedisClient(REDIS_CONFIG()) | ||
| log.info('Redis connection established') | ||
|
|
||
| const pgQx = connQx(dbClient) | ||
| const mas = new MemberAttributeService(redisClient, new DbStore(log, dbClient), log) | ||
|
|
||
| let totalProcessed = 0 | ||
| let totalUpdated = 0 | ||
| let batchNumber = 1 | ||
|
|
||
| log.info('Starting to process members with attribute issues') | ||
| let membersToFix = await getMemberIds(dbClient) | ||
| log.info({ count: membersToFix.length }, 'Found members to process in first batch') | ||
|
|
||
| while (membersToFix.length > 0) { | ||
| log.info({ batchNumber, batchSize: membersToFix.length }, 'Processing batch') | ||
| let batchUpdated = 0 | ||
|
|
||
| for (const data of membersToFix) { | ||
| try { | ||
| if (data.attributes) { | ||
| log.debug( | ||
| { memberId: data.id, oldAttributes: data.attributes }, | ||
| 'Processing member attributes', | ||
| ) | ||
|
|
||
| // check if any has default empty but other are full | ||
| let toProcess = false | ||
| for (const attName of Object.keys(data.attributes)) { | ||
| const defValue = data.attributes[attName].default | ||
|
|
||
| if (defValue === undefined || defValue === null || String(defValue) === '') { | ||
| log.debug( | ||
| { | ||
| memberId: data.id, | ||
| attribute: data.attributes[attName], | ||
| attName, | ||
| defValue: defValue ? String(defValue) : 'undefined', | ||
| }, | ||
| 'Attribute has default empty', | ||
| ) | ||
| for (const platform of Object.keys(data.attributes[attName]).filter( | ||
| (p) => p !== 'default', | ||
| )) { | ||
| const value = data.attributes[attName][platform] | ||
|
|
||
| if (value !== undefined && value !== null && String(value) !== '') { | ||
| log.debug( | ||
| { memberId: data.id, attName, platform, value }, | ||
| 'Found value for attribute', | ||
| ) | ||
| toProcess = true | ||
| break | ||
| } | ||
| } | ||
|
|
||
| if (toProcess) { | ||
| break | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (toProcess) { | ||
| const oldAttributes = JSON.parse(JSON.stringify(data.attributes)) // Deep copy | ||
| data.attributes = await mas.setAttributesDefaultValues(data.attributes) | ||
|
|
||
| let attributes: Record<string, unknown> | undefined | ||
| const temp = mergeWith({}, oldAttributes, data.attributes) | ||
| const manuallyChangedFields: string[] = data.manuallyChangedFields || [] | ||
|
|
||
| if (manuallyChangedFields.length > 0) { | ||
| log.warn( | ||
| { | ||
| memberId: data.id, | ||
| manuallyChangedFieldsCount: manuallyChangedFields.length, | ||
| }, | ||
| 'Member has manually changed fields', | ||
| ) | ||
|
|
||
| const prefix = 'attributes.' | ||
| const manuallyChangedAttributes = [ | ||
| ...new Set( | ||
| manuallyChangedFields | ||
| .filter((f) => f.startsWith(prefix)) | ||
| .map((f) => f.slice(prefix.length)), | ||
| ), | ||
| ] | ||
|
|
||
| log.warn( | ||
| { | ||
| memberId: data.id, | ||
| manuallyChangedAttributes, | ||
| }, | ||
| 'Preserving manually changed attributes', | ||
| ) | ||
|
|
||
| // Preserve manually changed attributes | ||
| for (const key of manuallyChangedAttributes) { | ||
| if (oldAttributes?.[key] !== undefined) { | ||
| temp[key] = oldAttributes[key] // Fixed: removed .attributes | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (!isEqual(temp, oldAttributes)) { | ||
| attributes = temp | ||
| log.info({ memberId: data.id }, 'Attributes changed, will update') | ||
| } else { | ||
| log.debug( | ||
| { memberId: data.id, newAttributes: temp, oldAttributes }, | ||
| 'No changes needed for attributes', | ||
| ) | ||
| } | ||
|
|
||
| if (attributes) { | ||
| log.info({ memberId: data.id }, 'Updating member attributes') | ||
|
|
||
| if (!process.env.TEST_RUN) { | ||
| await updateMember(pgQx, data.id, { attributes } as any) | ||
| } | ||
|
|
||
| batchUpdated++ | ||
| totalUpdated++ | ||
| log.debug({ memberId: data.id }, 'Member attributes updated successfully') | ||
| } | ||
| } else { | ||
| log.debug( | ||
| { memberId: data.id, attributes: data.attributes }, | ||
| 'No changes needed for attributes', | ||
| ) | ||
| } | ||
| } else { | ||
| log.debug({ memberId: data.id }, 'Member has no attributes to process') | ||
| } | ||
|
|
||
| totalProcessed++ | ||
| } catch (error) { | ||
| log.error( | ||
| { | ||
| error: error.message, | ||
| memberId: data.id, | ||
| stack: error.stack, | ||
| }, | ||
| 'Failed to process member', | ||
| ) | ||
| // Continue processing other members | ||
| } | ||
| } | ||
|
|
||
| log.info( | ||
| { | ||
| batchNumber, | ||
| batchProcessed: membersToFix.length, | ||
| batchUpdated, | ||
| totalProcessed, | ||
| totalUpdated, | ||
| }, | ||
| 'Completed batch processing', | ||
| ) | ||
|
|
||
| // Get next batch | ||
| const lastId = membersToFix[membersToFix.length - 1].id | ||
| log.debug({ lastId }, 'Fetching next batch starting from last ID') | ||
|
|
||
| if (process.env.TEST_RUN) { | ||
| break | ||
| } | ||
|
|
||
| membersToFix = await getMemberIds(dbClient, lastId) | ||
| log.info({ count: membersToFix.length }, 'Found members for next batch') | ||
|
|
||
| batchNumber++ | ||
| } | ||
|
|
||
| log.info( | ||
| { | ||
| totalProcessed, | ||
| totalUpdated, | ||
| totalBatches: batchNumber - 1, | ||
| }, | ||
| 'Member attributes fix completed successfully', | ||
| ) | ||
| } catch (error) { | ||
| log.error( | ||
| { | ||
| error: error.message, | ||
| stack: error.stack, | ||
| }, | ||
| 'Fatal error in member attributes fix script', | ||
| ) | ||
| exitCode = 1 | ||
| } finally { | ||
| log.info('Script execution completed') | ||
| process.exit(exitCode) | ||
| } | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.