Skip to content

Commit 4723d4a

Browse files
authored
add tracking to search flow (#5626)
* add tracking to search flow * fix lint errors * track page views
1 parent 4b690c0 commit 4723d4a

9 files changed

Lines changed: 102 additions & 8 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"@babel/preset-env": "7.29.0",
2626
"@babel/preset-react": "7.28.5",
2727
"@babel/preset-typescript": "7.28.5",
28-
"@canonical/analytics-events": "1.0.4",
28+
"@canonical/analytics-events": "1.0.5",
2929
"@canonical/cookie-policy": "3.8.4",
3030
"@canonical/global-nav": "3.8.0",
3131
"@canonical/react-components": "3.12.1",

static/js/public/homepage.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,36 @@ import { initFSFLanguageSelect } from "./fsf-language-select";
22
import nps from "./nps";
33
import initExpandableArea from "./expandable-area";
44
import declareGlobal from "../libs/declare";
5+
import { trackEvent, trackPageView } from "@canonical/analytics-events";
6+
7+
if (window.ANALYTICS_ENDPOINT) {
8+
trackPageView("snap_home_page");
9+
}
10+
11+
function initHomeSearchTracking(): void {
12+
const form = document.querySelector(
13+
"[data-js='home-search-form']",
14+
) as HTMLFormElement | null;
15+
16+
if (form) {
17+
form.addEventListener("submit", () => {
18+
const input = form.querySelector("input[name='q']") as HTMLInputElement;
19+
if (input?.value) {
20+
const searchId = crypto.randomUUID();
21+
sessionStorage.setItem("search_id", searchId);
22+
23+
trackEvent("snap_home_search_submitted", {
24+
search_id: searchId,
25+
query: input.value,
26+
});
27+
}
28+
});
29+
}
30+
}
531

632
declareGlobal("snapcraft.public.homepage", {
733
initExpandableArea,
834
initFSFLanguageSelect,
35+
initHomeSearchTracking,
936
nps,
1037
});

static/js/store/components/PackageList/PackageList.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Row,
88
Strip,
99
} from "@canonical/react-components";
10+
import { trackEvent } from "@canonical/analytics-events";
1011

1112
import { PackageFilter } from "../PackageFilter";
1213

