-
Notifications
You must be signed in to change notification settings - Fork 516
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Insights): Insights inside Instantsearch (#3598)
* feat(Insights): integrate Insights (Click Analytics) with InstantSearch feat(Insights): add withInsightsClient connector wrapper This PR adds a withInsightsClient which is an HOC for connectors. ```js const connectHitsWithInsightsClient = withInsightsClient(connectHits) ``` This connector will be used by default in the widget Hits and InfiniteHits to wrap connectHits and connectInfiniteHits respectively. When this PR is merged and released we can start using withInsightsClient in the flavours. feat(Insights): add template helpers feat(Insights): add withInsightsListener This commit adds withInsightsListener which - wraps Hits and InfiniteHits component, - listens to inner clicks targetting elements with data-insights attributes - calls the insights client exposed by `withInsightsClient` ```js const HitsWithInsightsListener = withInsightsListener(Hits) ``` feat(Insights): allow passing insightsClient to instantsearch instance feat(Insights): add listener to Hits feat(Insights): add listener to InfiniteHits feat(Insights): add storybook example for hits feat(Insights & typescript): type all the things extract *WithInsightsListeners components to upper scope rename withInsightsClient to withInsights feat(Insights): fix positions in infiniteScroll feat(Insights): extract addAbsolutePositions to make it connector responsibility * feat(Insights): expose `insights` helper on instantsearch instance This is mimicking the way we allow usage for `highlight` and `snippet`. ```js Instantsearch.widgets.hits({ // ... templates: { item(hit) { return ` <h2> ${hit.name} </h2> <button ${ Instantsearch.insights('clickedObjectIDsAfterSearch', { eventName: "Add to favorite", objectIDs: [hit.objectID] }) }> Add to favorite </button> `; }, }, }); ``` * feat(Insights): expose connectHitsWithInsights and connectInfiniteHitsWithInsights The intention is to make it easier and more straight forward to create custom InfiniteHits and Hits with the connector. If we exposed `withInsights` directly, this is what we'd need to decide on a definitive name that implies it works only on Hits (like withHitsInsights) ```js const connectHitsWithInsights = Instantsearch.connectors.withHitsInsights(Instantsearch.connectors.connectHits); const connectInfiniteHitsWithInsights = Instantsearch.connectors.withHitsInsights(Instantsearch.connectors.connectInfiniteHits); ``` If we expose `connectHitsWithInsights` and `connectInfiniteHitsWithInsights` directly, we can keep `withInsights` totally private, and all custom components example are more simple.
- Loading branch information
Showing
38 changed files
with
1,422 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
src/connectors/hits/__tests__/connectHitsWithInsights-test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import jsHelper, { SearchResults } from 'algoliasearch-helper'; | ||
import connectHitsWithInsights from '../connectHitsWithInsights'; | ||
import { Client } from '../../../types'; | ||
|
||
jest.mock('../../../lib/utils/hits-absolute-position', () => ({ | ||
addAbsolutePosition: hits => hits, | ||
})); | ||
|
||
describe('connectHitsWithInsights', () => { | ||
it('should expose `insights` props', () => { | ||
const rendering = jest.fn(); | ||
const makeWidget = connectHitsWithInsights(rendering, jest.fn()); | ||
const widget: any = makeWidget({}); | ||
|
||
const helper = jsHelper({} as Client, '', {}); | ||
helper.search = jest.fn(); | ||
|
||
widget.init({ | ||
helper, | ||
state: helper.state, | ||
createURL: () => '#', | ||
onHistoryChange: () => {}, | ||
instantSearchInstance: { | ||
insightsClient: jest.fn(), | ||
}, | ||
}); | ||
|
||
const firstRenderingOptions = rendering.mock.calls[0][0]; | ||
expect(firstRenderingOptions.insights).toBeUndefined(); | ||
|
||
const hits = [{ fake: 'data' }, { sample: 'infos' }]; | ||
const results = new SearchResults(helper.state, [{ hits }]); | ||
widget.render({ | ||
results, | ||
state: helper.state, | ||
helper, | ||
createURL: () => '#', | ||
instantSearchInstance: { | ||
insightsClient: jest.fn(), | ||
}, | ||
}); | ||
|
||
const secondRenderingOptions = rendering.mock.calls[1][0]; | ||
expect(secondRenderingOptions.insights).toBeInstanceOf(Function); | ||
}); | ||
|
||
it('should preserve props exposed by connectHits', () => { | ||
const rendering = jest.fn(); | ||
const makeWidget = connectHitsWithInsights(rendering, jest.fn()); | ||
const widget: any = makeWidget({}); | ||
|
||
const helper = jsHelper({} as Client, '', {}); | ||
helper.search = jest.fn(); | ||
|
||
widget.init({ | ||
helper, | ||
state: helper.state, | ||
createURL: () => '#', | ||
onHistoryChange: () => {}, | ||
instantSearchInstance: { | ||
insightsClient: jest.fn(), | ||
}, | ||
}); | ||
|
||
const hits = [{ fake: 'data' }, { sample: 'infos' }]; | ||
const results = new SearchResults(helper.state, [{ hits }]); | ||
widget.render({ | ||
results, | ||
state: helper.state, | ||
helper, | ||
createURL: () => '#', | ||
instantSearchInstance: { | ||
insightsClient: jest.fn(), | ||
}, | ||
}); | ||
|
||
const secondRenderingOptions = rendering.mock.calls[1][0]; | ||
expect(secondRenderingOptions.hits).toEqual(expect.objectContaining(hits)); | ||
expect(secondRenderingOptions.results).toEqual(results); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { withInsights } from '../../lib/insights'; | ||
import connectHits from './connectHits'; | ||
|
||
const connectHitsWithInsights = withInsights(connectHits); | ||
|
||
export default connectHitsWithInsights; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
src/connectors/infinite-hits/__tests__/connectInfiniteHitsWithInsights-test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import jsHelper, { SearchResults } from 'algoliasearch-helper'; | ||
import connectInfiniteHitsWithInsights from '../connectInfiniteHitsWithInsights'; | ||
import { Client } from '../../../types'; | ||
|
||
jest.mock('../../../lib/utils/hits-absolute-position', () => ({ | ||
addAbsolutePosition: hits => hits, | ||
})); | ||
|
||
describe('connectInfiniteHitsWithInsights', () => { | ||
it('should expose `insights` props', () => { | ||
const rendering = jest.fn(); | ||
const makeWidget = connectInfiniteHitsWithInsights(rendering, jest.fn()); | ||
const widget: any = makeWidget({}); | ||
|
||
const helper = jsHelper({} as Client, '', {}); | ||
helper.search = jest.fn(); | ||
|
||
widget.init({ | ||
helper, | ||
state: helper.state, | ||
createURL: () => '#', | ||
onHistoryChange: () => {}, | ||
instantSearchInstance: { | ||
insightsClient: jest.fn(), | ||
}, | ||
}); | ||
|
||
const firstRenderingOptions = rendering.mock.calls[0][0]; | ||
expect(firstRenderingOptions.insights).toBeUndefined(); | ||
|
||
const hits = [{ fake: 'data' }, { sample: 'infos' }]; | ||
const results = new SearchResults(helper.state, [{ hits }]); | ||
widget.render({ | ||
results, | ||
state: helper.state, | ||
helper, | ||
createURL: () => '#', | ||
instantSearchInstance: { | ||
insightsClient: jest.fn(), | ||
}, | ||
}); | ||
|
||
const secondRenderingOptions = rendering.mock.calls[1][0]; | ||
expect(secondRenderingOptions.insights).toBeInstanceOf(Function); | ||
}); | ||
|
||
it('should preserve props exposed by connectInfiniteHits', () => { | ||
const rendering = jest.fn(); | ||
const makeWidget = connectInfiniteHitsWithInsights(rendering, jest.fn()); | ||
const widget: any = makeWidget({}); | ||
|
||
const helper = jsHelper({} as Client, '', {}); | ||
helper.search = jest.fn(); | ||
|
||
widget.init({ | ||
helper, | ||
state: helper.state, | ||
createURL: () => '#', | ||
onHistoryChange: () => {}, | ||
instantSearchInstance: { | ||
insightsClient: jest.fn(), | ||
}, | ||
}); | ||
|
||
const hits = [{ fake: 'data' }, { sample: 'infos' }]; | ||
const results = new SearchResults(helper.state, [{ hits }]); | ||
widget.render({ | ||
results, | ||
state: helper.state, | ||
helper, | ||
createURL: () => '#', | ||
instantSearchInstance: { | ||
insightsClient: jest.fn(), | ||
}, | ||
}); | ||
|
||
const secondRenderingOptions = rendering.mock.calls[1][0]; | ||
expect(secondRenderingOptions.hits).toEqual(expect.objectContaining(hits)); | ||
expect(secondRenderingOptions.results).toEqual(results); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
src/connectors/infinite-hits/connectInfiniteHitsWithInsights.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { withInsights } from '../../lib/insights'; | ||
import connectInfiniteHits from './connectInfiniteHits'; | ||
|
||
const connectInfiniteHitsWithInsights = withInsights(connectInfiniteHits); | ||
|
||
export default connectInfiniteHitsWithInsights; |
Oops, something went wrong.