-
Notifications
You must be signed in to change notification settings - Fork 528
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
fix(insights): do not throw when sending event right after creating insights middleware #4575
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit b3e0247:
|
@@ -36,6 +36,30 @@ describe('insights', () => { | |||
}; | |||
}; | |||
|
|||
const createUmdTestEnvironment = () => { |
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.
This has been moved here from other describe
scope with no change.
@@ -236,6 +251,7 @@ aa('setUserToken', 'your-user-token');`); | |||
insightsClient, | |||
})({ instantSearchInstance }); | |||
middleware.subscribe(); | |||
libraryLoadedAndProcessQueue(); |
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.
This was missing. We need this because the library will eventually be loaded.
} | ||
|
||
if (Array.isArray((insightsClient as any).queue)) { |
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.
This part has been move to the line 44. And there it doesn't call setUserTokenToSearch()
. It instead sets a variable there, and later on, at the line 95, it reads the variable and sets it.
// At this point, even though `search-insights` is not loaded yet, | ||
// we still want to read the token from the queue. | ||
// Otherwise, the first search call will be fired without the token. | ||
(insightsClient as any).queue.forEach(([method, firstArgument]) => { |
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.
Why using forEach
if we override the variable every time? Using find
would make more sense:
find(insightsClient.queue.slice().reverse(), /* ... */)
slice
to not mutate the queuereverse
to find the latest occurrence
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.
@@ -38,26 +38,35 @@ export const createInsightsMiddleware: CreateInsightsMiddleware = props => { | |||
_insightsClient === null ? (noop as InsightsClient) : _insightsClient; |
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.
If we type it correctly here, we shouldn't have to cast insightsClient
every time.
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.
Do you mean regarding (insightsClient as any).queue
right?
5ddedee
setUserTokenToSearch(anonymousUserToken); | ||
} | ||
|
||
if (queuedUserToken) { |
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.
Why can this condition and the one below be satisfied at the same time? It makes the flow harder to understand.
If that what it means, I'd suggest changing to this:
// We consider the `userToken` coming from a `init` call to have a higher
// importance than the one coming from the queue.
if (userTokenBeforeInit) {
insightsClient('setUserToken', userTokenBeforeInit);
} else if (queuedUserToken) {
insightsClient('setUserToken', queuedUserToken);
}
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.
test/mock/createInsightsClient.ts
Outdated
@@ -26,13 +26,23 @@ export function createAlgoliaAnalytics() { | |||
callback(values._userToken); | |||
} | |||
}; | |||
const sendEvent = () => { |
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.
Why do we implement another whole logic in its mock?
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.
Good point. I thought about it again, and I think the new test is better.
0add3dc
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.
We probably shouldn't even mock any logic, but just spy for methods called. That's coming from before so it shouldn't block that PR though.
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.
At some point, I began to wonder if I just should've used the real search-insights client for the tests, because there are some implementations on the search-insights side that needs to be tested with the insights middleware.
// or | ||
aa('setUserToken', 'your-user-token');`); | ||
// It tries to send an event. | ||
instantSearchInstance.sendEventToInsights({ |
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.
We keep testing internals here, that's fragile. Again, that's how it was before these changes.
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.
Although the method is public, in this context, we're indeed testing the internals. I'll keep it that way for now.
Summary
There is a bug where an error is thrown when sending events right after creating
insights
middleware.Details
It happens only when:
search-insights
is used.search-insights
is not loaded yet at the time of creation ofinsights
middleware.In
insights
middleware, the way it intializesinsightsClient
is asynchronous. If it happens too late (normally it happens when the library itself hasn't loaded yet), sending event will throw an error saying "the insightsClient is not intialized yet".So, in this PR, we change to initialize
insightsClient
synchronously without checking if it was already initialized before.A behavioral change
Before this PR, userToken that was set before creating insights middleware was ignored. However since this PR, that has to change, because although user has a code like the following:
insights
middleware callsaa('init', ...)
anyway. It meansaa(init', ...)
will override the token with an anonymous token. Of course, we can updatesearch-insights
not to override already given token with an anonymous one, but there is no guarantee that user upgrades search-insights correctly. So we cannot rely on it.Since
insights
middleware initializesinsightsClient
, we guide not to callaa('init')
by themselves, but we cannot stop it. And if they set userToken like above, we shouldn't lose the token (although before this PR, we used to ignore the token before creating the middleware).This behavioral change can be considered as a breaking change, but we can also consider it as a "fix" because it is correct to prefer a userToken that was explicitly set by user over an anonymous token.
Alternatives
There are a couple of other options requiring changes in
search-insights
side, but it's risky to have a change in InstantSearch that depends on a change insearch-insights
because we cannot guarantee users will upgrade them both.Future task
This doesn't concern another issue we've recently discussed about, which is to provide an option to disable cookie at all. It will be dealt separately.
Result
insights
middleware, it takes it into account. (It used to not.)