Skip to content

[CDX-201] Tracking other related parameters#459

Open
Alexey-Pavlov wants to merge 2 commits into
masterfrom
feat/CDX-201-track-window-parameters
Open

[CDX-201] Tracking other related parameters#459
Alexey-Pavlov wants to merge 2 commits into
masterfrom
feat/CDX-201-track-window-parameters

Conversation

@Alexey-Pavlov

Copy link
Copy Markdown
Contributor

This pull request introduces support for using global window variables as fallback values for userId, testCells, and userSegments in the main query modules (search, browse, autocomplete, and recommendations) when the new trackWindowParameters option is enabled. It also ensures that the tracker module does not use these window globals, and adds comprehensive tests to validate this behavior. Additionally, a utility function for applying these window-based parameter getters is introduced and thoroughly tested.

@Alexey-Pavlov Alexey-Pavlov requested a review from a team June 5, 2026 18:27
…ing options and improve handling of window parameters
@Alexey-Pavlov Alexey-Pavlov marked this pull request as ready for review June 19, 2026 17:10
Copilot AI review requested due to automatic review settings June 19, 2026 17:10
@Alexey-Pavlov Alexey-Pavlov requested a review from a team as a code owner June 19, 2026 17:10

@constructor-claude-bedrock constructor-claude-bedrock Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This PR adds trackWindowParameters support so that userId, testCells, and userSegments from window.cnstrc / legacy window globals are used as live fallbacks in query modules, while intentionally excluding the tracker module. The approach is clever but has a few reliability and correctness concerns worth addressing before merging.

Inline comments: 6 discussions added

Overall Assessment: ⚠️ Needs Work

Comment thread src/constructorio.js
trackWindowParameters: trackWindowParameters || false,
};

this.trackerOptions = { ...this.options };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important Issue: The spread { ...this.options } runs before applyWindowParameterGetters is called on this.options (three lines later). At spread time the properties are still plain data values, so trackerOptions ends up as a plain-value snapshot — this is actually the desired behavior (tracker should not see window globals). However, this correctness relies entirely on execution order and is non-obvious to future maintainers.

More importantly, there is a subtle bug: applyWindowParameterGetters installs live getter descriptors only on this.options. When setClientOptions later calls setOption('userId', userId) it writes this.options[key] = value, which invokes the setter of the getter-property and stores the new value in the backing closure — that's correct. But it also writes this.trackerOptions[key] = value (line 185), which correctly updates the plain property on trackerOptions. The two objects staying in sync is fragile: any future property added to setOption paths must remember to also update trackerOptions. Consider adding a comment explaining the intentional split, or making the pattern more explicit.

});

