Skip to content

Commit 5942d04

Browse files
authored
feat: ✨ Tracking of search action ENT-4126 (#95)
* feat: ✨ Tracking of search action When search button is clicked or search initiated, send tracking event with query string data ENT-4126 * feat: ✨ Use trackingName from context if found Only send tracking during search if context contains the trackingEvent string input value ENT-4126 * test: use user-events instead of fireEvent use user-events instead of fireEvent ENT-4126 * feat: refine query_submitted event name better naming of the query_submitted action as a event name under catalog_search ENT-4126
1 parent fd7344a commit 5942d04

File tree

8 files changed

+67
-9
lines changed

8 files changed

+67
-9
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.vscode
12
.idea
23
coverage
34
dist

.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
12.10.0

package-lock.json

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@testing-library/jest-dom": "5.11.6",
4646
"@testing-library/react": "11.2.6",
4747
"@testing-library/react-hooks": "3.4.2",
48+
"@testing-library/user-event": "^13.1.5",
4849
"algoliasearch": "^4.8.5",
4950
"axios-mock-adapter": "1.19.0",
5051
"classnames": "^2.2.5",

src/course-search/SearchBox.jsx

+13-1
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,26 @@ import classNames from 'classnames';
44
import { SearchField } from '@edx/paragon';
55
import { connectSearchBox } from 'react-instantsearch-dom';
66

7+
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
8+
79
import { deleteRefinementAction, setRefinementAction } from './data/actions';
810
import { STYLE_VARIANTS } from '../constants';
911
import { SearchContext } from './SearchContext';
1012
import { QUERY_PARAM_FOR_PAGE, QUERY_PARAM_FOR_SEARCH_QUERY } from './data/constants';
1113

1214
export const searchText = 'Search courses';
15+
// this prefix will be combined with one of the SearchBox props to create a full tracking event name
16+
// only if event name prop is provided by user. In the absence of the tracking name prop,
17+
// no tracking event will be sent.
18+
export const SEARCH_EVENT_NAME_PREFIX = 'edx.enterprise';
19+
export const QUERY_SUBMITTED_EVENT = 'catalog_search.query_submitted';
1320

1421
export const SearchBoxBase = ({
1522
className,
1623
defaultRefinement,
1724
variant,
1825
}) => {
19-
const { dispatch } = useContext(SearchContext);
26+
const { dispatch, trackingName } = useContext(SearchContext);
2027

2128
/**
2229
* Handles when a search is submitted by adding the user's search
@@ -26,6 +33,11 @@ export const SearchBoxBase = ({
2633
const handleSubmit = (searchQuery) => {
2734
dispatch(setRefinementAction(QUERY_PARAM_FOR_SEARCH_QUERY, searchQuery));
2835
dispatch(deleteRefinementAction(QUERY_PARAM_FOR_PAGE));
36+
if (trackingName) {
37+
sendTrackEvent(`${SEARCH_EVENT_NAME_PREFIX}.${trackingName}.${QUERY_SUBMITTED_EVENT}`, {
38+
query: searchQuery,
39+
});
40+
}
2941
};
3042

3143
/**

src/course-search/SearchContext.jsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const getRefinementsToSet = (queryParams, activeFacetAttributes) => {
2727
return refinementsToSet;
2828
};
2929

30-
const SearchData = ({ children, searchFacetFilters }) => {
30+
const SearchData = ({ children, searchFacetFilters, trackingName }) => {
3131
const [refinementsFromQueryParams, dispatch] = useReducer(
3232
refinementsReducer,
3333
{},
@@ -61,8 +61,9 @@ const SearchData = ({ children, searchFacetFilters }) => {
6161
refinementsFromQueryParams,
6262
dispatch,
6363
searchFacetFilters,
64+
trackingName,
6465
}),
65-
[refinementsFromQueryParams, dispatch, searchFacetFilters],
66+
[refinementsFromQueryParams, dispatch, searchFacetFilters, trackingName],
6667
);
6768

6869
return (
@@ -72,6 +73,7 @@ const SearchData = ({ children, searchFacetFilters }) => {
7273

7374
SearchData.defaultProps = {
7475
searchFacetFilters: SEARCH_FACET_FILTERS,
76+
trackingName: null,
7577
};
7678

7779
SearchData.propTypes = {
@@ -81,6 +83,7 @@ SearchData.propTypes = {
8183
title: PropTypes.string.isRequired,
8284
isSortedAlphabetical: PropTypes.bool,
8385
})),
86+
trackingName: PropTypes.string,
8487
};
8588

8689
export default SearchData;

src/course-search/tests/SearchBox.test.jsx

+31-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import React from 'react';
2-
import { screen, fireEvent } from '@testing-library/react';
2+
import { screen } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
34
import '@testing-library/jest-dom/extend-expect';
45

5-
import { SearchBoxBase, searchText } from '../SearchBox';
6-
import { renderWithSearchContext } from '../../utils/tests';
6+
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
7+
8+
import {
9+
SearchBoxBase,
10+
searchText,
11+
SEARCH_EVENT_NAME_PREFIX,
12+
QUERY_SUBMITTED_EVENT,
13+
} from '../SearchBox';
14+
import { renderWithSearchContext, renderWithSearchContextAndTracking } from '../../utils/tests';
15+
16+
jest.mock('@edx/frontend-platform/analytics');
717

818
const TEST_QUERY = 'test query';
919

@@ -30,18 +40,33 @@ describe('<SearchBox />', () => {
3040
const { history } = renderWithSearchContext(<SearchBoxBase />);
3141

3242
// fill in search input and submit the search
33-
fireEvent.change(screen.getByRole('searchbox'), { target: { value: TEST_QUERY } });
34-
fireEvent.click(screen.getByText('submit search'));
43+
userEvent.type(screen.getByRole('searchbox'), TEST_QUERY);
44+
userEvent.click(screen.getByText('submit search'));
3545

3646
// assert url is updated with the query
3747
expect(history).toHaveLength(2);
3848
expect(history.location.search).toEqual('?q=test%20query');
49+
// check tracking is not invoked due to absent trackingName in context
50+
expect(sendTrackEvent).not.toHaveBeenCalled();
3951

4052
// clear the input
41-
fireEvent.click(screen.getByText('clear search'));
53+
userEvent.click(screen.getByText('clear search'));
4254

4355
// assert query no longer exists in url
4456
expect(history).toHaveLength(3);
4557
expect(history.location.search).toEqual('');
4658
});
59+
test('tracking event when search initiated with trackingName present in context', () => {
60+
renderWithSearchContextAndTracking(<SearchBoxBase />, 'aProduct');
61+
62+
// fill in search input and submit the search
63+
userEvent.type(screen.getByRole('searchbox'), TEST_QUERY);
64+
userEvent.click(screen.getByText('submit search'));
65+
66+
// check tracking is invoked due to trackingName in context
67+
expect(sendTrackEvent).toHaveBeenCalledWith(
68+
`${SEARCH_EVENT_NAME_PREFIX}.aProduct.${QUERY_SUBMITTED_EVENT}`,
69+
{ query: TEST_QUERY },
70+
);
71+
});
4772
});

src/utils/tests.jsx

+6
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,9 @@ export const renderWithSearchContext = (children) => renderWithRouter(
2929
{children}
3030
</SearchData>,
3131
);
32+
33+
export const renderWithSearchContextAndTracking = (children, trackingName) => renderWithRouter(
34+
<SearchData trackingName={trackingName}>
35+
{children}
36+
</SearchData>,
37+
);

0 commit comments

Comments
 (0)