Skip to content

Commit

Permalink
feat(recommend): introduce connectLookingSimilar connector (#6180)
Browse files Browse the repository at this point in the history
  • Loading branch information
Haroenv authored and dhayab committed May 21, 2024
1 parent 07b0d9e commit 78a7723
Show file tree
Hide file tree
Showing 9 changed files with 473 additions and 1 deletion.
2 changes: 1 addition & 1 deletion bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
"maxSize": "175.75 kB"
"maxSize": "190 kB"
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
Expand Down
28 changes: 28 additions & 0 deletions packages/instantsearch.js/src/__tests__/common-connectors.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
connectRelatedProducts,
connectFrequentlyBoughtTogether,
connectTrendingItems,
connectLookingSimilar,
} from '../connectors';
import instantsearch from '../index.es';
import { refinementList } from '../widgets';
Expand Down Expand Up @@ -475,6 +476,32 @@ const testSetups: TestSetupsMap<TestSuites> = {
})
.start();
},
createLookingSimilarConnectorTests({ instantSearchOptions, widgetParams }) {
const customLookingSimilar = connectLookingSimilar<{
container: HTMLElement;
}>((renderOptions) => {
renderOptions.widgetParams.container.innerHTML = `
<ul>${renderOptions.recommendations
.map((recommendation) => `<li>${recommendation.objectID}</li>`)
.join('')}</ul>
`;
});

instantsearch(instantSearchOptions)
.addWidgets([
customLookingSimilar({
container: document.body.appendChild(document.createElement('div')),
...widgetParams,
}),
])
.on('error', () => {
/*
* prevent rethrowing InstantSearch errors, so tests can be asserted.
* IRL this isn't needed, as the error doesn't stop execution.
*/
})
.start();
},
};

const testOptions: TestOptionsMap<TestSuites> = {
Expand All @@ -491,6 +518,7 @@ const testOptions: TestOptionsMap<TestSuites> = {
createRelatedProductsConnectorTests: undefined,
createFrequentlyBoughtTogetherConnectorTests: undefined,
createTrendingItemsConnectorTests: undefined,
createLookingSimilarConnectorTests: undefined,
};

describe('Common connector tests (InstantSearch.js)', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/instantsearch.js/src/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ export { default as connectQueryRules } from './query-rules/connectQueryRules';
export { default as connectVoiceSearch } from './voice-search/connectVoiceSearch';
export { default as connectRelevantSort } from './relevant-sort/connectRelevantSort';
export { default as connectFrequentlyBoughtTogether } from './frequently-bought-together/connectFrequentlyBoughtTogether';
export { default as connectLookingSimilar } from './looking-similar/connectLookingSimilar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import {
createDocumentationMessageGenerator,
checkRendering,
noop,
} from '../../lib/utils';

import type { Connector, TransformItems, Hit, BaseHit } from '../../types';
import type {
PlainSearchParameters,
RecommendResultItem,
} from 'algoliasearch-helper';

const withUsage = createDocumentationMessageGenerator({
name: 'looking-similar',
connector: true,
});

export type LookingSimilarRenderState<THit extends BaseHit = BaseHit> = {
/**
* The matched recommendations from the Algolia API.
*/
recommendations: Array<Hit<THit>>;
};

export type LookingSimilarConnectorParams<THit extends BaseHit = BaseHit> = {
/**
* The `objectIDs` of the items to get similar looking products from.
*/
objectIDs: string[];
/**
* The number of recommendations to retrieve.
*/
maxRecommendations?: number;
/**
* The threshold for the recommendations confidence score (between 0 and 100).
*/
threshold?: number;
/**
* List of search parameters to send.
*/
fallbackParameters?: Omit<
PlainSearchParameters,
'page' | 'hitsPerPage' | 'offset' | 'length'
>;
/**
* List of search parameters to send.
*/
queryParameters?: Omit<
PlainSearchParameters,
'page' | 'hitsPerPage' | 'offset' | 'length'
>;
/**
* Function to transform the items passed to the templates.
*/
transformItems?: TransformItems<Hit<THit>, { results: RecommendResultItem }>;
};

export type LookingSimilarWidgetDescription<THit extends BaseHit = BaseHit> = {
$$type: 'ais.lookingSimilar';
renderState: LookingSimilarRenderState<THit>;
};

export type LookingSimilarConnector<THit extends BaseHit = BaseHit> = Connector<
LookingSimilarWidgetDescription<THit>,
LookingSimilarConnectorParams<THit>
>;

const connectLookingSimilar: LookingSimilarConnector =
function connectLookingSimilar(renderFn, unmountFn = noop) {
checkRendering(renderFn, withUsage());

return function LookingSimilar(widgetParams) {
const {
objectIDs,
maxRecommendations,
threshold,
fallbackParameters,
queryParameters,
transformItems = ((items) => items) as NonNullable<
LookingSimilarConnectorParams['transformItems']
>,
} = widgetParams || {};

if (!objectIDs || objectIDs.length === 0) {
throw new Error(withUsage('The `objectIDs` option is required.'));
}

return {
dependsOn: 'recommend',
$$type: 'ais.lookingSimilar',

init(initOptions) {
renderFn(
{
...this.getWidgetRenderState(initOptions),
instantSearchInstance: initOptions.instantSearchInstance,
},
true
);
},

render(renderOptions) {
const renderState = this.getWidgetRenderState(renderOptions);

renderFn(
{
...renderState,
instantSearchInstance: renderOptions.instantSearchInstance,
},
false
);
},

getRenderState(renderState) {
return renderState;
},

getWidgetRenderState({ results }) {
if (results === null || results === undefined) {
return { recommendations: [], widgetParams };
}

return {
recommendations: transformItems(results.hits, {
results: results as RecommendResultItem,
}),
widgetParams,
};
},

dispose({ state }) {
unmountFn();

return state;
},

getWidgetParameters(state) {
return objectIDs.reduce(
(acc, objectID) =>
acc.addLookingSimilar({
objectID,
maxRecommendations,
threshold,
fallbackParameters,
queryParameters,
$$id: this.$$id!,
}),
state
);
},
};
};
};

export default connectLookingSimilar;
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ const testSetups: TestSetupsMap<TestSuites> = {
</InstantSearch>
);
},
createLookingSimilarConnectorTests: () => {
throw new Error('Not implemented');
},
};