it('Should not include window globals when trackWindowParameters is false', (done) => {
window.cnstrc = { userId: 'window-user-id', testCells: { exp: 'var' }, userSegments: ['seg'] };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Several test cases set window.cnstrc and then clean it up inside the .then() callback using delete window.cnstrc. If the then() callback throws before the delete (e.g., a failed assertion), or if the Promise rejects, window.cnstrc will leak into subsequent tests.

Consider using afterEach to clean up window globals, similar to the pattern already used in spec/src/utils/helpers.js for the applyWindowParameterGetters tests:

afterEach(() => { delete window.cnstrc; });

This pattern appears in all four module test files (search.js, browse.js, autocomplete.js, recommendations.js) and tracker.js.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an opt-in trackWindowParameters client option that, when enabled in DOM environments, falls back to specific window globals for userId, testCells, and segments across the main query modules (search/browse/autocomplete/recommendations), while ensuring the tracker module does not use those window fallbacks. It also expands test coverage for these behaviors.

Changes:

  • Added helpers.applyWindowParameterGetters to implement window-global fallbacks for userId, testCells, and segments.
  • Added trackWindowParameters option wiring in the client constructor, and ensured tracker uses a separate options object.
  • Added recommendations support for testCells query params and added extensive tests for window fallback behavior across modules.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/utils/helpers.js Adds applyWindowParameterGetters to provide window-based fallback getters for user-related options.
src/types/index.d.ts Adds trackWindowParameters?: boolean to the public TS options type.
src/modules/recommendations.js Adds testCellsef-* query param support for recommendations requests.
src/constructorio.js Wires trackWindowParameters into client options and isolates tracker from window fallbacks.
spec/src/utils/helpers.js Adds unit tests for applyWindowParameterGetters behavior and priority rules.
spec/src/modules/tracker.js Adds tests ensuring tracker does not include window-global fallbacks, but still respects explicit option updates.
spec/src/modules/search.js Adds tests verifying search requests include/exclude window globals depending on trackWindowParameters.
spec/src/modules/recommendations.js Adds tests verifying recommendations requests include/exclude window globals depending on trackWindowParameters.
spec/src/modules/browse.js Adds tests verifying browse requests include/exclude window globals depending on trackWindowParameters.
spec/src/modules/autocomplete.js Adds tests verifying autocomplete requests include/exclude window globals depending on trackWindowParameters.
cspell.json Adds cnstrc to the dictionary.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/utils/helpers.js
Comment on lines +393 to +398
applyWindowParameterGetters: (options) => {
const backing = {
userId: options.userId || undefined,
testCells: options.testCells || undefined,
segments: options.segments || undefined,
};
Comment thread src/constructorio.js
beaconMode: (beaconMode === false) ? false : true, // Defaults to 'true',
networkParameters: networkParameters || {},
humanityCheckLocation: humanityCheckLocation || 'session',
trackWindowParameters: trackWindowParameters || false,
Comment thread src/constructorio.js

this.trackerOptions = { ...this.options };

if (trackWindowParameters && helpers.canUseDOM()) {

@esezen esezen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. Does the tracker need window params too?

Right now trackerOptions is snapshotted before applyWindowParameterGetters runs (src/constructorio.js:147), so the tracker module is deliberately excluded from window fallbacks — search/browse/autocomplete/recommendations get them, tracking events don't. Why are we doing this? I thought we wanted to include tracker in this implementation as well.

2. Renaming trackWindowParameters

The track prefix collides with the SDK's tracker vocabulary. What do you think about renaming it? Some options I thought about:

  • useWindowParameters
  • useDefaultWindowParameters
  • fallbackToWindowParameters

Comment thread src/constructorio.js
trackWindowParameters: trackWindowParameters || false,
};

this.trackerOptions = { ...this.options };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spread { ...this.options } copies plain values before applyWindowParameterGetters installs the getters three lines down. Why do we want to exclude tracker here?

Comment thread src/constructorio.js
setClientOptions(options) {
if (Object.keys(options).length) {
const { apiKey, segments, testCells, sessionId, userId, sendTrackingEvents } = options;
const setOption = (key, value) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is dependant on the previous comment but because there are now two independent options objects, every update has to write to both. Any future property added to a setClientOptions path must remember to update trackerOptions too, or the two drift apart silently.

Comment thread src/utils/helpers.js
return filtered;
},

applyWindowParameterGetters: (options) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper is exported and its getters reference window directly. It's only guarded by the canUseDOM() check at the call site in constructorio.js. If it's ever called independently in a DOM-less context (or a test forgets JSDOM), reading options.userId/testCells/segments throws ReferenceError. What do you think about adding canUseDOM() no-op guard inside the helper so it's safe on its own.

Comment thread src/utils/helpers.js
segments: options.segments || undefined,
};

Object.defineProperty(options, 'userId', {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a good amount of repeating code with these three. What do you think about extracting some of that logic into a helper?

Comment thread src/constructorio.js
beaconMode: (beaconMode === false) ? false : true, // Defaults to 'true',
networkParameters: networkParameters || {},
humanityCheckLocation: humanityCheckLocation || 'session',
trackWindowParameters: trackWindowParameters || false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trackWindowParameters || false treats any truthy non-boolean (e.g. the string 'false') as enabled. And the gate at line 149 reads the raw constructor arg, not the normalized value — so even after normalizing this.options, the feature can still switch on from a bad input. Can we do trackWindowParameters === true?

search.getSearchResults(query, { section }).then(() => {
const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy);
expect(requestedUrlParams).to.have.property('ui').to.equal('window-user-id');
delete window.cnstrc;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several tests set window.cnstrc and clean it up with delete window.cnstrc inside the .then() callback. If an assertion throws first (or the promise rejects), the global leaks into subsequent tests. Can we move cleanup to afterEach?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants