From 2e967be6a33c532c472f9ca76295d605ed4f6f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 30 Nov 2020 18:38:31 +0100 Subject: [PATCH] feat(plugins): introduce Insights plugin (#373) --- .circleci/config.yml | 4 + .eslintrc.js | 7 +- README.md | 1 + bundlesize.config.json | 6 +- examples/js/app.ts | 24 ++- examples/js/package.json | 5 +- .../src/createAutocomplete.ts | 27 ++- .../autocomplete-core/src/getDefaultProps.ts | 14 +- packages/autocomplete-core/src/types/api.ts | 4 +- packages/autocomplete-core/src/types/index.ts | 1 + .../autocomplete-core/src/types/plugins.ts | 28 ++- .../autocomplete-core/src/types/subscriber.ts | 10 + .../README.md | 11 + .../package.json | 40 ++++ .../rollup.config.js | 5 + .../src/createAlgoliaInsightsPlugin.ts | 151 +++++++++++++ .../src/createClickedEvent.ts | 21 ++ .../src/createSearchInsightsApi.ts | 66 ++++++ .../src/createViewedEvents.ts | 29 +++ .../src/index.ts | 1 + .../src/isAlgoliaInsightsHit.ts | 7 + .../src/types/AlgoliaInsightsHit.ts | 5 + .../src/types/EventParams.ts | 24 +++ .../src/types/InsightsApi.ts | 62 ++++++ .../src/types/InsightsClient.ts | 1 + .../src/types/index.ts | 4 + .../tsconfig.declaration.json | 3 + .../src/createQuerySuggestionsPlugin.ts | 7 +- .../src/createRecentSearchesPlugin.ts | 6 +- .../autocomplete-preset-algolia/src/index.ts | 4 +- .../src/search/getAlgoliaHits.ts | 10 +- .../src/__tests__/isEqual.test.ts | 202 ++++++++++++++++++ packages/autocomplete-shared/src/debounce.ts | 11 + packages/autocomplete-shared/src/index.ts | 5 +- packages/autocomplete-shared/src/isEqual.ts | 34 +++ ship.config.js | 1 + yarn.lock | 129 +++++++++++ 37 files changed, 937 insertions(+), 33 deletions(-) create mode 100644 packages/autocomplete-core/src/types/subscriber.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/README.md create mode 100644 packages/autocomplete-plugin-algolia-insights/package.json create mode 100644 packages/autocomplete-plugin-algolia-insights/rollup.config.js create mode 100644 packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/createClickedEvent.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/createSearchInsightsApi.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/createViewedEvents.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/index.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/isAlgoliaInsightsHit.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/types/AlgoliaInsightsHit.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/types/EventParams.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/types/InsightsApi.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/types/InsightsClient.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/src/types/index.ts create mode 100644 packages/autocomplete-plugin-algolia-insights/tsconfig.declaration.json create mode 100644 packages/autocomplete-shared/src/__tests__/isEqual.test.ts create mode 100644 packages/autocomplete-shared/src/debounce.ts create mode 100644 packages/autocomplete-shared/src/isEqual.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 5b206a128..fd62705c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,6 +27,7 @@ aliases: mkdir -p packages/autocomplete-core/dist mkdir -p packages/autocomplete-js/dist mkdir -p packages/autocomplete-preset-algolia/dist + mkdir -p packages/autocomplete-plugin-algolia-insights/dist mkdir -p packages/autocomplete-plugin-recent-searches/dist mkdir -p packages/autocomplete-plugin-query-suggestions/dist @@ -34,6 +35,7 @@ aliases: cp -R /tmp/workspace/packages/autocomplete-core/dist packages/autocomplete-core cp -R /tmp/workspace/packages/autocomplete-js/dist packages/autocomplete-js cp -R /tmp/workspace/packages/autocomplete-preset-algolia/dist packages/autocomplete-preset-algolia + cp -R /tmp/workspace/packages/autocomplete-plugin-algolia-insights/dist packages/autocomplete-plugin-algolia-insights cp -R /tmp/workspace/packages/autocomplete-plugin-recent-searches/dist packages/autocomplete-plugin-recent-searches cp -R /tmp/workspace/packages/autocomplete-plugin-query-suggestions/dist packages/autocomplete-plugin-query-suggestions @@ -69,6 +71,7 @@ jobs: mkdir -p /tmp/workspace/packages/autocomplete-core/dist mkdir -p /tmp/workspace/packages/autocomplete-js/dist mkdir -p /tmp/workspace/packages/autocomplete-preset-algolia/dist + mkdir -p /tmp/workspace/packages/autocomplete-plugin-algolia-insights/dist mkdir -p /tmp/workspace/packages/autocomplete-plugin-recent-searches/dist mkdir -p /tmp/workspace/packages/autocomplete-plugin-query-suggestions/dist @@ -76,6 +79,7 @@ jobs: cp -R packages/autocomplete-core/dist /tmp/workspace/packages/autocomplete-core cp -R packages/autocomplete-js/dist /tmp/workspace/packages/autocomplete-js cp -R packages/autocomplete-preset-algolia/dist /tmp/workspace/packages/autocomplete-preset-algolia + cp -R packages/autocomplete-plugin-algolia-insights/dist /tmp/workspace/packages/autocomplete-plugin-algolia-insights cp -R packages/autocomplete-plugin-recent-searches/dist /tmp/workspace/packages/autocomplete-plugin-recent-searches cp -R packages/autocomplete-plugin-query-suggestions/dist /tmp/workspace/packages/autocomplete-plugin-query-suggestions - persist_to_workspace: diff --git a/.eslintrc.js b/.eslintrc.js index 33351b493..44d1f0d7b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,7 +26,12 @@ module.exports = { 'new-cap': 0, 'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }], 'import/extensions': 0, - '@typescript-eslint/camelcase': ['error', { allow: ['__autocomplete_id'] }], + '@typescript-eslint/camelcase': [ + 'error', + { + allow: ['__autocomplete_'], + }, + ], // Useful to call functions like `nodeItem?.scrollIntoView()`. 'no-unused-expressions': 0, complexity: 0, diff --git a/README.md b/README.md index 2d99c9b4c..ed0f1d1a4 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ You can find the Autocomplete documentation on [autocomplete.algolia.com](https: | [`autocomplete-preset-algolia`](packages/autocomplete-preset-algolia) | Presets to use Algolia features with Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/getAlgoliaHits) | | [`autocomplete-plugin-recent-searches`](packages/autocomplete-plugin-recent-searches) | A plugin to add recent searches to Algolia Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/createLocalStorageRecentSearchesPlugin) | | [`autocomplete-plugin-query-suggestions`](packages/autocomplete-plugin-query-suggestions) | A plugin to add query suggestions to Algolia Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/createQuerySuggestionsPlugin) | +| [`autocomplete-plugin-algolia-insights`](packages/autocomplete-plugin-algolia-insights) | A plugin to add Algolia Insights to Algolia Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/createAlgoliaInsightsPlugin) | ## License diff --git a/bundlesize.config.json b/bundlesize.config.json index 78f0fbd8b..066ec5858 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -6,12 +6,16 @@ }, { "path": "packages/autocomplete-js/dist/umd/index.production.js", - "maxSize": "8.25 kB" + "maxSize": "8.5 kB" }, { "path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js", "maxSize": "1.5 kB" }, + { + "path": "packages/autocomplete-plugin-algolia-insights/dist/umd/index.production.js", + "maxSize": "2 kB" + }, { "path": "packages/autocomplete-plugin-recent-searches/dist/umd/index.production.js", "maxSize": "3.25 kB" diff --git a/examples/js/app.ts b/examples/js/app.ts index 4c9425941..be5f2b823 100644 --- a/examples/js/app.ts +++ b/examples/js/app.ts @@ -1,28 +1,36 @@ import { autocomplete } from '@algolia/autocomplete-js'; +import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; -import algoliasearch from 'algoliasearch/lite'; +import algoliasearch from 'algoliasearch'; +import insightsClient from 'search-insights'; -const searchClient = algoliasearch( - 'latency', - '6be0576ff61c053d5f9a3225e2a90f76' -); +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); +insightsClient('init', { appId, apiKey }); +const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: 'search', limit: 3, }); - const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: 'instant_search_demo_query_suggestions', getSearchParams() { - return recentSearchesPlugin.data.getAlgoliaSearchParams(); + return recentSearchesPlugin.data.getAlgoliaSearchParams({ + clickAnalytics: true, + }); }, }); autocomplete({ container: '#autocomplete', openOnFocus: true, - plugins: [recentSearchesPlugin, querySuggestionsPlugin], + plugins: [ + algoliaInsightsPlugin, + recentSearchesPlugin, + querySuggestionsPlugin, + ], }); diff --git a/examples/js/package.json b/examples/js/package.json index b73fd2ee5..d8b94723f 100644 --- a/examples/js/package.json +++ b/examples/js/package.json @@ -11,9 +11,12 @@ }, "dependencies": { "@algolia/autocomplete-js": "1.0.0-alpha.35", + "@algolia/autocomplete-plugin-algolia-insights": "1.0.0-alpha.35", "@algolia/autocomplete-plugin-query-suggestions": "1.0.0-alpha.35", "@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.35", - "algoliasearch": "4.6.0" + "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.35", + "algoliasearch": "4.7.0", + "search-insights": "1.6.2" }, "devDependencies": { "parcel-bundler": "1.12.4" diff --git a/packages/autocomplete-core/src/createAutocomplete.ts b/packages/autocomplete-core/src/createAutocomplete.ts index a17f40f6b..40596bd6f 100644 --- a/packages/autocomplete-core/src/createAutocomplete.ts +++ b/packages/autocomplete-core/src/createAutocomplete.ts @@ -5,7 +5,12 @@ import { getDefaultProps } from './getDefaultProps'; import { getPropGetters } from './getPropGetters'; import { onInput } from './onInput'; import { stateReducer } from './stateReducer'; -import { AutocompleteApi, AutocompleteOptions, BaseItem } from './types'; +import { + AutocompleteApi, + AutocompleteOptions, + BaseItem, + Subscribers, +} from './types'; export function createAutocomplete< TItem extends BaseItem, @@ -17,7 +22,8 @@ export function createAutocomplete< ): AutocompleteApi { checkOptions(options); - const props = getDefaultProps(options); + const subscribers: Subscribers = []; + const props = getDefaultProps(options, subscribers); const store = createStore(stateReducer, props); const { @@ -68,6 +74,23 @@ export function createAutocomplete< }); } + props.plugins.forEach((plugin) => + plugin.subscribe?.({ + setSelectedItemId, + setQuery, + setCollections, + setIsOpen, + setStatus, + setContext, + onSelect(fn) { + subscribers.push({ onSelect: fn }); + }, + onHighlight(fn) { + subscribers.push({ onHighlight: fn }); + }, + }) + ); + return { setSelectedItemId, setQuery, diff --git a/packages/autocomplete-core/src/getDefaultProps.ts b/packages/autocomplete-core/src/getDefaultProps.ts index bdff7cc74..b8ba83095 100644 --- a/packages/autocomplete-core/src/getDefaultProps.ts +++ b/packages/autocomplete-core/src/getDefaultProps.ts @@ -3,6 +3,7 @@ import { AutocompleteOptions, BaseItem, InternalAutocompleteOptions, + Subscribers, } from './types'; import { generateAutocompleteId, @@ -12,7 +13,8 @@ import { } from './utils'; export function getDefaultProps( - props: AutocompleteOptions + props: AutocompleteOptions, + subscribers: Subscribers ): InternalAutocompleteOptions { const environment: InternalAutocompleteOptions< TItem @@ -70,8 +72,14 @@ export function getDefaultProps( ...source, onSelect(params) { source.onSelect(params); - plugins.forEach((plugin) => { - plugin.subscribed?.onSelect?.(params); + subscribers.forEach((subscriber) => { + subscriber.onSelect?.(params); + }); + }, + onHighlight(params) { + source.onHighlight(params); + subscribers.forEach((subscriber) => { + subscriber.onHighlight?.(params); }); }, })) diff --git a/packages/autocomplete-core/src/types/api.ts b/packages/autocomplete-core/src/types/api.ts index 6622ab313..fced43465 100644 --- a/packages/autocomplete-core/src/types/api.ts +++ b/packages/autocomplete-core/src/types/api.ts @@ -52,14 +52,14 @@ interface ItemParams { source: InternalAutocompleteSource; } -interface OnSelectParams +export interface OnSelectParams extends ItemParams, AutocompleteSetters { state: AutocompleteState; event: any; } -type OnHighlightParams = OnSelectParams; +export type OnHighlightParams = OnSelectParams; interface OnSubmitParams extends AutocompleteSetters { diff --git a/packages/autocomplete-core/src/types/index.ts b/packages/autocomplete-core/src/types/index.ts index abaaf911b..eefd7757b 100644 --- a/packages/autocomplete-core/src/types/index.ts +++ b/packages/autocomplete-core/src/types/index.ts @@ -4,3 +4,4 @@ export * from './plugins'; export * from './setters'; export * from './state'; export * from './store'; +export * from './subscriber'; diff --git a/packages/autocomplete-core/src/types/plugins.ts b/packages/autocomplete-core/src/types/plugins.ts index ff251450b..5ad8abd69 100644 --- a/packages/autocomplete-core/src/types/plugins.ts +++ b/packages/autocomplete-core/src/types/plugins.ts @@ -1,4 +1,18 @@ -import { AutocompleteOptions, AutocompleteSource, BaseItem } from './api'; +import { + AutocompleteOptions, + OnHighlightParams, + OnSelectParams, + BaseItem, +} from './api'; +import { AutocompleteSetters } from './setters'; + +type PluginSubscriber = (params: TParams) => void; + +interface PluginSubscribeParams + extends AutocompleteSetters { + onSelect(fn: PluginSubscriber>): void; + onHighlight(fn: PluginSubscriber>): void; +} export type AutocompletePlugin< TItem extends BaseItem, @@ -7,14 +21,14 @@ export type AutocompletePlugin< Pick, 'onStateChange' | 'onSubmit' | 'getSources'> > & { /** - * The subscribed properties are properties that are called when other sources - * are interacted with. + * Function called when Autocomplete starts. + * + * It can be used to subscribe to lifecycle hooks or to interact with the + * Autocomplete state and context. */ - subscribed?: { - onSelect: AutocompleteSource['onSelect']; - }; + subscribe?(params: PluginSubscribeParams): void; /** - * An extra plugin specific object to store variables and functions + * Extra plugin object to expose properties and functions as APIs. */ data?: TData; }; diff --git a/packages/autocomplete-core/src/types/subscriber.ts b/packages/autocomplete-core/src/types/subscriber.ts new file mode 100644 index 000000000..1c9bf4783 --- /dev/null +++ b/packages/autocomplete-core/src/types/subscriber.ts @@ -0,0 +1,10 @@ +import { BaseItem, OnHighlightParams, OnSelectParams } from './api'; + +export type Subscriber = { + onSelect(params: OnSelectParams): void; + onHighlight(params: OnHighlightParams): void; +}; + +export type Subscribers = Array< + Partial> +>; diff --git a/packages/autocomplete-plugin-algolia-insights/README.md b/packages/autocomplete-plugin-algolia-insights/README.md new file mode 100644 index 000000000..4af43d9c0 --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/README.md @@ -0,0 +1,11 @@ +# @algolia/autocomplete-plugin-algolia-insights + +A plugin to add Algolia Insights to Algolia Autocomplete. + +## Installation + +```sh +yarn add @algolia/autocomplete-plugin-algolia-insights@alpha +# or +npm install @algolia/autocomplete-plugin-algolia-insights@alpha +``` diff --git a/packages/autocomplete-plugin-algolia-insights/package.json b/packages/autocomplete-plugin-algolia-insights/package.json new file mode 100644 index 000000000..eee93e149 --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/package.json @@ -0,0 +1,40 @@ +{ + "name": "@algolia/autocomplete-plugin-algolia-insights", + "description": "A plugin to add Algolia Insights to Algolia Autocomplete.", + "version": "1.0.0-alpha.35", + "license": "MIT", + "homepage": "https://github.com/algolia/autocomplete.js", + "repository": "algolia/autocomplete.js", + "author": { + "name": "Algolia, Inc.", + "url": "https://www.algolia.com" + }, + "source": "src/index.ts", + "types": "dist/esm/index.d.ts", + "module": "dist/esm/index.js", + "main": "dist/umd/index.production.js", + "umd:main": "dist/umd/index.production.js", + "unpkg": "dist/umd/index.production.js", + "jsdelivr": "dist/umd/index.production.js", + "sideEffects": false, + "files": [ + "dist/" + ], + "scripts": { + "build:clean": "rm -rf ./dist", + "build:esm": "babel src --root-mode upward --extensions '.ts,.tsx' --out-dir dist/esm --ignore '**/*/__tests__/'", + "build:types": "tsc -p ./tsconfig.declaration.json --outDir ./dist/esm", + "build:umd": "rollup --config", + "build": "rm -rf ./dist && yarn build:umd && yarn build:esm && yarn build:types", + "on:change": "concurrently \"yarn build:esm\" \"yarn build:types\"", + "prepare": "yarn run build:esm", + "watch": "watch \"yarn on:change\" --ignoreDirectoryPattern \"/dist/\"" + }, + "dependencies": { + "@algolia/autocomplete-core": "1.0.0-alpha.35", + "@algolia/autocomplete-shared": "1.0.0-alpha.35" + }, + "peerDependencies": { + "search-insights": "^1.0.0" + } +} diff --git a/packages/autocomplete-plugin-algolia-insights/rollup.config.js b/packages/autocomplete-plugin-algolia-insights/rollup.config.js new file mode 100644 index 000000000..099ce0e3a --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/rollup.config.js @@ -0,0 +1,5 @@ +import { createRollupConfigs } from '../../scripts/rollup/config'; + +import pkg from './package.json'; + +export default createRollupConfigs({ pkg }); diff --git a/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts b/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts new file mode 100644 index 000000000..3591f7c87 --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts @@ -0,0 +1,151 @@ +import { + AutocompletePlugin, + AutocompleteState, +} from '@algolia/autocomplete-core'; +import { createRef, debounce, isEqual } from '@algolia/autocomplete-shared'; + +import { createClickedEvent } from './createClickedEvent'; +import { createSearchInsightsApi } from './createSearchInsightsApi'; +import { createViewedEvents } from './createViewedEvents'; +import { isAlgoliaInsightsHit } from './isAlgoliaInsightsHit'; +import { + AlgoliaInsightsHit, + InsightsApi, + InsightsClient, + OnHighlightParams, + OnItemsChangeParams, + OnSelectParams, +} from './types'; + +const VIEW_EVENT_DELAY = 400; + +type SendViewedObjectIDsParams = { + onItemsChange(params: OnItemsChangeParams): void; + items: AlgoliaInsightsHit[]; + insights: InsightsApi; + state: AutocompleteState; +}; + +const sendViewedObjectIDs = debounce( + ({ onItemsChange, items, insights, state }) => { + onItemsChange({ + insights, + insightsEvents: createViewedEvents({ items }).map((event) => ({ + eventName: 'Items Viewed', + ...event, + })), + state, + }); + }, + VIEW_EVENT_DELAY +); + +export type CreateAlgoliaInsightsPluginParams = { + /** + * The initialized Search Insights client. + */ + insightsClient: InsightsClient; + /** + * Hook to send an Insights event when the items change. + * + * This hook is debounced every 400ms to better reflect when items are + * acknowledged by the user. + */ + onItemsChange?(params: OnItemsChangeParams): void; + /** + * Hook to send an Insights event when an item is selected. + */ + onSelect?(params: OnSelectParams): void; + /** + * Hook to send an Insights event when an item is highlighted. + */ + onHighlight?(params: OnHighlightParams): void; +}; + +export function createAlgoliaInsightsPlugin({ + insightsClient, + onItemsChange = ({ insights, insightsEvents }) => { + insights.viewedObjectIDs(...insightsEvents); + }, + onSelect: onSelectEvent = ({ insights, insightsEvents }) => { + insights.clickedObjectIDsAfterSearch(...insightsEvents); + }, + onHighlight: onHighlightEvent = () => {}, +}: CreateAlgoliaInsightsPluginParams): AutocompletePlugin { + const insights = createSearchInsightsApi(insightsClient); + const previousItems = createRef([]); + + const debouncedOnStateChange = debounce<{ + state: AutocompleteState; + }>(({ state }) => { + if (!state.isOpen) { + return; + } + + const items = state.collections + .reduce((acc, current) => { + return [...acc, ...current.items]; + }, []) + .filter(isAlgoliaInsightsHit); + + if ( + !isEqual( + previousItems.current.map((x) => x.objectID), + items.map((x) => x.objectID) + ) + ) { + previousItems.current = items; + + if (items.length > 0) { + sendViewedObjectIDs({ onItemsChange, items, insights, state }); + } + } + }, 0); + + return { + subscribe({ setContext, onSelect, onHighlight }) { + setContext({ algoliaInsightsPlugin: { insights } }); + + onSelect(({ item, state, event }) => { + if (!isAlgoliaInsightsHit(item)) { + return; + } + + onSelectEvent({ + state, + event, + insights, + item, + insightsEvents: [ + { + eventName: 'Item Selected', + ...createClickedEvent({ item, items: previousItems.current }), + }, + ], + }); + }); + + onHighlight(({ item, state, event }) => { + if (!isAlgoliaInsightsHit(item)) { + return; + } + + onHighlightEvent({ + state, + event, + insights, + item, + insightsEvents: [ + { + eventName: 'Item Highlighted', + ...createClickedEvent({ item, items: previousItems.current }), + }, + ], + }); + }); + }, + onStateChange({ state }) { + debouncedOnStateChange({ state }); + }, + }; +} diff --git a/packages/autocomplete-plugin-algolia-insights/src/createClickedEvent.ts b/packages/autocomplete-plugin-algolia-insights/src/createClickedEvent.ts new file mode 100644 index 000000000..439d3565b --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/createClickedEvent.ts @@ -0,0 +1,21 @@ +import { AlgoliaInsightsHit, ClickedObjectIDsAfterSearchParams } from './types'; + +type CreateClickedEventParams = { + item: AlgoliaInsightsHit; + items: AlgoliaInsightsHit[]; +}; + +export function createClickedEvent({ + item, + items, +}: CreateClickedEventParams): Omit< + ClickedObjectIDsAfterSearchParams, + 'eventName' +> { + return { + index: item.__autocomplete_indexName, + objectIDs: [item.objectID], + positions: [1 + items.findIndex((x) => x.objectID === item.objectID)], + queryID: item.__autocomplete_queryID, + }; +} diff --git a/packages/autocomplete-plugin-algolia-insights/src/createSearchInsightsApi.ts b/packages/autocomplete-plugin-algolia-insights/src/createSearchInsightsApi.ts new file mode 100644 index 000000000..86c296585 --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/createSearchInsightsApi.ts @@ -0,0 +1,66 @@ +import { + ClickedFiltersParams, + ClickedObjectIDsAfterSearchParams, + ClickedObjectIDsParams, + ConvertedFiltersParams, + ConvertedObjectIDsAfterSearchParams, + ConvertedObjectIDsParams, + InsightsClient, + ViewedFiltersParams, + ViewedObjectIDsParams, +} from './types'; + +export function createSearchInsightsApi(searchInsights: InsightsClient) { + return { + init(appId: string, apiKey: string) { + searchInsights('init', { appId, apiKey }); + }, + setUserToken(userToken: string) { + searchInsights('setUserToken', userToken); + }, + clickedObjectIDsAfterSearch( + ...params: ClickedObjectIDsAfterSearchParams[] + ) { + if (params.length > 0) { + searchInsights('clickedObjectIDsAfterSearch', ...params); + } + }, + clickedObjectIDs(...params: ClickedObjectIDsParams[]) { + if (params.length > 0) { + searchInsights('clickedObjectIDs', ...params); + } + }, + clickedFilters(...params: ClickedFiltersParams[]) { + if (params.length > 0) { + searchInsights('clickedFilters', ...params); + } + }, + convertedObjectIDsAfterSearch( + ...params: ConvertedObjectIDsAfterSearchParams[] + ) { + if (params.length > 0) { + searchInsights('convertedObjectIDsAfterSearch', ...params); + } + }, + convertedObjectIDs(...params: ConvertedObjectIDsParams[]) { + if (params.length > 0) { + searchInsights('convertedObjectIDs', ...params); + } + }, + convertedFilters(...params: ConvertedFiltersParams[]) { + if (params.length > 0) { + searchInsights('convertedFilters', ...params); + } + }, + viewedObjectIDs(...params: ViewedObjectIDsParams[]) { + if (params.length > 0) { + searchInsights('viewedObjectIDs', ...params); + } + }, + viewedFilters(...params: ViewedFiltersParams[]) { + if (params.length > 0) { + searchInsights('viewedFilters', ...params); + } + }, + }; +} diff --git a/packages/autocomplete-plugin-algolia-insights/src/createViewedEvents.ts b/packages/autocomplete-plugin-algolia-insights/src/createViewedEvents.ts new file mode 100644 index 000000000..074309ede --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/createViewedEvents.ts @@ -0,0 +1,29 @@ +import { AlgoliaInsightsHit, ViewedObjectIDsParams } from './types'; + +type CreateViewedEventsParams = { + items: AlgoliaInsightsHit[]; +}; + +export function createViewedEvents({ + items, +}: CreateViewedEventsParams): Array> { + const objectIDsByIndexName = items.reduce>( + (acc, current) => { + acc[current.__autocomplete_indexName] = ( + acc[current.__autocomplete_indexName] ?? [] + ).concat(current.objectID); + + return acc; + }, + {} + ); + + return Object.keys(objectIDsByIndexName).map((indexName) => { + const objectIDs = objectIDsByIndexName[indexName]; + + return { + index: indexName, + objectIDs, + }; + }); +} diff --git a/packages/autocomplete-plugin-algolia-insights/src/index.ts b/packages/autocomplete-plugin-algolia-insights/src/index.ts new file mode 100644 index 000000000..aeaabf287 --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/index.ts @@ -0,0 +1 @@ +export * from './createAlgoliaInsightsPlugin'; diff --git a/packages/autocomplete-plugin-algolia-insights/src/isAlgoliaInsightsHit.ts b/packages/autocomplete-plugin-algolia-insights/src/isAlgoliaInsightsHit.ts new file mode 100644 index 000000000..463b0ad6e --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/isAlgoliaInsightsHit.ts @@ -0,0 +1,7 @@ +import { AlgoliaInsightsHit } from './types'; + +export function isAlgoliaInsightsHit(hit: any): hit is AlgoliaInsightsHit { + return ( + hit.objectID && hit.__autocomplete_indexName && hit.__autocomplete_queryID + ); +} diff --git a/packages/autocomplete-plugin-algolia-insights/src/types/AlgoliaInsightsHit.ts b/packages/autocomplete-plugin-algolia-insights/src/types/AlgoliaInsightsHit.ts new file mode 100644 index 000000000..6b1a4f8e4 --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/types/AlgoliaInsightsHit.ts @@ -0,0 +1,5 @@ +export type AlgoliaInsightsHit = { + objectID: string; + __autocomplete_indexName: string; + __autocomplete_queryID: string; +}; diff --git a/packages/autocomplete-plugin-algolia-insights/src/types/EventParams.ts b/packages/autocomplete-plugin-algolia-insights/src/types/EventParams.ts new file mode 100644 index 000000000..b66d1d406 --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/types/EventParams.ts @@ -0,0 +1,24 @@ +import { AutocompleteState } from '@algolia/autocomplete-core'; + +import { + ClickedObjectIDsAfterSearchParams, + ViewedObjectIDsParams, +} from './InsightsApi'; + +import { AlgoliaInsightsHit, InsightsApi } from '.'; + +export type OnSelectParams = { + insights: InsightsApi; + insightsEvents: ClickedObjectIDsAfterSearchParams[]; + item: AlgoliaInsightsHit; + state: AutocompleteState; + event: any; +}; + +export type OnHighlightParams = OnSelectParams; + +export type OnItemsChangeParams = { + insights: InsightsApi; + insightsEvents: ViewedObjectIDsParams[]; + state: AutocompleteState; +}; diff --git a/packages/autocomplete-plugin-algolia-insights/src/types/InsightsApi.ts b/packages/autocomplete-plugin-algolia-insights/src/types/InsightsApi.ts new file mode 100644 index 000000000..82d465832 --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/types/InsightsApi.ts @@ -0,0 +1,62 @@ +import { createSearchInsightsApi } from '../createSearchInsightsApi'; + +export type InsightsApi = ReturnType; + +export type ClickedObjectIDsAfterSearchParams = { + eventName: string; + index: string; + objectIDs: string[]; + positions: number[]; + queryID: string; + userToken?: string; +}; + +export type ClickedObjectIDsParams = { + eventName: string; + index: string; + objectIDs: string[]; + userToken?: string; +}; + +export type ClickedFiltersParams = { + eventName: string; + filters: string[]; + index: string; + userToken: string; +}; + +export type ConvertedObjectIDsAfterSearchParams = { + eventName: string; + index: string; + objectIDs: string[]; + queryID: string; + userToken?: string; +}; + +export type ConvertedObjectIDsParams = { + eventName: string; + index: string; + objectIDs: string[]; + userToken: string; +}; + +export type ConvertedFiltersParams = { + eventName: string; + filters: string[]; + index: string; + userToken: string; +}; + +export type ViewedObjectIDsParams = { + eventName: string; + index: string; + objectIDs: string[]; + userToken?: string; +}; + +export type ViewedFiltersParams = { + eventName: string; + filters: string[]; + index: string; + userToken: string; +}; diff --git a/packages/autocomplete-plugin-algolia-insights/src/types/InsightsClient.ts b/packages/autocomplete-plugin-algolia-insights/src/types/InsightsClient.ts new file mode 100644 index 000000000..8eb9865e0 --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/types/InsightsClient.ts @@ -0,0 +1 @@ +export type InsightsClient = any; diff --git a/packages/autocomplete-plugin-algolia-insights/src/types/index.ts b/packages/autocomplete-plugin-algolia-insights/src/types/index.ts new file mode 100644 index 000000000..7d8c8a35f --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/src/types/index.ts @@ -0,0 +1,4 @@ +export * from './AlgoliaInsightsHit'; +export * from './InsightsApi'; +export * from './InsightsClient'; +export * from './EventParams'; diff --git a/packages/autocomplete-plugin-algolia-insights/tsconfig.declaration.json b/packages/autocomplete-plugin-algolia-insights/tsconfig.declaration.json new file mode 100644 index 000000000..1e0c6449f --- /dev/null +++ b/packages/autocomplete-plugin-algolia-insights/tsconfig.declaration.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.declaration" +} diff --git a/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts b/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts index 4b9934f00..9161368f0 100644 --- a/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts +++ b/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts @@ -1,5 +1,8 @@ import { AutocompletePlugin } from '@algolia/autocomplete-core'; -import { getAlgoliaHits, SourceTemplates } from '@algolia/autocomplete-js'; +import { + getAlgoliaHits as defaultGetAlgoliaHits, + SourceTemplates, +} from '@algolia/autocomplete-js'; import { SearchOptions } from '@algolia/client-search'; import { SearchClient } from 'algoliasearch/lite'; @@ -16,6 +19,7 @@ export type CreateQuerySuggestionsPluginParams< indexName: string; getSearchParams?(): SearchOptions; getTemplates?(params: GetTemplatesParams): SourceTemplates; + getAlgoliaHits?: typeof defaultGetAlgoliaHits; }; export function createQuerySuggestionsPlugin< @@ -25,6 +29,7 @@ export function createQuerySuggestionsPlugin< indexName, getSearchParams = () => ({}), getTemplates = defaultGetTemplates, + getAlgoliaHits = defaultGetAlgoliaHits, }: CreateQuerySuggestionsPluginParams): AutocompletePlugin< TItem, undefined diff --git a/packages/autocomplete-plugin-recent-searches/src/createRecentSearchesPlugin.ts b/packages/autocomplete-plugin-recent-searches/src/createRecentSearchesPlugin.ts index c8a0ff6ed..44fda124c 100644 --- a/packages/autocomplete-plugin-recent-searches/src/createRecentSearchesPlugin.ts +++ b/packages/autocomplete-plugin-recent-searches/src/createRecentSearchesPlugin.ts @@ -36,8 +36,8 @@ export function createRecentSearchesPlugin({ const lastItemsRef: Ref> = { current: [] }; return { - subscribed: { - onSelect({ item, state, source }) { + subscribe({ onSelect }) { + onSelect(({ item, state, source }) => { const inputValue = source.getItemInputValue({ item, state }); if (inputValue) { @@ -46,7 +46,7 @@ export function createRecentSearchesPlugin({ query: inputValue, } as TItem); } - }, + }); }, onSubmit({ state }) { const { query } = state; diff --git a/packages/autocomplete-preset-algolia/src/index.ts b/packages/autocomplete-preset-algolia/src/index.ts index 0c8119f2a..8008cda6f 100644 --- a/packages/autocomplete-preset-algolia/src/index.ts +++ b/packages/autocomplete-preset-algolia/src/index.ts @@ -1,6 +1,6 @@ -export * from './search/getAlgoliaHits'; -export * from './search/getAlgoliaResults'; export * from './highlight/parseAlgoliaHitHighlight'; export * from './highlight/parseAlgoliaHitReverseHighlight'; export * from './highlight/parseAlgoliaHitReverseSnippet'; export * from './highlight/parseAlgoliaHitSnippet'; +export * from './search/getAlgoliaHits'; +export * from './search/getAlgoliaResults'; diff --git a/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts b/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts index 1f2434b60..6e4459b8e 100644 --- a/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts +++ b/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts @@ -9,6 +9,14 @@ export function getAlgoliaHits({ return search({ searchClient, queries }).then((response) => { const results = response.results; - return results.map((result) => result.hits); + return results.map((result) => + result.hits.map((hit) => { + return { + ...hit, + __autocomplete_indexName: result.index, + __autocomplete_queryID: result.queryID, + }; + }) + ); }); } diff --git a/packages/autocomplete-shared/src/__tests__/isEqual.test.ts b/packages/autocomplete-shared/src/__tests__/isEqual.test.ts new file mode 100644 index 000000000..ce9586930 --- /dev/null +++ b/packages/autocomplete-shared/src/__tests__/isEqual.test.ts @@ -0,0 +1,202 @@ +import { isEqual } from '../isEqual'; + +describe('isEqual', () => { + describe('with primitives', () => { + test('with null should be true', () => { + const first = null; + const second = null; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with undefined should be true', () => { + const first = undefined; + const second = undefined; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with same booleans should be true', () => { + const first = true; + const second = true; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with different booleans should be false', () => { + const first = true; + const second = false; + + expect(isEqual(first, second)).toEqual(false); + }); + + test('with same numbers should be true', () => { + const first = 1; + const second = 1; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with different numbers should be false', () => { + const first = 1; + const second = 2; + + expect(isEqual(first, second)).toEqual(false); + }); + + test('with same strings should be true', () => { + const first = 'string'; + const second = 'string'; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with different strings should be false', () => { + const first = 'string1'; + const second = 'string2'; + + expect(isEqual(first, second)).toEqual(false); + }); + + test('with same symbols should be true', () => { + const first = Symbol('42'); + const second = first; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with different symbols should be false', () => { + const first = Symbol('42'); + const second = Symbol('42'); + + expect(isEqual(first, second)).toEqual(false); + }); + }); + + describe('with functions', () => { + test('with same functions should be true', () => { + const first = function a(): void {}; + const second = first; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with different functions should be false', () => { + const first = function a(): void {}; + const second = function a(): void {}; + + expect(isEqual(first, second)).toEqual(false); + }); + }); + + describe('with arrays', () => { + test('with same array values should be true', () => { + const first = ['Alphonse', 'Fred']; + const second = ['Alphonse', 'Fred']; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with different array values should be false', () => { + const first = ['Alphonse', 'Fred']; + const second = ['Adeline', 'Fred']; + + expect(isEqual(first, second)).toEqual(false); + }); + }); + + describe('with objects', () => { + test('with same reference should be true', () => { + const first = { name: 'Alfred' }; + const second = first; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with different number of keys should be false', () => { + const first = { name: 'Alfred' }; + const second = { name: 'Alfred', age: 33 }; + + expect(isEqual(first, second)).toEqual(false); + }); + + test('with different keys types should be false', () => { + const first = { name: 'Alfred', age: 33 }; + const second = { name: 'Alfred', age: '33' }; + + expect(isEqual(first, second)).toEqual(false); + }); + + test('with different keys should be false', () => { + const first = { name: 'Alfred' }; + const second = { firstName: 'Alfred' }; + + expect(isEqual(first, second)).toEqual(false); + }); + + test('with different values should be false', () => { + const first = { name: 'Alfred' }; + const second = { name: 'Georges' }; + + expect(isEqual(first, second)).toEqual(false); + }); + + test('with equal nested objects should be true', () => { + const first = { name: { first: 'John', last: 'Doe' } }; + const second = { name: { first: 'John', last: 'Doe' } }; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with different nested objects should be false', () => { + const first = { name: { first: 'John', last: 'Doe' } }; + const second = { name: { first: 'Jane', last: 'Doe' } }; + + expect(isEqual(first, second)).toEqual(false); + }); + + test('with full equal objets should be true', () => { + const first = { + index: '', + query: '', + facets: [], + facetsRefinements: {}, + tagRefinements: [], + numericFilters: undefined, + }; + + const second = { + index: '', + query: '', + facets: [], + facetsRefinements: {}, + tagRefinements: [], + numericFilters: undefined, + }; + + expect(isEqual(first, second)).toEqual(true); + }); + + test('with full different objets should be false', () => { + const first = { + index: '', + query: 'first query', + facets: [], + facetsRefinements: {}, + tagRefinements: [], + numericFilters: undefined, + }; + + const second = { + index: '', + query: '', + facets: ['brand'], + facetsRefinements: {}, + tagRefinements: [], + numericFilters: undefined, + }; + + expect(isEqual(first, second)).toEqual(false); + }); + }); +}); diff --git a/packages/autocomplete-shared/src/debounce.ts b/packages/autocomplete-shared/src/debounce.ts new file mode 100644 index 000000000..c42edaf84 --- /dev/null +++ b/packages/autocomplete-shared/src/debounce.ts @@ -0,0 +1,11 @@ +export function debounce(fn: (params: TParams) => void, time: number) { + let timerId: ReturnType | undefined = undefined; + + return function (args: TParams) { + if (timerId) { + clearTimeout(timerId); + } + + timerId = setTimeout(() => fn(args), time); + }; +} diff --git a/packages/autocomplete-shared/src/index.ts b/packages/autocomplete-shared/src/index.ts index 6e8a9b12b..3a2bf1e50 100644 --- a/packages/autocomplete-shared/src/index.ts +++ b/packages/autocomplete-shared/src/index.ts @@ -1,3 +1,6 @@ export * from './createRef'; -export * from './warn'; +export * from './debounce'; +export * from './isEqual'; export * from './MaybePromise'; +export * from './warn'; +export * from './warn'; diff --git a/packages/autocomplete-shared/src/isEqual.ts b/packages/autocomplete-shared/src/isEqual.ts new file mode 100644 index 000000000..3883689c0 --- /dev/null +++ b/packages/autocomplete-shared/src/isEqual.ts @@ -0,0 +1,34 @@ +function isPrimitive(obj: any): boolean { + return obj !== Object(obj); +} + +export function isEqual(first: any, second: any): boolean { + if (first === second) { + return true; + } + + if ( + isPrimitive(first) || + isPrimitive(second) || + typeof first === 'function' || + typeof second === 'function' + ) { + return first === second; + } + + if (Object.keys(first).length !== Object.keys(second).length) { + return false; + } + + for (const key of Object.keys(first)) { + if (!(key in second)) { + return false; + } + + if (!isEqual(first[key], second[key])) { + return false; + } + } + + return true; +} diff --git a/ship.config.js b/ship.config.js index 707466a9f..7d0ec70f8 100644 --- a/ship.config.js +++ b/ship.config.js @@ -6,6 +6,7 @@ const packages = [ 'packages/autocomplete-core', 'packages/autocomplete-js', 'packages/autocomplete-preset-algolia', + 'packages/autocomplete-plugin-algolia-insights', 'packages/autocomplete-plugin-recent-searches', 'packages/autocomplete-plugin-query-suggestions', ]; diff --git a/yarn.lock b/yarn.lock index 436d6578b..afe48b43f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,6 +9,13 @@ dependencies: "@algolia/cache-common" "4.6.0" +"@algolia/cache-browser-local-storage@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.7.0.tgz#e1770b6ce6a810e326bdf2c830feec7c6a6d3c14" + integrity sha512-qkGCcvyj/h/OCvKM/dZS7g8zwExxdx07r4Zb/vw18TAgTc4XHHiZIpEJt1u27iiYSxoXM0+prVGc/kMWjvoA6g== + dependencies: + "@algolia/cache-common" "4.7.0" + "@algolia/cache-browser-local-storage@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.8.0.tgz#42137b8e18474e8a94bd7206d7e4ada2daf5da04" @@ -28,6 +35,11 @@ resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.6.0.tgz#f67da65766a004368db0833caf4890fe450d8f0a" integrity sha512-mEedrPb2O3WwtiIHggFoIhTbHVCMNikxMiiN9kqmwZkdDfClfxm435OUGZfAl67rBZfc0DNs/jmPM2mUoefM9A== +"@algolia/cache-common@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.7.0.tgz#6622d59fa419be1c0a987f95165fd8ad28739411" + integrity sha512-b/4tmdDedD7r+ZRy0t4iKRMsyApz5mJUWAlxLjQ36sQz5R54H3yTs/zw3JwMAAZ2yaOsP+5gdaqPFf20m+O0Cg== + "@algolia/cache-common@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.8.0.tgz#fd57e4bfb75c93ae82c1cf047fa81511d720cfd9" @@ -45,6 +57,13 @@ dependencies: "@algolia/cache-common" "4.6.0" +"@algolia/cache-in-memory@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.7.0.tgz#278483f80deef36f154673a639be7283c2d7af5b" + integrity sha512-cMGKB7xQhn7kRHp7MPuBgZXMMgIYLJRgPz8c0497hfe3PBp9ySIi3UI36zABuEDkWOiGC/LrHPpc5fkxkOny1Q== + dependencies: + "@algolia/cache-common" "4.7.0" + "@algolia/cache-in-memory@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.8.0.tgz#7716ec2dd797f890e69fb48e44add5ae1faa1ad3" @@ -68,6 +87,15 @@ "@algolia/client-search" "4.6.0" "@algolia/transporter" "4.6.0" +"@algolia/client-account@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.7.0.tgz#1925d3dc8fb7e2333ff9b8d0e7fc9ebc0370e744" + integrity sha512-exO+abGUBhz05YeFGxJep09coXWCzE/wxir6CyiiseQshAWSgWEjYatKIsBspMBFlE702eWVM+wlRddCuE57Ng== + dependencies: + "@algolia/client-common" "4.7.0" + "@algolia/client-search" "4.7.0" + "@algolia/transporter" "4.7.0" + "@algolia/client-account@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.8.0.tgz#0b88d04a1733fc48b813db4e1d7a8226062ae79b" @@ -96,6 +124,16 @@ "@algolia/requester-common" "4.6.0" "@algolia/transporter" "4.6.0" +"@algolia/client-analytics@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.7.0.tgz#19d5381c59ec53152084a9c2fa338e4c7f663094" + integrity sha512-aVGnG6jsTKsX3n2Q22WqUBUpfkF20ttGNrdRzS8e9ng48da37PLFcAzsz+Nm2g5+2XaEEKm5KN5tBAIRGDuP5Q== + dependencies: + "@algolia/client-common" "4.7.0" + "@algolia/client-search" "4.7.0" + "@algolia/requester-common" "4.7.0" + "@algolia/transporter" "4.7.0" + "@algolia/client-analytics@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.8.0.tgz#3bd928f20e20c15feb0cb3741ac581bfb8aecd51" @@ -124,6 +162,14 @@ "@algolia/requester-common" "4.6.0" "@algolia/transporter" "4.6.0" +"@algolia/client-common@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.7.0.tgz#1e9235f54a7845f5dfc74cd753a5bc497478e4e7" + integrity sha512-0DR3Brl72gr/WpGdlDqX4qSmlmKADBmOEceH0bvmET6IHnCqkH+mL8nifesp+E4u1g6Jqv3rd5H9oqN7MixpDQ== + dependencies: + "@algolia/requester-common" "4.7.0" + "@algolia/transporter" "4.7.0" + "@algolia/client-common@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.8.0.tgz#3c3fcfa135fae2813d2be13e23282714cc306b98" @@ -149,6 +195,15 @@ "@algolia/requester-common" "4.6.0" "@algolia/transporter" "4.6.0" +"@algolia/client-recommendation@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/client-recommendation/-/client-recommendation-4.7.0.tgz#67c0ddbe3039b9fbf2e1a71ab114ae345340a0d9" + integrity sha512-gmRX8ykToLZNdeDZG5tIiFKx9uk7fZZ1YC2OjFvapPPbljO7dsmNzbFHZ1AT8GsKZMcjTZh5Azlm5vCB9FzAYA== + dependencies: + "@algolia/client-common" "4.7.0" + "@algolia/requester-common" "4.7.0" + "@algolia/transporter" "4.7.0" + "@algolia/client-recommendation@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/client-recommendation/-/client-recommendation-4.8.0.tgz#fbe6d49ad9a87ee0ffdd79491e1332a1711ebbd2" @@ -176,6 +231,15 @@ "@algolia/requester-common" "4.6.0" "@algolia/transporter" "4.6.0" +"@algolia/client-search@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.7.0.tgz#234e6c0ed66643d79f819152a29759b6557dbfb1" + integrity sha512-AzM9+x4zJN4pGi5Q55WF3y1UJi4iAAikxVUUG1ekQMFAIopaorlW66EetctHv4fSahPqbx5+42K2KTIXdrIACw== + dependencies: + "@algolia/client-common" "4.7.0" + "@algolia/requester-common" "4.7.0" + "@algolia/transporter" "4.7.0" + "@algolia/client-search@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.8.0.tgz#be9021adcb958f9e5478159e38f741b98e88f8ca" @@ -199,6 +263,11 @@ resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.6.0.tgz#966591b903eae60201d12c4126fbf278303e2d2a" integrity sha512-F+0HTGSQzJfWsX/cJq2l4eG2Y5JA6pqZ0YETyo5XJhZX4JaDrGszVKuOqp8kovZF/Ifebywxb8JdCiSUskmbig== +"@algolia/logger-common@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.7.0.tgz#b3d1d9671296acf63f58b846918de26ba2c6875f" + integrity sha512-L49C/2OrMMZaiA0VDponA8Mz7lAciv5tNzQKXiRPgRDYf666FgSWb7uqy193FBleKvYfdgUkooJHn6hQC2fc7Q== + "@algolia/logger-common@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.8.0.tgz#ef5541a39bc13cf2a8b024f8f3dc6e35b08bbffe" @@ -216,6 +285,13 @@ dependencies: "@algolia/logger-common" "4.6.0" +"@algolia/logger-console@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.7.0.tgz#b2b31c778f1619d28b9a024704f9e5c2bb1be711" + integrity sha512-sBUKM83xIY5GZzu4xmrwn4whZT6N4eOvY19nlHucywIQhGZI0AC0Bb/sXsaMzp5H7EJfntVH1hvvS9tGs1drIw== + dependencies: + "@algolia/logger-common" "4.7.0" + "@algolia/logger-console@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.8.0.tgz#4c2dd4c66788f7e47b91fe027c129f20c3006146" @@ -237,6 +313,13 @@ dependencies: "@algolia/requester-common" "4.6.0" +"@algolia/requester-browser-xhr@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.7.0.tgz#96d92583d2c294ac2dfb484856b155bf950dd247" + integrity sha512-sACLoGjYRIfgO/w916T2gWhaYQDEBWA6m6b/Wr0IHt1UBRCmKz3K2gbN8bvBdrbzWZp2qDXCyaIeZ7RK6mtJIg== + dependencies: + "@algolia/requester-common" "4.7.0" + "@algolia/requester-browser-xhr@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.8.0.tgz#30c59783ea865504aa843eb6ba49daf92bbfb6d1" @@ -256,6 +339,11 @@ resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.6.0.tgz#0990f3b21414a3ec5a3a468f33f2b9bff4489222" integrity sha512-DJ5iIGBGrRudimaaFnpBFM19pv8SsXiMYuukn9q1GgQh2mPPBCBBJiezKc7+OzE1UyCVrAFBpR/hrJnflZnRdQ== +"@algolia/requester-common@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.7.0.tgz#179189bd40d60654f22dec8b607233b12c9a62be" + integrity sha512-vmbzqj+eU/t28H274NCrcExYcTiS870iq1oe71cTKEwvKzBuMY8quFcpGAxkgQVmOtcTzoXqsNBofODeJcAg1g== + "@algolia/requester-common@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.8.0.tgz#515d46294ec14a982ea75ac9ce97e20566c1c576" @@ -273,6 +361,13 @@ dependencies: "@algolia/requester-common" "4.6.0" +"@algolia/requester-node-http@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.7.0.tgz#3bd0fd6c8ee4ba73c2a414545c57e0b69b5633d9" + integrity sha512-3SXjpNSo6grpSpmKsSOIhhTflWcrPa60+foExsXxt7eM5KD5WlWop6EkSt3lbfOH+71bpNciXeylFXakJzanRg== + dependencies: + "@algolia/requester-common" "4.7.0" + "@algolia/requester-node-http@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.8.0.tgz#7bf308a91f7b1854df91c5855fa9313949a70607" @@ -296,6 +391,15 @@ "@algolia/logger-common" "4.6.0" "@algolia/requester-common" "4.6.0" +"@algolia/transporter@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.7.0.tgz#dc2ec996b9ca0a2cf960cea36c1921fd357c6915" + integrity sha512-gp65yvDBZcRdriUzLuMz8PUGg28CSYevrRc0i2FQx63OJ/ERmbp0aRZrZeDPxZ56E5J1txNeh02yyXpp0CW56A== + dependencies: + "@algolia/cache-common" "4.7.0" + "@algolia/logger-common" "4.7.0" + "@algolia/requester-common" "4.7.0" + "@algolia/transporter@4.8.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.8.0.tgz#4ddf9db11c946ddbbd640ad3bced38147734a24a" @@ -4406,6 +4510,26 @@ algoliasearch@4.6.0: "@algolia/requester-node-http" "4.6.0" "@algolia/transporter" "4.6.0" +algoliasearch@4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.7.0.tgz#5be53ad7588b4ec1ec6504fc61e9adacea9f2461" + integrity sha512-QxPtWy2EgU74S0OCO5GsSE2OlqEugJqsR8vo2fnBMl+W/VNGB/rRYxBjf3OsR+UYJ8mGYwByyCHafkknBX3cow== + dependencies: + "@algolia/cache-browser-local-storage" "4.7.0" + "@algolia/cache-common" "4.7.0" + "@algolia/cache-in-memory" "4.7.0" + "@algolia/client-account" "4.7.0" + "@algolia/client-analytics" "4.7.0" + "@algolia/client-common" "4.7.0" + "@algolia/client-recommendation" "4.7.0" + "@algolia/client-search" "4.7.0" + "@algolia/logger-common" "4.7.0" + "@algolia/logger-console" "4.7.0" + "@algolia/requester-browser-xhr" "4.7.0" + "@algolia/requester-common" "4.7.0" + "@algolia/requester-node-http" "4.7.0" + "@algolia/transporter" "4.7.0" + algoliasearch@4.8.0: version "4.8.0" resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.8.0.tgz#12bb88b4093b05a5afdc369d3e8b83036d5f4822" @@ -16950,6 +17074,11 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +search-insights@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-1.6.2.tgz#a592c0fcafdb628e37db92d6a3fbd113304521da" + integrity sha512-mpy+57HZVMZH5HsMHYMCLvkf+tUvhy+ycP2tDy1j7wmj+mQsNZ3LC61IcMYomok02NozaMR3GiGyfH6uc+ibdA== + section-matter@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167"