diff --git a/packages/instantsearch.js/src/types/render-state.ts b/packages/instantsearch.js/src/types/render-state.ts index 4064993dee..9cf9fdd7b4 100644 --- a/packages/instantsearch.js/src/types/render-state.ts +++ b/packages/instantsearch.js/src/types/render-state.ts @@ -23,7 +23,6 @@ import type { SortByWidgetDescription } from '../connectors/sort-by/connectSortB import type { StatsWidgetDescription } from '../connectors/stats/connectStats'; import type { ToggleRefinementWidgetDescription } from '../connectors/toggle-refinement/connectToggleRefinement'; import type { VoiceSearchWidgetDescription } from '../connectors/voice-search/connectVoiceSearch'; -import type { AnalyticsWidgetDescription } from '../widgets/analytics/analytics'; import type { PlacesWidgetDescription } from '../widgets/places/places'; type ConnectorRenderStates = AnswersWidgetDescription['indexRenderState'] & @@ -52,8 +51,7 @@ type ConnectorRenderStates = AnswersWidgetDescription['indexRenderState'] & ToggleRefinementWidgetDescription['indexRenderState'] & VoiceSearchWidgetDescription['indexRenderState']; -type WidgetRenderStates = AnalyticsWidgetDescription['indexRenderState'] & - PlacesWidgetDescription['indexRenderState']; +type WidgetRenderStates = PlacesWidgetDescription['indexRenderState']; export type IndexRenderState = Partial< ConnectorRenderStates & WidgetRenderStates diff --git a/packages/instantsearch.js/src/types/widget.ts b/packages/instantsearch.js/src/types/widget.ts index 580ef3ea22..aa7014e15d 100644 --- a/packages/instantsearch.js/src/types/widget.ts +++ b/packages/instantsearch.js/src/types/widget.ts @@ -56,7 +56,6 @@ export type DisposeOptions = { // @MAJOR: Remove these exported types if we don't need them export type BuiltinTypes = - | 'ais.analytics' | 'ais.answers' | 'ais.autocomplete' | 'ais.breadcrumb' @@ -95,7 +94,6 @@ export type BuiltinTypes = | 'ais.voiceSearch'; export type BuiltinWidgetTypes = - | 'ais.analytics' | 'ais.answers' | 'ais.autocomplete' | 'ais.breadcrumb' diff --git a/packages/instantsearch.js/src/widgets/__tests__/index.test.ts b/packages/instantsearch.js/src/widgets/__tests__/index.test.ts index 9b1375e01b..a0503bc1af 100644 --- a/packages/instantsearch.js/src/widgets/__tests__/index.test.ts +++ b/packages/instantsearch.js/src/widgets/__tests__/index.test.ts @@ -100,12 +100,6 @@ function initiateAllWidgets(): Array<[WidgetNames, Widget | IndexWidget]> { items: [{ label: 'x', value: 'x' }], }); } - case 'analytics': { - const analytics = widget as Widgets['analytics']; - return analytics({ - pushFunction() {}, - }); - } case 'queryRuleContext': { const queryRuleContext = widget as Widgets['queryRuleContext']; return queryRuleContext({ diff --git a/packages/instantsearch.js/src/widgets/analytics/__tests__/analytics-test.ts b/packages/instantsearch.js/src/widgets/analytics/__tests__/analytics-test.ts deleted file mode 100644 index cad30f0900..0000000000 --- a/packages/instantsearch.js/src/widgets/analytics/__tests__/analytics-test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import analytics from '../analytics'; - -describe('Usage', () => { - it('throws without `pushFunction`', () => { - expect(() => { - analytics({ - // @ts-expect-error - pushFunction: undefined, - }); - }).toThrowErrorMatchingInlineSnapshot(` -"The \`pushFunction\` option is required. - -See documentation: https://www.algolia.com/doc/api-reference/widgets/analytics/js/" -`); - }); - - it('shows deprecation warning message.', () => { - expect(() => { - analytics({ - pushFunction: () => {}, - }); - }).toWarnDev( - `[InstantSearch.js]: \`analytics\` widget has been deprecated. It is still supported in 4.x releases, but not further. It is replaced by the \`insights\` middleware. - -For the migration, visit https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/js/#analytics-widget` - ); - }); -}); diff --git a/packages/instantsearch.js/src/widgets/analytics/analytics.ts b/packages/instantsearch.js/src/widgets/analytics/analytics.ts deleted file mode 100644 index 90bf415723..0000000000 --- a/packages/instantsearch.js/src/widgets/analytics/analytics.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { createDocumentationMessageGenerator, warning } from '../../lib/utils'; - -import type { WidgetFactory, WidgetRenderState } from '../../types'; -import type { SearchParameters, SearchResults } from 'algoliasearch-helper'; - -export type AnalyticsWidgetParamsPushFunction = ( - /** - * Contains the search parameters, serialized as a query string. - */ - formattedParameters: string, - - /** - * Contains the whole search state. - */ - state: SearchParameters, - - /** - * The last received results. - */ - results: SearchResults -) => void; - -export type AnalyticsWidgetParams = { - /** - * A function that is called every time the query or refinements changes. You - * need to add the logic to push the data to your analytics platform. - */ - pushFunction: AnalyticsWidgetParamsPushFunction; - - /** - * The number of milliseconds between the last search keystroke and calling `pushFunction`. - * - * @default 3000 - */ - delay?: number; - - /** - * Triggers `pushFunction` after click on page or redirecting the page. This is useful if - * you want the pushFunction to be called for the last actions before the user leaves the - * current page, even if the delay wasn’t reached. - * - * @default false - */ - triggerOnUIInteraction?: boolean; - - /** - * Triggers `pushFunction` when InstantSearch is initialized. This means - * the `pushFunction` might be called even though the user didn’t perfom - * any search-related action. - * - * @default true - */ - pushInitialSearch?: boolean; - - /** - * Triggers `pushFunction` when the page changes, either through the UI or programmatically. - * - * @default false - */ - pushPagination?: boolean; -}; - -const withUsage = createDocumentationMessageGenerator({ name: 'analytics' }); - -export type AnalyticsWidgetDescription = { - $$type: 'ais.analytics'; - $$widgetType: 'ais.analytics'; - renderState: Record; - indexRenderState: { - analytics: WidgetRenderState< - Record, - AnalyticsWidgetParams - >; - }; -}; - -export type AnalyticsWidget = WidgetFactory< - AnalyticsWidgetDescription, - AnalyticsWidgetParams, - AnalyticsWidgetParams ->; - -// @major this widget will be removed from the next major version. -const analytics: AnalyticsWidget = function analytics(widgetParams) { - const { - pushFunction, - delay = 3000, - triggerOnUIInteraction = false, - pushInitialSearch = true, - pushPagination = false, - } = widgetParams || {}; - - if (!pushFunction) { - throw new Error(withUsage('The `pushFunction` option is required.')); - } - - warning( - false, - `\`analytics\` widget has been deprecated. It is still supported in 4.x releases, but not further. It is replaced by the \`insights\` middleware. - -For the migration, visit https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/js/#analytics-widget` - ); - - type AnalyticsState = { - results: SearchResults; - state: SearchParameters; - } | null; - - let cachedState: AnalyticsState = null; - - type RefinementParameters = { - [key: string]: string[]; - }; - - const serializeRefinements = function ( - parameters: RefinementParameters - ): string { - const refinements: string[] = []; - - // eslint-disable-next-line no-restricted-syntax - for (const parameter in parameters) { - if (parameters.hasOwnProperty(parameter)) { - const values = parameters[parameter].join('+'); - refinements.push( - `${encodeURIComponent(parameter)}=${encodeURIComponent( - parameter - )}_${encodeURIComponent(values)}` - ); - } - } - - return refinements.join('&'); - }; - - const serializeNumericRefinements = function ( - numericRefinements: SearchParameters['numericRefinements'] - ): string { - const refinements: string[] = []; - - // eslint-disable-next-line no-restricted-syntax - for (const attribute in numericRefinements) { - if (numericRefinements.hasOwnProperty(attribute)) { - const filter = numericRefinements[attribute]; - - if (filter.hasOwnProperty('>=') && filter.hasOwnProperty('<=')) { - if ( - filter['>='] && - filter['>='][0] === filter['<='] && - filter['<='][0] - ) { - refinements.push(`${attribute}=${attribute}_${filter['>=']}`); - } else { - refinements.push( - `${attribute}=${attribute}_${filter['>=']}to${filter['<=']}` - ); - } - } else if (filter.hasOwnProperty('>=')) { - refinements.push(`${attribute}=${attribute}_from${filter['>=']}`); - } else if (filter.hasOwnProperty('<=')) { - refinements.push(`${attribute}=${attribute}_to${filter['<=']}`); - } else if (filter.hasOwnProperty('=')) { - const equals: string[] = []; - - // eslint-disable-next-line no-restricted-syntax - for (const equal in filter['=']) { - // eslint-disable-next-line max-depth - if (filter['='].hasOwnProperty(equal)) { - // @ts-ignore somehow 'equal' is a string, even though it's a number? - equals.push(filter['='][equal]); - } - } - - refinements.push(`${attribute}=${attribute}_${equals.join('-')}`); - } - } - } - - return refinements.join('&'); - }; - - let lastSentData = ''; - - const sendAnalytics = function (analyticsState: AnalyticsState | null): void { - if (analyticsState === null) { - return; - } - - const serializedParams: string[] = []; - - const serializedRefinements = serializeRefinements({ - ...analyticsState.state.disjunctiveFacetsRefinements, - ...analyticsState.state.facetsRefinements, - ...analyticsState.state.hierarchicalFacetsRefinements, - }); - - const serializedNumericRefinements = serializeNumericRefinements( - analyticsState.state.numericRefinements - ); - - if (serializedRefinements !== '') { - serializedParams.push(serializedRefinements); - } - - if (serializedNumericRefinements !== '') { - serializedParams.push(serializedNumericRefinements); - } - - const stringifiedParams = serializedParams.join('&'); - - let dataToSend = `Query: ${ - analyticsState.state.query || '' - }, ${stringifiedParams}`; - if (pushPagination === true) { - dataToSend += `, Page: ${analyticsState.state.page || 0}`; - } - - if (lastSentData !== dataToSend) { - pushFunction( - stringifiedParams, - analyticsState.state, - analyticsState.results - ); - - lastSentData = dataToSend; - } - }; - - let pushTimeout: number; - let isInitialSearch = true; - - if (pushInitialSearch === true) { - isInitialSearch = false; - } - - const onClick = (): void => { - sendAnalytics(cachedState); - }; - - const onUnload = (): void => { - sendAnalytics(cachedState); - }; - - return { - $$type: 'ais.analytics', - $$widgetType: 'ais.analytics', - - init() { - if (triggerOnUIInteraction === true) { - document.addEventListener('click', onClick); - window.addEventListener('beforeunload', onUnload); - } - }, - - render({ results, state }) { - if (isInitialSearch === true) { - isInitialSearch = false; - - return; - } - - cachedState = { results, state }; - - if (pushTimeout) { - clearTimeout(pushTimeout); - } - - pushTimeout = window.setTimeout(() => sendAnalytics(cachedState), delay); - }, - - dispose() { - if (triggerOnUIInteraction === true) { - document.removeEventListener('click', onClick); - window.removeEventListener('beforeunload', onUnload); - } - }, - - getRenderState(renderState, renderOptions) { - return { - ...renderState, - analytics: this.getWidgetRenderState(renderOptions), - }; - }, - - getWidgetRenderState() { - return { - widgetParams, - }; - }, - }; -}; - -export default analytics; diff --git a/packages/instantsearch.js/src/widgets/index.ts b/packages/instantsearch.js/src/widgets/index.ts index 6fd492c520..63e980f10b 100644 --- a/packages/instantsearch.js/src/widgets/index.ts +++ b/packages/instantsearch.js/src/widgets/index.ts @@ -23,7 +23,6 @@ export const EXPERIMENTAL_dynamicWidgets = deprecate( ); export { dynamicWidgets }; -export { default as analytics } from './analytics/analytics'; export { default as breadcrumb } from './breadcrumb/breadcrumb'; export { default as clearRefinements } from './clear-refinements/clear-refinements'; export { default as configure } from './configure/configure'; diff --git a/packages/instantsearch.js/stories/analytics.stories.ts b/packages/instantsearch.js/stories/analytics.stories.ts deleted file mode 100644 index 97ee328954..0000000000 --- a/packages/instantsearch.js/stories/analytics.stories.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { storiesOf } from '@storybook/html'; - -import { withHits } from '../.storybook/decorators'; - -storiesOf('Metadata/Analytics', module).add( - 'default', - withHits(({ search, container, instantsearch }) => { - const description = document.createElement('p'); - description.innerText = 'Search for something, look into Action Logger'; - container.appendChild(description); - - search.addWidgets([ - instantsearch.widgets.analytics({ - pushFunction(formattedParameters, state, results) { - action('pushFunction[formattedParameters]')(formattedParameters); - action('pushFunction[state]')(state); - action('pushFunction[results]')(results); - }, - triggerOnUIInteraction: true, - pushInitialSearch: false, - }), - ]); - }) -);