Skip to content

Commit

Permalink
feat(recommend): introduce connectTrendingItems connector (#6169)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhayab committed May 21, 2024
1 parent 93b2eae commit 22ea4a5
Show file tree
Hide file tree
Showing 9 changed files with 446 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 kB"
"maxSize": "175.25 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 @@ -17,6 +17,7 @@ import {
connectToggleRefinement,
connectRelatedProducts,
connectFrequentlyBoughtTogether,
connectTrendingItems,
} from '../connectors';
import instantsearch from '../index.es';
import { refinementList } from '../widgets';
Expand Down Expand Up @@ -448,6 +449,32 @@ const testSetups: TestSetupsMap<TestSuites> = {
})
.start();
},
createTrendingItemsConnectorTests({ instantSearchOptions, widgetParams }) {
const customTrendingItems = connectTrendingItems<{
container: HTMLElement;
}>((renderOptions) => {
renderOptions.widgetParams.container.innerHTML = `
<ul>${renderOptions.recommendations
.map((recommendation) => `<li>${recommendation.objectID}</li>`)
.join('')}</ul>
`;
});

instantsearch(instantSearchOptions)
.addWidgets([
customTrendingItems({
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 @@ -463,6 +490,7 @@ const testOptions: TestOptionsMap<TestSuites> = {
createToggleRefinementConnectorTests: undefined,
createRelatedProductsConnectorTests: undefined,
createFrequentlyBoughtTogetherConnectorTests: undefined,
createTrendingItemsConnectorTests: 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 @@ -36,6 +36,7 @@ export { default as connectSortBy } from './sort-by/connectSortBy';
export { default as connectRatingMenu } from './rating-menu/connectRatingMenu';
export { default as connectStats } from './stats/connectStats';
export { default as connectToggleRefinement } from './toggle-refinement/connectToggleRefinement';
export { default as connectTrendingItems } from './trending-items/connectTrendingItems';
export { default as connectBreadcrumb } from './breadcrumb/connectBreadcrumb';
export { default as connectGeoSearch } from './geo-search/connectGeoSearch';
export { default as connectPoweredBy } from './powered-by/connectPoweredBy';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
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: 'trending-items',
connector: true,
});

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

export type TrendingItemsConnectorParams<THit extends BaseHit = BaseHit> = (
| {
/**
* The facet attribute to get recommendations for.
*/
facetName: string;
/**
* The facet value to get recommendations for.
*/
facetValue: string;
}
| { facetName?: never; facetValue?: never }
) & {
/**
* 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 TrendingItemsWidgetDescription<THit extends BaseHit = BaseHit> = {
$$type: 'ais.trendingItems';
renderState: TrendingItemsRenderState<THit>;
};

export type TrendingItemsConnector<THit extends BaseHit = BaseHit> = Connector<
TrendingItemsWidgetDescription<THit>,
TrendingItemsConnectorParams<THit>
>;

const connectTrendingItems: TrendingItemsConnector =
function connectTrendingItems(renderFn, unmountFn = noop) {
checkRendering(renderFn, withUsage());

return function trendingItems(widgetParams) {
const {
facetName,
facetValue,
maxRecommendations,
threshold,
fallbackParameters,
queryParameters,
transformItems = ((items) => items) as NonNullable<
TrendingItemsConnectorParams['transformItems']
>,
} = widgetParams || {};

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

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 state.addTrendingItems({
facetName,
facetValue,
maxRecommendations,
threshold,
fallbackParameters,
queryParameters,
$$id: this.$$id!,
});
},
};
};
};

export default connectTrendingItems;
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ const testSetups: TestSetupsMap<TestSuites> = {
</InstantSearch>
);
},
createTrendingItemsConnectorTests: () => {},
};

const testOptions: TestOptionsMap<TestSuites> = {
Expand All @@ -384,6 +385,12 @@ const testOptions: TestOptionsMap<TestSuites> = {
createToggleRefinementConnectorTests: { act },
createRelatedProductsConnectorTests: { act },
createFrequentlyBoughtTogetherConnectorTests: { act },
createTrendingItemsConnectorTests: {
act,
skippedTests: {
options: true,
},
},
};

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

function createCustomWidget({
Expand Down Expand Up @@ -425,6 +426,11 @@ const testOptions = {
options: true,
},
},
createTrendingItemsConnectorTests: {
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 @@ -10,3 +10,4 @@ export * from './toggle-refinement';
export * from './current-refinements';
export * from './related-products';
export * from './frequently-bought-together';
export * from './trending-items';
23 changes: 23 additions & 0 deletions tests/common/connectors/trending-items/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 { TrendingItemsConnectorParams } from 'instantsearch.js/src/connectors/trending-items/connectTrendingItems';

export type TrendingItemsConnectorSetup = TestSetup<{
widgetParams: TrendingItemsConnectorParams;
}>;

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

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

0 comments on commit 22ea4a5

Please sign in to comment.