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

poc: warn added widgets require missing clients #5943

Draft
wants to merge 2 commits into
base: poc/multi-clients
Choose a base branch
from
Draft
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
11 changes: 7 additions & 4 deletions examples/js/multi-clients/src/algolia.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import algoliasearch from 'algoliasearch/lite';
import recommend from '@algolia/recommend';
// import recommend from '@algolia/recommend';

export function algolia(appID, apiKey) {
const searchClient = algoliasearch(appID, apiKey);
const recommendClient = recommend(appID, apiKey);

return { searchClient, recommendClient };
// const recommendClient = recommend(appID, apiKey);

return {
searchClient,
// recommendClient,
};
}
3 changes: 2 additions & 1 deletion examples/js/multi-clients/src/search.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import instantsearch from 'instantsearch.js';
import { algolia } from '@algolia/client';

import { algolia } from './algolia';

const client = algolia('XX85YRZZMV', '098f71f9e2267178bdfc08cc986d2999');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { getFrequentlyBoughtTogether } from '@algolia/recommend-core';

import {
SendEventForHits,
checkRendering,
createDocumentationMessageGenerator,
createSendEventForHits,
noop,
} from '../../lib/utils';

import type { SendEventForHits } from '../../lib/utils';
import type { Connector, Hit, WidgetRenderState } from '../../types';

const withUsage = createDocumentationMessageGenerator({
Expand Down Expand Up @@ -52,6 +53,8 @@ const connectFrequentlyBoughtTogether: FrequentlyBoughtTogetherConnector =
return {
$$type: 'ais.frequentlyBoughtTogether',

requires: ['recommendClient'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not related to the api, but personally I think what we should do is replace getWidgetSearchParameters with

getWidgetParameters() {
  return {
    search: ...,
    recommend: ...,
  }
}

(we can detect that the same in index of course, but it seems less hardcoded)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main difference is that you don't redo the work twice, once for checking and once for actually setting the parameters. If we have getWidgetParameters, you just translate the parameters, then check if anything set parameters for the reco client.


init(initOptions) {
const { state, instantSearchInstance } = initOptions;

Expand Down
16 changes: 13 additions & 3 deletions packages/instantsearch.js/src/lib/InstantSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ function defaultCreateURL() {
// source: https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-504042546
type NoInfer<T> = T extends infer S ? S : never;

export type InstantSearchClients = {
searchClient: SearchClient;
recommendClient: any;
};

/**
* Global options for an InstantSearch instance.
*/
Expand Down Expand Up @@ -95,7 +100,7 @@ export type InstantSearchOptions<
*/
searchClient?: SearchClient;

client?: { searchClient: SearchClient; recommendClient: any };
client?: InstantSearchClients;

/**
* The locale used to display numbers. This will be passed
Expand Down Expand Up @@ -191,6 +196,7 @@ class InstantSearch<
TUiState extends UiState = UiState,
TRouteState = TUiState
> extends EventEmitter {
public clients: NonNullable<InstantSearchOptions['client']>;
public client: NonNullable<InstantSearchOptions['searchClient']>;
public recommendClient: any;
public indexName: string;
Expand Down Expand Up @@ -334,8 +340,12 @@ See documentation: ${createDocumentationLink({
`);
}

this.client = (client?.searchClient || searchClient) as SearchClient;
this.recommendClient = client?.recommendClient!;
this.clients = client || {
searchClient: searchClient!,
recommendClient: undefined,
};
this.client = this.clients.searchClient;
this.recommendClient = this.clients.recommendClient!;
this.future = future;
this.insightsClient = insightsClient;
this.indexName = indexName;
Expand Down
3 changes: 3 additions & 0 deletions packages/instantsearch.js/src/types/widget.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { InstantSearchClients } from '../lib/InstantSearch';
import type { IndexWidget } from '../widgets/index/index';
import type { InstantSearch } from './instantsearch';
import type { IndexRenderState, WidgetRenderState } from './render-state';
Expand Down Expand Up @@ -140,6 +141,8 @@ type RequiredWidgetLifeCycle<TWidgetDescription extends WidgetDescription> = {
*/
$$type: TWidgetDescription['$$type'];

requires?: Array<keyof InstantSearchClients>;

/**
* Called once before the first search.
*/
Expand Down
62 changes: 60 additions & 2 deletions packages/instantsearch.js/src/widgets/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
isIndexWidget,
createInitArgs,
createRenderArgs,
warn,
} from '../../lib/utils';

import type {
Expand Down Expand Up @@ -323,6 +324,16 @@ const index = (widgetParams: IndexWidgetParams): IndexWidget => {
localWidgets = localWidgets.concat(widgets);

if (localInstantSearchInstance && Boolean(widgets.length)) {
const [validWidgets, invalidWidgets] = satisfyWidgetRequirements(
localInstantSearchInstance,
widgets
);
if (invalidWidgets.length > 0) {
localWidgets = localWidgets.filter(
(widget) => invalidWidgets.indexOf(widget) === -1
);
}

privateHelperSetState(helper!, {
state: getLocalWidgetsSearchParameters(localWidgets, {
uiState: localUiState,
Expand All @@ -334,7 +345,7 @@ const index = (widgetParams: IndexWidgetParams): IndexWidget => {
// We compute the render state before calling `init` in a separate loop
// to construct the whole render state object that is then passed to
// `init`.
widgets.forEach((widget) => {
validWidgets.forEach((widget) => {
if (widget.getRenderState) {
const renderState = widget.getRenderState(
localInstantSearchInstance!.renderState[this.getIndexId()] || {},
Expand All @@ -353,7 +364,7 @@ const index = (widgetParams: IndexWidgetParams): IndexWidget => {
}
});

widgets.forEach((widget) => {
validWidgets.forEach((widget) => {
if (widget.init) {
widget.init(
createInitArgs(
Expand Down Expand Up @@ -444,6 +455,17 @@ const index = (widgetParams: IndexWidgetParams): IndexWidget => {
localParent = parent;
localUiState = uiState[indexId] || {};

// Ensure widgets requirements are available
const [, invalidWidgets] = satisfyWidgetRequirements(
localInstantSearchInstance,
localWidgets
);
if (invalidWidgets.length > 0) {
localWidgets = localWidgets.filter(
(widget) => invalidWidgets.indexOf(widget) === -1
);
}

// The `mainHelper` is already defined at this point. The instance is created
// inside InstantSearch at the `start` method, which occurs before the `init`
// step.
Expand Down Expand Up @@ -784,3 +806,39 @@ function storeRenderState({
},
};
}

function satisfyWidgetRequirements(
instantSearchInstance: InstantSearch,
widgets: Array<Widget | IndexWidget>
): [Array<Widget | IndexWidget>, Array<Widget | IndexWidget>] {
const clients = Object.keys(instantSearchInstance.clients);
const [widgetsToKeep, widgetsToSkip] = widgets.reduce(
([keep, skip], widget) => {
if (
(widget.requires || ['searchClient']).some(
(requirement) => !clients.includes(requirement)
)
) {
skip.push(widget);
} else {
keep.push(widget);
}

return [keep, skip];
},
[[] as Array<Widget | IndexWidget>, [] as Array<Widget | IndexWidget>]
);

if (widgetsToSkip.length > 0) {
warn(`Some widgets cannot be added to your InstantSearch application, because they require clients that are not configured:

${widgetsToSkip
.map((widget) => `- ${widget.$$type} requires ${widget.requires}`)
.join('\n')}

See: https://alg.li/instantsearch-multi-clients
`);
}

return [widgetsToKeep, widgetsToSkip];
}