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

feat(react-instantsearch): introduce Frequently Bought Together Hook and widget #6155

Merged
merged 98 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 94 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
440e9cc
chore: update algoliasearch version
dhayab Mar 28, 2024
81b4400
feat(helper): add methods to request and retrieve recommendations
dhayab Mar 28, 2024
0eac184
add tests
dhayab Mar 29, 2024
98018d8
emit placeholder change event and test events better
dhayab Mar 29, 2024
84937b3
Merge branch 'master' into feat/helper-recommend-methods
dhayab Mar 29, 2024
6f064f8
bump bundle sizes
dhayab Mar 29, 2024
9469f26
Merge branch 'master' into feat/helper-recommend-methods
dhayab Apr 2, 2024
2cc4743
retrieve recommend queries on all index widgets through derived helpers
dhayab Apr 3, 2024
2a70204
add tests for derived helper
dhayab Apr 3, 2024
2518d91
bump bundlesize
dhayab Apr 3, 2024
7ab1436
feat(recommend): add FBT to instantsearch-ui-components
raed667 Apr 3, 2024
d326cab
fix: split component files to fix type errors
raed667 Apr 4, 2024
60f8c9c
fix babel spread issue
raed667 Apr 4, 2024
ab5dd33
replace classes auc with ais
raed667 Apr 4, 2024
e8544c6
fix: address feedback
raed667 Apr 4, 2024
7ade025
fix: address feedback by adding tests and TS types
raed667 Apr 4, 2024
e041a2f
wip: map recommend results to widgets
aymeric-giraudet Apr 4, 2024
8813c64
remove comment
raed667 Apr 4, 2024
60a9ef6
Merge branch 'feat/map-recommend-results' into feat-recommend-fbt-ui-…
raed667 Apr 5, 2024
ad70293
feat(recommend): add FBT to connector
raed667 Apr 5, 2024
60f052b
fix: results
aymeric-giraudet Apr 5, 2024
c90ef99
fix: typescript
aymeric-giraudet Apr 5, 2024
418cffb
fix types
aymeric-giraudet Apr 5, 2024
5c69418
bundlesize
aymeric-giraudet Apr 5, 2024
46c2531
Merge branch 'master' into feat/map-recommend-results
aymeric-giraudet Apr 5, 2024
bd942b4
Merge branch 'feat-recommend-fbt-ui-component' into feat/recommend-fb…
raed667 Apr 5, 2024
deab535
Merge branch 'feat/map-recommend-results' into feat-recommend-fbt-ui-…
raed667 Apr 5, 2024
c65c703
wip: map recommend results to widgets
aymeric-giraudet Apr 4, 2024
859c5ba
fix: results
aymeric-giraudet Apr 5, 2024
3785724
fix: typescript
aymeric-giraudet Apr 5, 2024
ea2259d
fix types
aymeric-giraudet Apr 5, 2024
6eab8a3
bundlesize
aymeric-giraudet Apr 5, 2024
61ba925
Merge branch 'master' into feat/map-recommend-results
aymeric-giraudet Apr 5, 2024
49f9efe
fix
aymeric-giraudet Apr 5, 2024
6c1dc58
Merge branch 'feat/map-recommend-results' into feat-recommend-fbt-ui-…
raed667 Apr 5, 2024
cbe9d9a
Merge branch 'feat-recommend-fbt-ui-component' into feat/recommend-fb…
raed667 Apr 5, 2024
41ec195
fix linit issue
raed667 Apr 5, 2024
f1eb001
fix linit issue
raed667 Apr 5, 2024
2baed0e
Merge branch 'feat-recommend-fbt-ui-component' into feat/recommend-fb…
raed667 Apr 5, 2024
02de7c2
bundlesize
raed667 Apr 5, 2024
8375968
wip
sarahdayan Apr 8, 2024
b37546e
wip
sarahdayan Apr 8, 2024
cb00cee
fix: fix build
sarahdayan Apr 8, 2024
44b40fe
fix: add __position
sarahdayan Apr 8, 2024
853d96a
Update packages/algoliasearch-helper/src/algoliasearch.helper.js
raed667 Apr 8, 2024
f9c44d2
add support and types for sendEvent
dhayab Apr 9, 2024
4dbc64c
fix tests
dhayab Apr 9, 2024
a62a979
fix: fix issues with sendEvent and test it
sarahdayan Apr 9, 2024
30d0f35
fix: stop passing createElement and Fragment to ItemComponent
sarahdayan Apr 9, 2024
087f1f3
style: lint
sarahdayan Apr 10, 2024
164989e
more parameters, automatic $$id
aymeric-giraudet Apr 15, 2024
7dd719c
feat: remove default click event
sarahdayan Apr 15, 2024
2f9c840
Merge branch 'feat-recommend-fbt-ui-component' into feat/recommend-fb…
dhayab Apr 15, 2024
5a7ed9e
feat(instantsearch-ui-components): introduce `FrequentlyBoughtTogethe…
raed667 Apr 15, 2024
3d8c469
feat(instantsearch-ui-components): introduce `RelatedProducts` compon…
sarahdayan Apr 15, 2024
3022ea6
Merge branch 'feat/map-recommend-results' into feat/recommend-fbt-con…
dhayab Apr 18, 2024
789fbe7
bump bundlesize, remove unwanted console log, revert example code
dhayab Apr 18, 2024
58f34ff
expose objectIDs as prop
dhayab Apr 18, 2024
30fa447
Merge branch 'feat/map-recommend-results' into feat/recommend-fbt-con…
dhayab Apr 19, 2024
b175652
check that object ids are provided
dhayab Apr 22, 2024
c513d67
Merge branch 'feat/map-recommend-results' into feat/recommend-fbt-con…
dhayab Apr 22, 2024
3d1be73
switch hits -> recommendations
dhayab Apr 22, 2024
ac3e273
first widget
raed667 Apr 5, 2024
abbfa9a
implement header template and view prop
dhayab Apr 15, 2024
bb4c2ae
set relevant props and markup for header
dhayab Apr 15, 2024
6311106
move `view` from prop to template
dhayab Apr 15, 2024
b457739
add tests
dhayab Apr 16, 2024
6843fd0
bump js bundle size
dhayab Apr 16, 2024
12b991c
remove view template from widget
dhayab Apr 17, 2024
61fa09d
mock api with createRecommendResponse
dhayab Apr 17, 2024
c1d530e
ignore irrelevant error in algoliasearch v3 type check
dhayab Apr 17, 2024
ce50193
expose objectIDs as prop
dhayab Apr 19, 2024
70dd365
switch hits -> recommendations
dhayab Apr 22, 2024
df76672
apply suggestions
dhayab Apr 22, 2024
fa5639e
fix widget test
dhayab Apr 22, 2024
f577aa9
display header by default
dhayab Apr 22, 2024
4e20248
Merge branch 'feat/map-recommend-results' into feat/recommend-fbt-widget
dhayab Apr 23, 2024
0f5baf9
bump bundlesize
dhayab Apr 23, 2024
3dbf6d1
feat(recommend): setup common test suite for `frequentlyBoughtTogether`
dhayab Apr 17, 2024
ffb6f0d
fix test type issues
dhayab Apr 17, 2024
5f4cf50
ignore irrelevant error in algoliasearch v3 type check
dhayab Apr 17, 2024
9e7500e
update widget tests to use objectIDs
dhayab Apr 19, 2024
fde10a7
fix tests
dhayab Apr 23, 2024
07a0dc3
Merge branch 'feat/map-recommend-results' into feat/recommend-fbt-cts
dhayab Apr 23, 2024
2c9e089
Merge branch 'feat/map-recommend-results' into feat/recommend-fbt-cts
dhayab Apr 23, 2024
22d6dbc
feat(recommend): add FBT react hook
raed667 Apr 23, 2024
d3a876e
react component
raed667 Apr 23, 2024
a5a6652
Merge branch 'feat/map-recommend-results' into feat/recommend-fbt-rea…
sarahdayan Apr 26, 2024
6fe02c2
feat: finish implementation
sarahdayan Apr 26, 2024
80b10c4
chore: increase bundle size
sarahdayan Apr 26, 2024
9cb943e
test: fix tests
sarahdayan Apr 26, 2024
110dceb
style: remove unnecessary cast
sarahdayan Apr 26, 2024
6db9a12
chore: increase bundle size
sarahdayan Apr 26, 2024
d4960b7
Merge branch 'feat/map-recommend-results' into feat/recommend-fbt-rea…
dhayab Apr 29, 2024
1914f84
Merge branch 'feat/map-recommend-results' into feat/recommend-fbt-rea…
dhayab Apr 30, 2024
a03ceca
bundlesize
dhayab Apr 30, 2024
4c46d4b
Update packages/react-instantsearch/src/widgets/__tests__/__utils__/a…
dhayab Apr 30, 2024
769fe24
Revert "Update packages/react-instantsearch/src/widgets/__tests__/__u…
dhayab Apr 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
"maxSize": "47.75 kB"
"maxSize": "48.25 kB"
},
{
"path": "packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js",
"maxSize": "60.5 kB"
"maxSize": "61 kB"
},
{
"path": "packages/vue-instantsearch/vue2/umd/index.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import connectFrequentlyBoughtTogether from 'instantsearch.js/es/connectors/frequently-bought-together/connectFrequentlyBoughtTogether';

import { useConnector } from '../hooks/useConnector';

import type { AdditionalWidgetProperties } from '../hooks/useConnector';
import type { BaseHit } from 'instantsearch.js';
import type {
FrequentlyBoughtTogetherConnector,
FrequentlyBoughtTogetherConnectorParams,
FrequentlyBoughtTogetherWidgetDescription,
} from 'instantsearch.js/es/connectors/frequently-bought-together/connectFrequentlyBoughtTogether';

export type UseFrequentlyBoughtTogetherProps<THit extends BaseHit = BaseHit> =
FrequentlyBoughtTogetherConnectorParams<THit>;

export function useFrequentlyBoughtTogether<THit extends BaseHit = BaseHit>(
props?: UseFrequentlyBoughtTogetherProps<THit>,
additionalWidgetProperties?: AdditionalWidgetProperties
) {
return useConnector<
FrequentlyBoughtTogetherConnectorParams<THit>,
FrequentlyBoughtTogetherWidgetDescription<THit>
>(
connectFrequentlyBoughtTogether as FrequentlyBoughtTogetherConnector<THit>,
props,
additionalWidgetProperties
);
}
1 change: 1 addition & 0 deletions packages/react-instantsearch-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './connectors/useClearRefinements';
export * from './connectors/useConfigure';
export * from './connectors/useCurrentRefinements';
export * from './connectors/useDynamicWidgets';
export * from './connectors/useFrequentlyBoughtTogether';
export * from './connectors/useGeoSearch';
export * from './connectors/useHierarchicalMenu';
export * from './connectors/useHits';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
useToggleRefinement,
useCurrentRefinements,
useRelatedProducts,
useFrequentlyBoughtTogether,
} from '..';

import type {
Expand All @@ -34,6 +35,7 @@ import type {
UseRefinementListProps,
UseToggleRefinementProps,
UseRelatedProductsProps,
UseFrequentlyBoughtTogetherProps,
} from '..';
import type { TestOptionsMap, TestSetupsMap } from '@instantsearch/tests';
import type {
Expand Down Expand Up @@ -337,7 +339,30 @@ const testSetups: TestSetupsMap<TestSuites> = {
</InstantSearch>
);
},
createFrequentlyBoughtTogetherConnectorTests: () => {},
createFrequentlyBoughtTogetherConnectorTests: ({
instantSearchOptions,
widgetParams,
}) => {
function CustomFrequentlyBoughtTogether(
props: UseFrequentlyBoughtTogetherProps
) {
const { recommendations } = useFrequentlyBoughtTogether(props);

return (
<ul>
{recommendations.map((recommendation) => (
<li key={recommendation.objectID}>{recommendation.objectID}</li>
))}
</ul>
);
}

render(
<InstantSearch {...instantSearchOptions}>
<CustomFrequentlyBoughtTogether {...widgetParams} />
</InstantSearch>
);
},
};

const testOptions: TestOptionsMap<TestSuites> = {
Expand All @@ -358,12 +383,7 @@ const testOptions: TestOptionsMap<TestSuites> = {
createRatingMenuConnectorTests: { act },
createToggleRefinementConnectorTests: { act },
createRelatedProductsConnectorTests: { act },
createFrequentlyBoughtTogetherConnectorTests: {
act,
skippedTests: {
options: true,
},
},
createFrequentlyBoughtTogetherConnectorTests: { act },
};

describe('Common connector tests (React InstantSearch)', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
SortBy,
Stats,
RelatedProducts,
FrequentlyBoughtTogether,
} from '..';

import type { TestOptionsMap, TestSetupsMap } from '@instantsearch/tests';
Expand Down Expand Up @@ -319,9 +320,12 @@ const testSetups: TestSetupsMap<TestSuites> = {
</InstantSearch>
);
},
createFrequentlyBoughtTogetherTests() {
throw new Error(
'FrequentlyBoughtTogether is not implemented in React InstantSearch yet'
createFrequentlyBoughtTogetherTests({ instantSearchOptions, widgetParams }) {
render(
<InstantSearch {...instantSearchOptions}>
<FrequentlyBoughtTogether {...widgetParams} />
<GlobalErrorSwallower />
</InstantSearch>
);
},
};
Expand Down Expand Up @@ -356,12 +360,7 @@ const testOptions: TestOptionsMap<TestSuites> = {
},
},
createRelatedProductsWidgetTests: { act },
createFrequentlyBoughtTogetherTests: {
act,
skippedTests: {
'FrequentlyBoughtTogether widget common tests': true,
},
},
createFrequentlyBoughtTogetherTests: { act },
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { createFrequentlyBoughtTogetherComponent } from 'instantsearch-ui-components';
import React, { createElement, Fragment } from 'react';
import {
useFrequentlyBoughtTogether,
useInstantSearch,
} from 'react-instantsearch-core';

import type {
FrequentlyBoughtTogetherProps as FrequentlyBoughtTogetherPropsUiComponentProps,
Pragma,
} from 'instantsearch-ui-components';
import type { Hit, BaseHit } from 'instantsearch.js';
import type { UseFrequentlyBoughtTogetherProps } from 'react-instantsearch-core';

type UiProps<THit extends BaseHit> = Pick<
FrequentlyBoughtTogetherPropsUiComponentProps<Hit<THit>>,
| 'items'
| 'itemComponent'
| 'headerComponent'
| 'fallbackComponent'
| 'status'
| 'sendEvent'
>;

export type FrequentlyBoughtTogetherProps<THit extends BaseHit> = Omit<
FrequentlyBoughtTogetherPropsUiComponentProps<Hit<THit>>,
keyof UiProps<THit>
> &
UseFrequentlyBoughtTogetherProps<THit> & {
itemComponent?: FrequentlyBoughtTogetherPropsUiComponentProps<THit>['itemComponent'];
headerComponent?: FrequentlyBoughtTogetherPropsUiComponentProps<THit>['headerComponent'];
fallbackComponent?: FrequentlyBoughtTogetherPropsUiComponentProps<THit>['fallbackComponent'];
};

const FrequentlyBoughtTogetherUiComponent =
createFrequentlyBoughtTogetherComponent({
createElement: createElement as Pragma,
Fragment,
});

export function FrequentlyBoughtTogether<THit extends BaseHit = BaseHit>({
objectIDs,
maxRecommendations,
threshold,
queryParameters,
transformItems,
itemComponent,
headerComponent,
fallbackComponent,
...props
}: FrequentlyBoughtTogetherProps<THit>) {
const { status } = useInstantSearch();
const { recommendations } = useFrequentlyBoughtTogether<THit>(
{
objectIDs,
maxRecommendations,
threshold,
queryParameters,
transformItems,
},
{ $$widgetType: 'ais.frequentlyBoughtTogether' }
);

const uiProps: UiProps<THit> = {
items: recommendations,
itemComponent,
headerComponent,
fallbackComponent,
status,
sendEvent: () => {},
};

return <FrequentlyBoughtTogetherUiComponent {...props} {...uiProps} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* @jest-environment jsdom
*/

import {
createMultiSearchResponse,
createSearchClient,
createSingleSearchResponse,
} from '@instantsearch/mocks';
import { InstantSearchTestWrapper } from '@instantsearch/testutils';
import { render, waitFor } from '@testing-library/react';
import React from 'react';

import { FrequentlyBoughtTogether } from '../FrequentlyBoughtTogether';

import type { SearchClient } from 'instantsearch.js';

describe('FrequentlyBoughtTogether', () => {
test('renders with translations', async () => {
const client = createMockedSearchClient();
const { container } = render(
<InstantSearchTestWrapper searchClient={client}>
<FrequentlyBoughtTogether
objectIDs={['1']}
translations={{ title: 'My FBT' }}
/>
</InstantSearchTestWrapper>
);

await waitFor(() => {
expect(client.search).toHaveBeenCalledTimes(1);
});

await waitFor(() => {
expect(container.querySelector('.ais-FrequentlyBoughtTogether'))
.toMatchInlineSnapshot(`
<section
class="ais-FrequentlyBoughtTogether"
>
<h3
class="ais-FrequentlyBoughtTogether-title"
>
My FBT
</h3>
<div
class="ais-FrequentlyBoughtTogether-container"
>
<ol
class="ais-FrequentlyBoughtTogether-list"
>
<li
class="ais-FrequentlyBoughtTogether-item"
>
{
"objectID": "1"
}
</li>
<li
class="ais-FrequentlyBoughtTogether-item"
>
{
"objectID": "2"
}
</li>
</ol>
</div>
</section>
`);
});
});

test('forwards custom class names and `div` props to the root element', () => {
const { container } = render(
<InstantSearchTestWrapper>
<FrequentlyBoughtTogether
objectIDs={['1']}
className="MyFrequentlyBoughtTogether"
classNames={{ root: 'ROOT' }}
aria-hidden={true}
/>
</InstantSearchTestWrapper>
);

const root = container.firstChild;
expect(root).toHaveClass('MyFrequentlyBoughtTogether', 'ROOT');
expect(root).toHaveAttribute('aria-hidden', 'true');
});
});

function createMockedSearchClient() {
return createSearchClient({
getRecommendations: jest.fn((requests) =>
Promise.resolve(
createMultiSearchResponse(
// @ts-ignore
// `request` will be implicitly typed as `any` in type-check:v3
// since `getRecommendations` is not available there
...requests.map((request) => {
return createSingleSearchResponse<any>({
hits:
request.maxRecommendations === 0
? []
: [{ objectID: '1' }, { objectID: '2' }],
});
})
)
)
) as SearchClient['getRecommendations'],
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ function Widget<TWidget extends SingleWidget>({
case 'SearchBox': {
return <widget.Component onSubmit={undefined} {...props} />;
}
case 'FrequentlyBoughtTogether': {
return <widget.Component objectIDs={['1']} {...props} />;
}
dhayab marked this conversation as resolved.
Show resolved Hide resolved
case 'RelatedProducts': {
return <widget.Component objectIDs={['1']} {...props} />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ describe('widgets', () => {
"$$widgetType": "ais.currentRefinements",
"name": "CurrentRefinements",
},
{
"$$type": "ais.frequentlyBoughtTogether",
"$$widgetType": "ais.frequentlyBoughtTogether",
"name": "FrequentlyBoughtTogether",
},
{
"$$type": "ais.hierarchicalMenu",
"$$widgetType": "ais.hierarchicalMenu",
Expand Down
1 change: 1 addition & 0 deletions packages/react-instantsearch/src/widgets/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './Breadcrumb';
export * from './ClearRefinements';
export * from './CurrentRefinements';
export * from './FrequentlyBoughtTogether';
export * from './HierarchicalMenu';
export * from './Highlight';
export * from './Hits';
Expand Down