Skip to content
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

Merged
merged 8 commits into from
Nov 12, 2020

Conversation

eunjae-lee
Copy link
Contributor

@eunjae-lee eunjae-lee commented Nov 9, 2020

Summary

There is a bug where an error is thrown when sending events right after creating insights middleware.

Details

It happens only when:

  • The UMD version of search-insights is used.
  • The search-insights is not loaded yet at the time of creation of insights middleware.

In insights middleware, the way it intializes insightsClient 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:

aa('init', { ... });
aa('setUserToken', 'token-a');

search.use(createInsightsMiddleware({ ... }));

insights middleware calls aa('init', ...) anyway. It means aa(init', ...) will override the token with an anonymous token. Of course, we can update search-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 initializes insightsClient, we guide not to call aa('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 in search-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

  1. When UMD search-insights is not loaded yet, it doesn't throw when sending events. It stores it in the queue and send it later correctly.
  2. If user sets a token before creating insights middleware, it takes it into account. (It used to not.)

@codesandbox-ci
Copy link

codesandbox-ci bot commented Nov 9, 2020

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:

Sandbox Source
InstantSearch.js Configuration

@@ -36,6 +36,30 @@ describe('insights', () => {
};
};

const createUmdTestEnvironment = () => {
Copy link
Contributor Author

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();
Copy link
Contributor Author

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.

@eunjae-lee eunjae-lee changed the title fix: restore user token which was set before creating the middleware fix(insights): do not throw when sending event right after creating insights middleware Nov 9, 2020
}

if (Array.isArray((insightsClient as any).queue)) {
Copy link
Contributor Author

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.

@eunjae-lee eunjae-lee marked this pull request as ready for review November 9, 2020 16:55
// 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]) => {
Copy link
Member

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 queue
  • reverse to find the latest occurrence

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Contributor Author

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) {
Copy link
Member

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);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -26,13 +26,23 @@ export function createAlgoliaAnalytics() {
callback(values._userToken);
}
};
const sendEvent = () => {
Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Contributor Author

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({
Copy link
Member

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.

Copy link
Contributor Author

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.

@eunjae-lee eunjae-lee merged commit d963f8d into master Nov 12, 2020
@eunjae-lee eunjae-lee deleted the fix/insights-init branch November 12, 2020 17:15
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.

2 participants