const testOptions: TestOptionsMap<TestSuites> = {
Expand All @@ -409,6 +412,7 @@ const testOptions: TestOptionsMap<TestSuites> = {
createRelatedProductsConnectorTests: { act },
createFrequentlyBoughtTogetherConnectorTests: { act },
createTrendingItemsConnectorTests: { act },
createLookingSimilarConnectorTests: { act, skippedTests: { options: true } },
};

describe('Common connector tests (React InstantSearch)', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ const testSetups = {
createRelatedProductsConnectorTests: () => {},
createFrequentlyBoughtTogetherConnectorTests: () => {},
createTrendingItemsConnectorTests: () => {},
createLookingSimilarConnectorTests: () => {},
};

function createCustomWidget({
Expand Down Expand Up @@ -431,6 +432,11 @@ const testOptions = {
options: true,
},
},
createLookingSimilarConnectorTests: {
skippedTests: {
options: true,
},
},
};

describe('Common connector tests (Vue InstantSearch)', () => {
Expand Down
1 change: 1 addition & 0 deletions tests/common/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './current-refinements';
export * from './related-products';
export * from './frequently-bought-together';
export * from './trending-items';
export * from './looking-similar';
23 changes: 23 additions & 0 deletions tests/common/connectors/looking-similar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { fakeAct } from '../../common';

import { createOptionsTests } from './options';

import type { TestOptions, TestSetup } from '../../common';
import type { LookingSimilarConnectorParams } from 'instantsearch.js/src/connectors/looking-similar/connectLookingSimilar';

export type LookingSimilarConnectorSetup = TestSetup<{
widgetParams: LookingSimilarConnectorParams;
}>;

export function createLookingSimilarConnectorTests(
setup: LookingSimilarConnectorSetup,
{ act = fakeAct, skippedTests = {} }: TestOptions = {}
) {
beforeAll(() => {
document.body.innerHTML = '';
});

describe('LookingSimilar connector common tests', () => {
createOptionsTests(setup, { act, skippedTests });
});
}

0 comments on commit 78a7723

Please sign in to comment.