@@ -127,11 +128,26 @@ function PackageList({
127128
))}
128129
{!isFetching &&
129130
data &&
130-
data.packages.map((packageData: Package) => (
131+
data.packages.map((packageData: Package, index: number) => (
131132
<Col
132133
size={3}
133134
style={{ marginBottom: "1.5rem" }}
134135
key={packageData.id}
136+
onClick={() => {
137+
const query = searchParams.get("q");
138+
if (query) {
139+
const searchId =
140+
sessionStorage.getItem("search_id") || "";
141+
trackEvent("snap_store_search_result_clicked", {
142+
search_id: searchId,
143+
query,
144+
position:
145+
(parseInt(currentPage) - 1) * ITEMS_PER_PAGE +
146+
index +
147+
1,
148+
});
149+
}
150+
}}
135151
>
136152
<DefaultCard data={packageData} />
137153
</Col>

static/js/store/components/SearchInput/SearchInput.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Button } from "@canonical/react-components";
2+
import { trackEvent } from "@canonical/analytics-events";
23
import { useSearchParams, useNavigate, useLocation } from "react-router-dom";
34

45
import type { RefObject } from "react";
@@ -27,6 +28,14 @@ export const SearchInput = ({
2728
searchParams.delete("page");
2829
searchParams.set("q", searchRef.current.value);
2930
setSearchParams(searchParams);
31+
32+
const searchId = crypto.randomUUID();
33+
sessionStorage.setItem("search_id", searchId);
34+
35+
trackEvent("snap_store_search_submitted", {
36+
search_id: searchId,
37+
query: searchRef.current.value,
38+
});
3039
}
3140
if (searchSummaryRef && searchSummaryRef.current) {
3241
searchSummaryRef.current.scrollIntoView({

static/js/store/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { createRoot } from "react-dom/client";
22
import { createBrowserRouter, RouterProvider } from "react-router-dom";
33
import { QueryClient, QueryClientProvider } from "react-query";
4+
import { trackPageView } from "@canonical/analytics-events";
45

56
import Root from "./layouts/Root";
67
import Store from "./pages/Store";
78

9+
if (window.ANALYTICS_ENDPOINT) {
10+
trackPageView("snap_store_page");
11+
}
12+
813
const router = createBrowserRouter([
914
{
1015
path: "/",

static/js/store/pages/Store/Store.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { useRef } from "react";
1+
import { useEffect, useRef } from "react";
22
import { useLocation, useSearchParams } from "react-router-dom";
3+
import { trackEvent } from "@canonical/analytics-events";
34

45
import Banner from "../../components/Banner";
56
import PackageList from "../../components/PackageList/PackageList";
@@ -30,6 +31,38 @@ function Store(): React.JSX.Element {
3031
const packagesCount = data?.packages ? data?.packages.length : 0;
3132
const isResultExist = status === "success" && packagesCount > 0;
3233

34+
useEffect(() => {
35+
if (searchTerm && status === "success" && !isFetching) {
36+
let searchId = sessionStorage.getItem("search_id");
37+
if (!searchId) {
38+
searchId = crypto.randomUUID();
39+
sessionStorage.setItem("search_id", searchId);
40+
}
41+
42+
if (packagesCount > 0) {
43+
trackEvent("snap_store_search_results_loaded", {
44+
search_id: searchId,
45+
query: searchTerm,
46+
total_items: data?.total_items ?? 0,
47+
page: currentPage,
48+
});
49+
} else {
50+
trackEvent("snap_store_search_no_results", {
51+
search_id: searchId,
52+
query: searchTerm,
53+
});
54+
}
55+
}
56+
}, [
57+
searchTerm,
58+
status,
59+
currentPage,
60+
searchParams,
61+
packagesCount,
62+
data,
63+
isFetching,
64+
]);
65+
3366
const searchRef = useRef<HTMLInputElement | null>(null);
3467
const searchSummaryRef = useRef<HTMLDivElement>(null);
3568

templates/home/_featured-snaps.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="p-block">
66
<h1>The app store for Linux</h1>
77
</div>
8-
<form class="p-search-box" action="/store">
8+
<form class="p-search-box" action="/store" data-js="home-search-form">
99
<label class="u-off-screen" for="search">Search</label>
1010
<input type="search" id="search" class="p-search-box__input" name="q" placeholder="Search thousands of snaps" required="" autocomplete="on">
1111
<button type="reset" class="p-search-box__reset"><i class="p-icon--close">Close</i></button>

templates/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ <h2>Measure user growth</h2>
183183
Sentry.captureException(e);
184184
}
185185

186+
try {
187+
snapcraft.public.homepage.initHomeSearchTracking();
188+
} catch(e) {}
189+
186190
try {
187191
snapcraft.public.featuredSnaps.init({{ featured_categories|safe }});
188192
} catch(e) {

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -924,10 +924,10 @@
924924
hashery "^1.5.0"
925925
keyv "^5.6.0"
926926

927-
"@canonical/analytics-events@1.0.4":
928-
version "1.0.4"
929-
resolved "https://registry.yarnpkg.com/@canonical/analytics-events/-/analytics-events-1.0.4.tgz#27440161a7970f2f2a990dd0597e70685706c3b4"
930-
integrity sha512-+ChQJHpwpBQxuSdVwNv1YSGSzX6z6QeCmfxKqo8K02Hk2nJGuIdwEfi4yIa/KxVOhxaFLO7fy7ACSsBkTGeGRA==
927+
"@canonical/analytics-events@1.0.5":
928+
version "1.0.5"
929+
resolved "https://registry.yarnpkg.com/@canonical/analytics-events/-/analytics-events-1.0.5.tgz#88a18381a657196ea5f21b5932a41c5afce69999"
930+
integrity sha512-GMFbPJ83FcN+ItLcc1czYp0HUA96uLfu+35B0NWEVCL/Wy/CRc0o8Ab1DgAyjBw16reIP+SkAg9k/NBwNj1Tjg==
931931

932932
"@canonical/cookie-policy@3.8.4":
933933
version "3.8.4"

0 commit comments

Comments
 (0)