Skip to content

Commit

Permalink
feat(insights): support authenticatedUserToken (#1233)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Sarah Dayan <[email protected]>
  • Loading branch information
dhayab and sarahdayan authored Jan 17, 2024
1 parent e741fe1 commit bd398ee
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe('createAutocomplete', () => {
insights: { insightsClient },
});

expect(insightsClient).toHaveBeenCalledTimes(3);
expect(insightsClient).toHaveBeenCalledTimes(5);
expect(insightsClient).toHaveBeenCalledWith(
'addAlgoliaAgent',
'insights-plugin'
Expand Down Expand Up @@ -168,7 +168,7 @@ describe('createAutocomplete', () => {
});

expect(defaultInsightsClient).toHaveBeenCalledTimes(0);
expect(userInsightsClient).toHaveBeenCalledTimes(3);
expect(userInsightsClient).toHaveBeenCalledTimes(5);
expect(userInsightsClient).toHaveBeenCalledWith(
'addAlgoliaAgent',
'insights-plugin'
Expand Down
7 changes: 4 additions & 3 deletions packages/autocomplete-js/src/__tests__/autocomplete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ See: https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocom
algoliaInsightsPlugin: expect.objectContaining({
insights: expect.objectContaining({
init: expect.any(Function),
setAuthenticatedUserToken: expect.any(Function),
setUserToken: expect.any(Function),
clickedObjectIDsAfterSearch: expect.any(Function),
clickedObjectIDs: expect.any(Function),
Expand Down Expand Up @@ -751,16 +752,16 @@ See: https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocom
insights: { insightsClient: defaultInsightsClient },
});

expect(defaultInsightsClient).toHaveBeenCalledTimes(3);
expect(defaultInsightsClient).toHaveBeenCalledTimes(5);
expect(userInsightsClient).toHaveBeenCalledTimes(0);

const insightsPlugin = createAlgoliaInsightsPlugin({
insightsClient: userInsightsClient,
});
update({ plugins: [insightsPlugin] });

expect(defaultInsightsClient).toHaveBeenCalledTimes(3);
expect(userInsightsClient).toHaveBeenCalledTimes(3);
expect(defaultInsightsClient).toHaveBeenCalledTimes(5);
expect(userInsightsClient).toHaveBeenCalledTimes(5);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ describe('createAlgoliaInsightsPlugin', () => {
algoliaInsightsPlugin: expect.objectContaining({
insights: expect.objectContaining({
init: expect.any(Function),
setAuthenticatedUserToken: expect.any(Function),
setUserToken: expect.any(Function),
clickedObjectIDsAfterSearch: expect.any(Function),
clickedObjectIDs: expect.any(Function),
Expand All @@ -91,7 +92,7 @@ describe('createAlgoliaInsightsPlugin', () => {

createPlayground(createAutocomplete, { plugins: [insightsPlugin] });

expect(insightsClient).toHaveBeenCalledTimes(3);
expect(insightsClient).toHaveBeenCalledTimes(5);
expect(insightsClient).toHaveBeenCalledWith(
'addAlgoliaAgent',
'insights-plugin'
Expand Down Expand Up @@ -180,54 +181,6 @@ describe('createAlgoliaInsightsPlugin', () => {
]);
});

test('forwards `userToken` from Search Insights to Algolia API requests', async () => {
const insightsPlugin = createAlgoliaInsightsPlugin({ insightsClient });

const searchClient = createSearchClient({
search: jest.fn(() =>
Promise.resolve(
createMultiSearchResponse({
hits: [{ objectID: '1' }],
})
)
),
});

insightsClient('setUserToken', 'customUserToken');

const playground = createPlayground(createAutocomplete, {
plugins: [insightsPlugin],
getSources({ query }) {
return [
{
sourceId: 'hits',
getItems() {
return getAlgoliaResults({
searchClient,
queries: [{ indexName: 'indexName', query }],
});
},
templates: {
item({ item }) {
return item.objectID;
},
},
},
];
},
});

userEvent.type(playground.inputElement, 'a');
await runAllMicroTasks();

expect(searchClient.search).toHaveBeenCalledTimes(1);
expect(searchClient.search).toHaveBeenCalledWith([
expect.objectContaining({
params: expect.objectContaining({ userToken: 'customUserToken' }),
}),
]);
});

test('does not call `init` if `insightsInitParams` not passed', () => {
const insightsClient = jest.fn();
createAlgoliaInsightsPlugin({
Expand All @@ -250,6 +203,173 @@ describe('createAlgoliaInsightsPlugin', () => {
});
});

describe('user token', () => {
afterEach(() => {
insightsClient('setAuthenticatedUserToken', undefined);
});

test('forwards `userToken` from Search Insights to Algolia API requests', async () => {
const insightsPlugin = createAlgoliaInsightsPlugin({ insightsClient });

const searchClient = createSearchClient({
search: jest.fn(() =>
Promise.resolve(
createMultiSearchResponse({
hits: [{ objectID: '1' }],
})
)
),
});

insightsClient('setUserToken', 'customUserToken');

const playground = createPlayground(createAutocomplete, {
plugins: [insightsPlugin],
getSources({ query }) {
return [
{
sourceId: 'hits',
getItems() {
return getAlgoliaResults({
searchClient,
queries: [{ indexName: 'indexName', query }],
});
},
templates: {
item({ item }) {
return item.objectID;
},
},
},
];
},
});

userEvent.type(playground.inputElement, 'a');
await runAllMicroTasks();

expect(searchClient.search).toHaveBeenCalledTimes(1);
expect(searchClient.search).toHaveBeenCalledWith([
expect.objectContaining({
params: expect.objectContaining({ userToken: 'customUserToken' }),
}),
]);
});

test('forwards `authenticatedUserToken` from Search Insights to Algolia API requests', async () => {
const insightsPlugin = createAlgoliaInsightsPlugin({ insightsClient });

const searchClient = createSearchClient({
search: jest.fn(() =>
Promise.resolve(
createMultiSearchResponse({
hits: [{ objectID: '1' }],
})
)
),
});

insightsClient('setAuthenticatedUserToken', 'customAuthUserToken');

const playground = createPlayground(createAutocomplete, {
plugins: [insightsPlugin],
getSources({ query }) {
return [
{
sourceId: 'hits',
getItems() {
return getAlgoliaResults({
searchClient,
queries: [{ indexName: 'indexName', query }],
});
},
templates: {
item({ item }) {
return item.objectID;
},
},
},
];
},
});

userEvent.type(playground.inputElement, 'a');
await runAllMicroTasks();

expect(searchClient.search).toHaveBeenCalledTimes(1);
expect(searchClient.search).toHaveBeenCalledWith([
expect.objectContaining({
params: expect.objectContaining({ userToken: 'customAuthUserToken' }),
}),
]);
});

test('uses `authenticatedUserToken` in priority over `userToken`', async () => {
const insightsPlugin = createAlgoliaInsightsPlugin({
insightsClient,
insightsInitParams: {
userToken: 'customUserToken',
},
});

const searchClient = createSearchClient({
search: jest.fn(() =>
Promise.resolve(
createMultiSearchResponse({
hits: [{ objectID: '1' }],
})
)
),
});

insightsClient('setAuthenticatedUserToken', 'customAuthUserToken');

const playground = createPlayground(createAutocomplete, {
plugins: [insightsPlugin],
getSources({ query }) {
return [
{
sourceId: 'hits',
getItems() {
return getAlgoliaResults({
searchClient,
queries: [{ indexName: 'indexName', query }],
});
},
templates: {
item({ item }) {
return item.objectID;
},
},
},
];
},
});

userEvent.type(playground.inputElement, 'a');
await runAllMicroTasks();

expect(searchClient.search).toHaveBeenCalledTimes(1);
expect(searchClient.search).toHaveBeenCalledWith([
expect.objectContaining({
params: expect.objectContaining({ userToken: 'customAuthUserToken' }),
}),
]);

insightsClient('setAuthenticatedUserToken', undefined);

userEvent.type(playground.inputElement, 'b');
await runAllMicroTasks();

expect(searchClient.search).toHaveBeenCalledTimes(2);
expect(searchClient.search).toHaveBeenLastCalledWith([
expect.objectContaining({
params: expect.objectContaining({ userToken: 'customUserToken' }),
}),
]);
});
});

describe('automatic pulling', () => {
const consoleError = jest
.spyOn(console, 'error')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
AlgoliaInsightsHit,
AutocompleteInsightsApi,
InsightsClient,
InsightsEvent,
InsightsMethodMap,
OnActiveParams,
OnItemsChangeParams,
Expand Down Expand Up @@ -182,7 +183,7 @@ export function createAlgoliaInsightsPlugin(
return {
name: 'aa.algoliaInsightsPlugin',
subscribe({ setContext, onSelect, onActive }) {
function setInsightsContext(userToken?: string | number) {
function setInsightsContext(userToken?: InsightsEvent['userToken']) {
setContext({
algoliaInsightsPlugin: {
__algoliaSearchParameters: {
Expand All @@ -201,11 +202,36 @@ export function createAlgoliaInsightsPlugin(
insightsClient('addAlgoliaAgent', 'insights-plugin');

setInsightsContext();

// Handles user token changes
insightsClient('onUserTokenChange', setInsightsContext);
insightsClient('getUserToken', null, (_error, userToken) => {
setInsightsContext(userToken);
});

// Handles authenticated user token changes
insightsClient(
'onAuthenticatedUserTokenChange',
(authenticatedUserToken) => {
if (authenticatedUserToken) {
setInsightsContext(authenticatedUserToken);
} else {
insightsClient('getUserToken', null, (_error, userToken) =>
setInsightsContext(userToken)
);
}
}
);
insightsClient(
'getAuthenticatedUserToken',
null,
(_error, authenticatedUserToken) => {
if (authenticatedUserToken) {
setInsightsContext(authenticatedUserToken);
}
}
);

onSelect(({ item, state, event, source }) => {
if (!isAlgoliaInsightsHit(item)) {
return;
Expand Down Expand Up @@ -323,6 +349,8 @@ function loadInsights(environment: typeof window) {
* While `search-insights` supports both string and number user tokens,
* the Search API only accepts strings. This function normalizes the user token.
*/
function normalizeUserToken(userToken: string | number): string {
function normalizeUserToken(
userToken: NonNullable<InsightsEvent['userToken']>
): string {
return typeof userToken === 'number' ? userToken.toString() : userToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ export function createSearchInsightsApi(searchInsights: InsightsClient) {
init(appId: string, apiKey: string) {
searchInsights('init', { appId, apiKey });
},
/**
* Sets the authenticated user token to attach to events.
* Unsets the authenticated token by passing `undefined`.
*
* @link https://www.algolia.com/doc/api-reference/api-methods/set-authenticated-user-token/
*/
setAuthenticatedUserToken(authenticatedUserToken: string | undefined) {
searchInsights('setAuthenticatedUserToken', authenticatedUserToken);
},
/**
* Sets the user token to attach to events.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type {
SetUserToken as InsightsSetUserToken,
GetUserToken as InsightsGetUserToken,
OnUserTokenChange as InsightsOnUserTokenChange,
InsightsEvent,
} from 'search-insights';

export type InsightsMethodMap = _InsightsMethodMap;
Expand Down

0 comments on commit bd398ee

Please sign in to comment.