-
Notifications
You must be signed in to change notification settings - Fork 460
fix(track): don't drop increment/decrement for a not-yet-created profile #473
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -321,29 +321,33 @@ async function adjustProfileProperty( | |
| ): Promise<void> { | ||
| const { profileId, property, value } = payload; | ||
| const profile = await getProfileById(String(profileId), projectId); | ||
| if (!profile) { | ||
| throw new HttpError('Profile not found', { status: 404 }); | ||
| } | ||
|
|
||
| // A missing profile is not an error. An increment/decrement legitimately | ||
| // fires before the profile's first event or identify has landed (a common | ||
| // race), or for an anonymous/transient id. Follow Mixpanel `$add`/`$subtract` | ||
| // semantics — treat the current value as 0 and upsert — so the delta is | ||
| // preserved and the profile is created, instead of dropping the operation. | ||
| const properties = profile?.properties ?? {}; | ||
|
|
||
| const parsed = Number.parseInt( | ||
| pathOr<string>('0', property.split('.'), profile.properties), | ||
| pathOr<string>('0', property.split('.'), properties), | ||
| 10 | ||
| ); | ||
|
Comment on lines
332
to
335
Contributor
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: printf '%s\n' '--- repository conventions and learnings ---'
find /tmp/coderabbit-repo-knowledge/openpanel-dev-openpanel-916c4053 -maxdepth 2 -type f -name '*.md' -print | sort | head -40
printf '%s\n' '--- target source ---'
sed -n '280,360p' apps/api/src/controllers/track.controller.ts
printf '%s\n' '--- payload declarations and related symbols ---'
rg -n -C 5 'I(Increment|Decrement)Payload|adjustProfileProperty|upsertProfile|profileBuffer' apps packages --glob '*.{ts,tsx,js,jsx}' | head -240
printf '%s\n' '--- Ramda version ---'
rg -n '"ramda"|"`@types/ramda`"' package.json apps packages --glob 'package.json' --glob 'pnpm-lock.yaml' --glob 'yarn.lock' --glob 'package-lock.json'Repository: Openpanel-dev/openpanel Length of output: 22431 🏁 Script executed: sed -n '1,190p' packages/validation/src/track.validation.ts
sed -n '1,90p' apps/api/src/controllers/track.controller.ts
sed -n '360,455p' apps/api/src/controllers/track.controller.tsRepository: Openpanel-dev/openpanel Length of output: 10380 Use strict numeric validation. 🤖 Prompt for AI Agents |
||
|
|
||
| // An existing value that isn't a number can't be adjusted; skip rather than | ||
| // rejecting the request (and never overwrite it with a NaN). | ||
| if (Number.isNaN(parsed)) { | ||
| throw new HttpError('Property value is not a number', { status: 400 }); | ||
| return; | ||
| } | ||
|
|
||
| profile.properties = assocPath( | ||
| property.split('.'), | ||
| parsed + direction * (value || 1), | ||
| profile.properties | ||
| ); | ||
|
|
||
| await upsertProfile({ | ||
| id: profile.id, | ||
| id: profile?.id ?? String(profileId), | ||
| projectId, | ||
| properties: profile.properties, | ||
| properties: assocPath( | ||
| property.split('.'), | ||
| parsed + direction * (value || 1), | ||
| properties | ||
|
Comment on lines
343
to
+349
Contributor
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. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Serialize concurrent adjustments for one profile. Two requests can both read a missing profile, calculate from 🤖 Prompt for AI Agents |
||
| ), | ||
| isExternal: true, | ||
| }); | ||
| } | ||
|
|
||
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.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: Openpanel-dev/openpanel
Length of output: 50380
🏁 Script executed:
Repository: Openpanel-dev/openpanel
Length of output: 50379
🏁 Script executed:
Repository: Openpanel-dev/openpanel
Length of output: 31245
🏁 Script executed:
Repository: Openpanel-dev/openpanel
Length of output: 38496
🌐 Web query:
Ramda 0.29.1 assocPath pathOr behavior non-object intermediate path node💡 Result:
In Ramda 0.29.1, the behaviors of
assocPathandpathOrregarding non-object intermediate paths differ fundamentally based on their purposes: construction vs. retrieval [1][2].assocPathBehavior WhenassocPathencounters a missing or non-object value at an intermediate path level, it creates a new structure to accommodate the specified path [3][2]. To determine what type of structure to create (an Array or an Object),assocPathinspects the next key in the path: If the next key is an integer (e.g.,0,1,4), Ramda creates an Array [4][3]. If the next key is not an integer (e.g.,'a','key'), Ramda creates an Object [3]. For example: R.assocPath(['a', 'b', 'c'], 42, {a: 5}); // => {a: {b: {c: 42}}} (replaces primitive 5 with an object) [1][2] R.assocPath(['a', 1, 'c'], 42, {a: []}); // => {a: [undefined, {c: 42}]} (creates index 1 as an object within an array) [2]pathOrBehaviorpathOris designed for safe data retrieval and does not create structures [5]. If any part of the path is missing or does not exist on the object,pathOrwill immediately return the provided default value [5]. Important nuances forpathOr: It evaluates whether a path exists by checking if the object "has" the property, but its handling ofnull/undefinedvalues has historically been a subject of internal debate [5][6]. It returns the default value if the path cannot be resolved (i.e., it is effectively "missing") [5]. It is generally inconsistent withpropOrregarding how they treat keys that exist but are explicitly set toundefined, a known point of contention in the library [7][5]. Summary of Key Differences: -assocPathis constructive: It forces the object structure to match the provided path by overwriting primitives or missing nodes [1][2]. -pathOris retrieval-only: It never modifies the source object and simply falls back to the default value if the path is not found [5].Citations:
🏁 Script executed:
Repository: Openpanel-dev/openpanel
Length of output: 12398
Preserve non-object intermediate properties.
When
propertyisa.bandproperties.ais"keep",pathOrreturns"0".assocPaththen replacesawith an object, andupsertProfilepersists the replacement throughProfileBuffer. Skip the update when an intermediate path value is not an object. Use0only when the target leaf is absent.🤖 Prompt for AI Agents