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

feat(insights): introduce insights middleware (1/4) #4446

Merged
merged 35 commits into from
Sep 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3963956
feat: update userToken automatically
eunjae-lee Jul 15, 2020
4331d24
chore: fix bug where it gets token before rendering the custom widget
eunjae-lee Jul 15, 2020
77996c9
chore: add access modifier
eunjae-lee Jul 15, 2020
467a648
chore: use custom widget and helper
eunjae-lee Jul 16, 2020
ec3f7bb
Update src/lib/InstantSearch.ts
Jul 16, 2020
3ab96a2
chore: update token to helper directly
eunjae-lee Jul 16, 2020
020381e
chore: move setupUserTokenUpdater to start()
eunjae-lee Jul 16, 2020
a0cc96f
chore: move to middleware
eunjae-lee Jul 23, 2020
c642234
Update src/middleware/insights.ts
Jul 23, 2020
03e52c7
export middleware
eunjae-lee Jul 23, 2020
0ae09e2
fix lint error
eunjae-lee Jul 23, 2020
2417c50
export for umd build
eunjae-lee Jul 23, 2020
090fe28
Update src/middleware/insights.ts
Jul 23, 2020
c262700
Merge branch 'master' into feat/automatic-user-token
Jul 23, 2020
2a49909
update token automatically
eunjae-lee Jul 23, 2020
20254cf
chore: update error message
eunjae-lee Jul 24, 2020
10ce292
inline functions
eunjae-lee Jul 24, 2020
5a1fc34
initialize insightsClient earlier
eunjae-lee Jul 24, 2020
963d305
add warning message if userToken is set before creating the middleware
eunjae-lee Jul 27, 2020
daca533
accept `false` for `insightsClient`
eunjae-lee Jul 27, 2020
000d708
clean up types
eunjae-lee Jul 29, 2020
602037a
fix wrong import
eunjae-lee Jul 29, 2020
8fd4698
Update src/middleware/insights.ts
Aug 24, 2020
b0b7add
accept null as insightsClient
eunjae-lee Aug 24, 2020
e2a2743
Merge branch 'master' into feat/automatic-user-token
Aug 24, 2020
21faadb
Update src/types/insights.ts
Aug 24, 2020
7f49f25
add test for getAppIdAndApiKey
eunjae-lee Aug 24, 2020
94e394b
Update src/types/insights.ts
Aug 24, 2020
ad0dafb
bring back exports
eunjae-lee Aug 24, 2020
f34170d
rename middleware to middlewares
eunjae-lee Aug 24, 2020
ef95a87
Merge branch 'master' into feat/automatic-user-token
eunjae-lee Aug 27, 2020
183b1ca
chore: rename files for better alignment
eunjae-lee Aug 31, 2020
e7c7e58
chore: export all types related to insights middleware
eunjae-lee Aug 31, 2020
60af9e0
feat(insights): send events from hits and refinementList (2/4) (#4456)
Sep 2, 2020
5067ec7
Merge branch 'master' into feat/automatic-user-token
Sep 2, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .storybook/playgrounds/default.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import instantsearch from '../../src/index';
import { panel, numericMenu, hits } from '../../src/widgets';
import { createInsightsMiddleware } from '../../src/middlewares';

export const hitsItemTemplate = `
<div
Expand Down Expand Up @@ -126,6 +127,14 @@ function instantSearchPlayground({
container: pagination,
}),
]);

const insights = createInsightsMiddleware({
insightsClient: null,
onEvent: props => {
console.log('insights onEvent', props);
},
});
search.EXPERIMENTAL_use(insights);
}

export default instantSearchPlayground;
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"@wdio/spec-reporter": "5.16.5",
"@wdio/static-server-service": "5.16.5",
"algoliasearch": "4.3.1",
"algoliasearch-v3": "npm:algoliasearch@3",
"babel-eslint": "10.0.3",
"babel-jest": "24.9.0",
"babel-loader": "8.0.6",
Expand Down Expand Up @@ -141,11 +142,11 @@
"bundlesize": [
{
"path": "./dist/instantsearch.production.min.js",
"maxSize": "64 kB"
"maxSize": "64.50 kB"
},
{
"path": "./dist/instantsearch.development.js",
"maxSize": "160 kB"
"maxSize": "150.40 kB"
}
]
}
5 changes: 4 additions & 1 deletion src/components/Hits/Hits.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import cx from 'classnames';
import Template from '../Template/Template';

const Hits = ({ results, hits, cssClasses, templateProps }) => {
const Hits = ({ results, hits, bindEvent, cssClasses, templateProps }) => {
if (results.hits.length === 0) {
return (
<Template
Expand Down Expand Up @@ -33,6 +33,7 @@ const Hits = ({ results, hits, cssClasses, templateProps }) => {
...hit,
__hitIndex: position,
}}
bindEvent={bindEvent}
/>
))}
</ol>
Expand All @@ -49,6 +50,8 @@ Hits.propTypes = {
}).isRequired,
hits: PropTypes.array.isRequired,
results: PropTypes.object.isRequired,
sendEvent: PropTypes.func.isRequired,
bindEvent: PropTypes.func.isRequired,
templateProps: PropTypes.object.isRequired,
};

Expand Down
5 changes: 5 additions & 0 deletions src/components/InfiniteHits/InfiniteHits.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Template from '../Template/Template';
import { SearchResults } from 'algoliasearch-helper';
import { Hits } from '../../types';
import { InfiniteHitsTemplates } from '../../widgets/infinite-hits/infinite-hits';
import { SendEventForHits, BindEventForHits } from '../../lib/utils';

type InfiniteHitsCSSClasses = {
root: string;
Expand All @@ -31,11 +32,14 @@ type InfiniteHitsProps = {
};
isFirstPage: boolean;
isLastPage: boolean;
sendEvent: SendEventForHits;
bindEvent: BindEventForHits;
};

const InfiniteHits = ({
results,
hits,
bindEvent,
hasShowPrevious,
showPrevious,
showMore,
Expand Down Expand Up @@ -86,6 +90,7 @@ const InfiniteHits = ({
...hit,
__hitIndex: position,
}}
bindEvent={bindEvent}
/>
))}
</ol>
Expand Down
15 changes: 15 additions & 0 deletions src/components/InfiniteHits/__tests__/InfiniteHits-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ describe('InfiniteHits', () => {
disabledLoadMore: 'disabledLoadMore',
};

const sendEvent = () => {};
const bindEvent = () => '';

describe('markup', () => {
it('should render <InfiniteHits /> on first page', () => {
const hits: Hits = [
Expand Down Expand Up @@ -50,6 +53,8 @@ describe('InfiniteHits', () => {
},
},
cssClasses,
sendEvent,
bindEvent,
};

const { container } = render(<InfiniteHits {...props} />);
Expand Down Expand Up @@ -88,6 +93,8 @@ describe('InfiniteHits', () => {
},
},
cssClasses,
sendEvent,
bindEvent,
};

const { container } = render(<InfiniteHits {...props} />);
Expand Down Expand Up @@ -115,6 +122,8 @@ describe('InfiniteHits', () => {
},
},
cssClasses,
sendEvent,
bindEvent,
};

const { container } = render(<InfiniteHits {...props} />);
Expand Down Expand Up @@ -142,6 +151,8 @@ describe('InfiniteHits', () => {
},
},
cssClasses,
sendEvent,
bindEvent,
};

const { container } = render(<InfiniteHits {...props} />);
Expand Down Expand Up @@ -180,6 +191,8 @@ describe('InfiniteHits', () => {
},
},
cssClasses,
sendEvent,
bindEvent,
};

const { container } = render(<InfiniteHits {...props} />);
Expand Down Expand Up @@ -223,6 +236,8 @@ describe('InfiniteHits', () => {
},
},
cssClasses,
sendEvent,
bindEvent,
};

const { container } = render(<InfiniteHits {...props} />);
Expand Down
2 changes: 2 additions & 0 deletions src/components/Template/Template.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Template extends Component {
compileOptions,
helpers: this.props.templatesConfig.helpers,
data: this.props.data,
bindEvent: this.props.bindEvent,
});

if (content === null) {
Expand Down Expand Up @@ -69,6 +70,7 @@ Template.propTypes = {
}),
}),
useCustomCompileOptions: PropTypes.objectOf(PropTypes.bool),
bindEvent: PropTypes.func,
};

Template.defaultProps = {
Expand Down
150 changes: 150 additions & 0 deletions src/connectors/autocomplete/__tests__/connectAutocomplete-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,4 +566,154 @@ search.addWidgets([
);
});
});

describe('insights', () => {
const createRenderedWidget = () => {
const searchClient = createSearchClient();
const render = jest.fn();
const makeWidget = connectAutocomplete(render);
const widget = makeWidget({ escapeHTML: false });

const helper = algoliasearchHelper(searchClient, '', {});
helper.search = jest.fn();

const initOptions = createInitOptions({ helper });
const instantSearchInstance = initOptions.instantSearchInstance;
widget.init!(initOptions);

const firstIndexHits = [
{
name: 'Hit 1-1',
objectID: '1-1',
__queryID: 'test-query-id',
__position: 0,
},
];
const secondIndexHits = [
{
name: 'Hit 2-1',
objectID: '2-1',
__queryID: 'test-query-id',
__position: 0,
},
{
name: 'Hit 2-2',
objectID: '2-2',
__queryID: 'test-query-id',
__position: 1,
},
];

const scopedResults = [
{
indexId: 'indexId0',
results: new SearchResults(helper.state, [
createSingleSearchResponse({
index: 'indexName0',
hits: firstIndexHits,
}),
]),
helper,
},
{
indexId: 'indexId1',
results: new SearchResults(helper.state, [
createSingleSearchResponse({
index: 'indexName1',
hits: secondIndexHits,
}),
]),
helper,
},
];

widget.render!(
createRenderOptions({ instantSearchInstance, helper, scopedResults })
);

const sendEventToInsights = instantSearchInstance.sendEventToInsights as jest.Mock;

return {
instantSearchInstance,
sendEventToInsights,
render,
firstIndexHits,
secondIndexHits,
};
};

it('sends view event when hits are rendered', () => {
const { sendEventToInsights } = createRenderedWidget();
expect(sendEventToInsights).toHaveBeenCalledTimes(2);
expect(sendEventToInsights.mock.calls[0][0]).toEqual({
eventType: 'view',
insightsMethod: 'viewedObjectIDs',
payload: {
eventName: 'Hits Viewed',
index: 'indexName0',
objectIDs: ['1-1'],
},
widgetType: 'ais.autocomplete',
});
expect(sendEventToInsights.mock.calls[1][0]).toEqual({
eventType: 'view',
insightsMethod: 'viewedObjectIDs',
payload: {
eventName: 'Hits Viewed',
index: 'indexName1',
objectIDs: ['2-1', '2-2'],
},
widgetType: 'ais.autocomplete',
});
});

it('sends click event', () => {
const {
sendEventToInsights,
render,
secondIndexHits,
} = createRenderedWidget();
expect(sendEventToInsights).toHaveBeenCalledTimes(2); // two view events for each index by render

const { indices } = render.mock.calls[render.mock.calls.length - 1][0];
indices[1].sendEvent('click', secondIndexHits[0], 'Product Added');
expect(sendEventToInsights).toHaveBeenCalledTimes(3);
expect(sendEventToInsights.mock.calls[2][0]).toEqual({
eventType: 'click',
insightsMethod: 'clickedObjectIDsAfterSearch',
payload: {
eventName: 'Product Added',
index: 'indexName1',
objectIDs: ['2-1'],
positions: [0],
queryID: 'test-query-id',
},
widgetType: 'ais.autocomplete',
});
});

it('sends conversion event', () => {
const {
sendEventToInsights,
render,
firstIndexHits,
} = createRenderedWidget();
expect(sendEventToInsights).toHaveBeenCalledTimes(2); // two view events for each index by render

const { indices } = render.mock.calls[render.mock.calls.length - 1][0];
indices[0].sendEvent('conversion', firstIndexHits[0], 'Product Ordered');
expect(sendEventToInsights).toHaveBeenCalledTimes(3);
expect(sendEventToInsights.mock.calls[2][0]).toEqual({
eventType: 'conversion',
insightsMethod: 'convertedObjectIDsAfterSearch',
payload: {
eventName: 'Product Ordered',
index: 'indexName0',
objectIDs: ['1-1'],
queryID: 'test-query-id',
},
widgetType: 'ais.autocomplete',
});
});
});
});
16 changes: 16 additions & 0 deletions src/connectors/autocomplete/connectAutocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import escapeHits, { TAG_PLACEHOLDER } from '../../lib/escape-highlight';
import {
checkRendering,
createDocumentationMessageGenerator,
createSendEventForHits,
SendEventForHits,
noop,
warning,
} from '../../lib/utils';
Expand Down Expand Up @@ -46,6 +48,11 @@ export type AutocompleteRendererOptions = {
* The full results object from the Algolia API.
*/
results: SearchResults;

/**
* Send event to insights middleware
*/
sendEvent: SendEventForHits;
}>;

/**
Expand Down Expand Up @@ -127,11 +134,20 @@ search.addWidgets([
? escapeHits(scopedResult.results.hits)
: scopedResult.results.hits;

const sendEvent = createSendEventForHits({
instantSearchInstance,
index: scopedResult.results.index,
widgetType: this.$$type!,
});

sendEvent('view', scopedResult.results.hits);

return {
indexId: scopedResult.indexId,
indexName: scopedResult.results.index,
hits: scopedResult.results.hits,
results: scopedResult.results,
sendEvent,
};
});

Expand Down
Loading