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

wip: support for multiple object ids in recommend widgets #6122

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 7 additions & 7 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@
"files": [
{
"path": "packages/algoliasearch-helper/dist/algoliasearch.helper.js",
"maxSize": "40.25 kB"
"maxSize": "40.5 kB"
},
{
"path": "packages/algoliasearch-helper/dist/algoliasearch.helper.min.js",
"maxSize": "12.75 kB"
"maxSize": "13 kB"
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.production.min.js",
"maxSize": "77.5 kB"
"maxSize": "77.75 kB"
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
"maxSize": "170.75 kB"
"maxSize": "171 kB"
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
"maxSize": "47.25 kB"
"maxSize": "47.5 kB"
},
{
"path": "packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js",
"maxSize": "59.25 kB"
},
{
"path": "packages/vue-instantsearch/vue2/umd/index.js",
"maxSize": "66.25 kB"
"maxSize": "66.5 kB"
},
{
"path": "packages/vue-instantsearch/vue3/umd/index.js",
"maxSize": "66.75 kB"
"maxSize": "67 kB"
},
{
"path": "packages/vue-instantsearch/vue2/cjs/index.js",
Expand Down
7 changes: 5 additions & 2 deletions examples/js/getting-started/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ <h1 class="header-title">

<div class="search-panel__results">
<div id="searchbox"></div>
<div id="hits"></div>
<div id="pagination"></div>
<h2>index: instant_search</h2>
<div id="trending"></div>
<div id="fbt1"></div>
<h2>index: devcon22_bm_products</h2>
<div id="fbt2"></div>
</div>
</div>
</div>
Expand Down
181 changes: 148 additions & 33 deletions examples/js/getting-started/src/app.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import algoliasearch from 'algoliasearch/lite';
import instantsearch from 'instantsearch.js';
import {
configure,
hits,
pagination,
panel,
refinementList,
searchBox,
} from 'instantsearch.js/es/widgets';
import { configure, hits, index, searchBox } from 'instantsearch.js/es/widgets';

const searchClient = algoliasearch(
'latency',
Expand All @@ -17,36 +10,158 @@ const searchClient = algoliasearch(
const search = instantsearch({
indexName: 'instant_search',
searchClient,
insights: true,
future: {
preserveSharedStateOnUnmount: true,
},
});

const trendingFacets = connectTrendingFacets(({ items, widgetOptions }) => {
const container = document.querySelector(widgetOptions.container);
container.innerHTML = '';
container.insertAdjacentHTML(
'beforeend',
`<div><h3>Trending facets</h3><ul></ul></div>`
);
items.forEach((item) => {
container.querySelector('ul').insertAdjacentHTML(
'beforeend',
`<li>
<p style="margin-left: 1rem">${item.facetValue}</p>
</li>`
);
});
});

const frequentlyBoughtTogether = connectFrequentlyBoughtTogether(
({ items, widgetOptions }) => {
const container = document.querySelector(widgetOptions.container);
container.innerHTML = '';
container.insertAdjacentHTML(
'beforeend',
`<div><h3>Frequently Bought Together</h3><ul></ul></div>`
);
items.forEach((item) => {
container.querySelector('ul').insertAdjacentHTML(
'beforeend',
`<li>
<p style="margin-left: 1rem">${item.name || item.title}</p>
</li>`
);
});
}
);

search.addWidgets([
searchBox({
container: '#searchbox',
}),
hits({
container: '#hits',
templates: {
item: (hit, { html, components }) => html`
<article>
<h1>${components.Highlight({ hit, attribute: 'name' })}</h1>
<p>${components.Highlight({ hit, attribute: 'description' })}</p>
</article>
`,
},
}),
configure({
hitsPerPage: 8,
trendingFacets({
container: '#trending',
facetName: 'categories',
maxRecommendations: 5,
}),
panel({
templates: { header: 'brand' },
})(refinementList)({
container: '#brand-list',
attribute: 'brand',
}),
pagination({
container: '#pagination',
frequentlyBoughtTogether({
container: '#fbt1',
objectIDs: ['5723537', '5005506'],
maxRecommendations: 3,
}),
index({ indexName: 'devcon22_bm_products' }).addWidgets([
frequentlyBoughtTogether({
container: '#fbt2',
objectIDs: ['90807', '91789'],
maxRecommendations: 4,
}),
]),
]);

search.start();

function connectTrendingFacets(renderFn, unmountFn = () => {}) {
return function _trendingFacets(widgetOptions) {
return {
$$type: 'ais.trendingFacets',
dependsOn: 'recommend',
init(initOptions) {
renderFn(
{
...this.getWidgetRenderState(initOptions),
instantSearchInstance: initOptions.instantSearchInstance,
},
true
);
},
render(renderOptions) {
renderFn(
{
...this.getWidgetRenderState(renderOptions),
instantSearchInstance: renderOptions.instantSearchInstance,
},
false
);
},
dispose() {
unmountFn();
},
getRenderState(renderState) {
return renderState;
},
getWidgetRenderState({ results }) {
console.log('\t trendingFacets', results);

return {
items: results?.length > 0 ? results[0].hits : [],
widgetOptions,
};
},
getWidgetParameters(state) {
const { container: _container, ...options } = widgetOptions;
return state.addTrendingFacets({ ...options });
},
};
};
}

function connectFrequentlyBoughtTogether(renderFn, unmountFn = () => {}) {
return function _trendingFacets(widgetOptions) {
return {
$$type: 'ais.frequentlyBoughtTogether',
dependsOn: 'recommend',
init(initOptions) {
renderFn(
{
...this.getWidgetRenderState(initOptions),
instantSearchInstance: initOptions.instantSearchInstance,
},
true
);
},
render(renderOptions) {
renderFn(
{
...this.getWidgetRenderState(renderOptions),
instantSearchInstance: renderOptions.instantSearchInstance,
},
false
);
},
dispose() {
unmountFn();
},
getRenderState(renderState) {
return renderState;
},
getWidgetRenderState({ results }) {
console.log('\t frequentlyBoughtTogether', results);

return {
items: results?.length > 0 ? results[0].hits : [],
widgetOptions,
};
},
getWidgetParameters(state) {
const { container: _container, objectIDs, ...options } = widgetOptions;
return objectIDs.reduce(
(acc, objectID) => acc.addRelatedProducts({ objectID, ...options }),
state
);
},
};
};
}
23 changes: 19 additions & 4 deletions packages/algoliasearch-helper/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
RelatedProductsQuery as RecommendRelatedProductsQuery,
TrendingFacetsQuery as RecommendTrendingFacetsQuery,
TrendingItemsQuery as RecommendTrendingItemsQuery,
RecommendQueriesResponse,
} from '@algolia/recommend';

/**
Expand All @@ -42,7 +43,7 @@ declare namespace algoliasearchHelper {
state: SearchParameters;
recommendState: RecommendParameters;
lastResults: SearchResults | null;
lastRecommendResults: unknown | null; // TODO: Define type in dedicated PR
lastRecommendResults: RecommendResults | null;
derivedHelpers: DerivedHelper[];

on(
Expand Down Expand Up @@ -402,15 +403,15 @@ declare namespace algoliasearchHelper {
event: 'recommend:result',
cb: (res: {
recommend: {
results: unknown | null; // TODO: Define type in dedicated PR
results: RecommendResults | null;
state: RecommendParameters;
};
}) => void
): this;
on(event: 'error', cb: (res: { error: Error }) => void): this;

lastResults: SearchResults | null;
lastRecommendResults: unknown | null; // TODO: Define type in dedicated PR
lastRecommendResults: RecommendResults | null;
detach(): void;
getModifiedState(): SearchParameters;
getModifiedRecommendState(): RecommendParameters;
Expand Down Expand Up @@ -1525,7 +1526,7 @@ declare namespace algoliasearchHelper {
export type RecommendParametersWithId<
T extends PlainRecommendParameters = PlainRecommendParameters
> = T & {
$$id: string;
$$id: number;
};

export type RecommendParametersOptions = {
Expand Down Expand Up @@ -1553,6 +1554,20 @@ declare namespace algoliasearchHelper {
params: RecommendParametersWithId<LookingSimilarQuery>
): RecommendParameters;
}

type RecommendResponse<TObject> =
RecommendQueriesResponse<TObject>['results'];

type RecommendResultItem<TObject = any> = RecommendResponse<TObject>[0];

export class RecommendResults<T = any> {
constructor(state: RecommendParameters, results: RecommendResponse<T>);

_state: RecommendParameters;
_rawResults: RecommendResponse<T>;

[index: number]: RecommendResultItem<T>;
}
}

export = algoliasearchHelper;
10 changes: 1 addition & 9 deletions packages/algoliasearch-helper/src/RecommendParameters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,7 @@ RecommendParameters.prototype = {

addParams: function (params) {
var newParams = this.params.slice();
var existingParamsIndex = this.params.findIndex(function (currentParams) {
return currentParams.$$id === params.$$id;
});

if (existingParamsIndex !== -1) {
newParams.splice(existingParamsIndex, 1, params);
} else {
newParams.push(params);
}
newParams.push(params);

return new RecommendParameters({ params: newParams });
},
Expand Down
28 changes: 28 additions & 0 deletions packages/algoliasearch-helper/src/RecommendResults/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

/**
* Constructor for SearchResults
* @class
* @classdesc SearchResults contains the results of a query to Algolia using the
* {@link AlgoliaSearchHelper}.
* @param {RecommendParameters} state state that led to the response
* @param {Record<string,RecommendResultItem>} results the results from algolia client
**/
function RecommendResults(state, results) {
this._state = state;
this._rawResults = results;

// eslint-disable-next-line consistent-this
var self = this;

state.params.forEach(function (param) {
var id = param.$$id;
self[id] = results[id];
});
}

RecommendResults.prototype = {
constructor: RecommendResults,
};

module.exports = RecommendResults;