From 052e4661dbb4598f3dc6db3023a9845010009b80 Mon Sep 17 00:00:00 2001 From: Urvashi Sharma Date: Sat, 8 Feb 2025 00:14:28 +0530 Subject: [PATCH] feat(analytics): Fix syntax for analytics with react-ga4 --- src/components/App/App.test.tsx | 2 +- src/components/App/App.tsx | 12 +++++-- .../CaptureRoutes/CaptureRoutes.test.tsx | 2 +- src/components/LogIn/LogIn.tsx | 13 +------- src/hooks/useAnalytics.test.ts | 15 +++++---- src/hooks/useAnalytics.tsx | 32 +++++++++++++------ 6 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/components/App/App.test.tsx b/src/components/App/App.test.tsx index 74454c0af..a6bf50b3e 100644 --- a/src/components/App/App.test.tsx +++ b/src/components/App/App.test.tsx @@ -83,7 +83,7 @@ describe("App", () => { ); expect(initializeSpy).toHaveBeenCalled(); expect(pageviewSpy).toHaveBeenCalledWith({ - hitType: "page_view", + hitType: "pageview", page: "/models", }); }); diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 781f0b0e1..242d138b2 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -12,9 +12,17 @@ function App() { const isProduction = import.meta.env.PROD; const analyticsEnabled = useAppSelector(getAnalyticsEnabled); if (isProduction && analyticsEnabled) { - ReactGA.initialize("UA-1018242-68"); // TODO: should use the GA4 Measurement ID (which starts with "G-") + ReactGA.initialize("G-JHXHM8VXJ1", { + gtagOptions: { + custom_map: { + dimension1: "dashboardVersion", + dimension2: "controllerVersion", + dimension3: "isJuju", + }, + }, + }); ReactGA.send({ - hitType: "page_view", + hitType: "pageview", page: window.location.href.replace(window.location.origin, ""), }); } diff --git a/src/components/CaptureRoutes/CaptureRoutes.test.tsx b/src/components/CaptureRoutes/CaptureRoutes.test.tsx index 7804e14f0..db3ce2b94 100644 --- a/src/components/CaptureRoutes/CaptureRoutes.test.tsx +++ b/src/components/CaptureRoutes/CaptureRoutes.test.tsx @@ -44,7 +44,7 @@ describe("CaptureRoutes", () => { , ); expect(pageviewSpy).toHaveBeenCalledWith({ - hitType: "page_view", + hitType: "pageview", page: "/new/path", }); }); diff --git a/src/components/LogIn/LogIn.tsx b/src/components/LogIn/LogIn.tsx index 95b0d8571..f3de25a47 100644 --- a/src/components/LogIn/LogIn.tsx +++ b/src/components/LogIn/LogIn.tsx @@ -16,8 +16,6 @@ import { getWSControllerURL, isLoggedIn, getIsJuju, - getAppVersion, - getControllerConnection, } from "store/general/selectors"; import { AuthMethod } from "store/general/types"; import { useAppSelector } from "store/store"; @@ -33,11 +31,7 @@ export default function LogIn() { const sendAnalytics = useAnalytics(); const config = useSelector(getConfig); const isJuju = useSelector(getIsJuju); - const appVersion = useSelector(getAppVersion); const wsControllerURL = useAppSelector(getWSControllerURL); - const controllerVersion = useAppSelector((state) => - getControllerConnection(state, wsControllerURL), - )?.serverVersion; const userIsLoggedIn = useAppSelector((state) => isLoggedIn(state, wsControllerURL), ); @@ -84,14 +78,9 @@ export default function LogIn() { sendAnalytics({ category: "Authentication", action: "User Login", - eventParams: { - dashboardVersion: appVersion ?? "", - controllerVersion: controllerVersion ?? "", - isJuju: (!!isJuju).toString(), - }, }); } - }, [userIsLoggedIn, appVersion, controllerVersion, isJuju, sendAnalytics]); + }, [userIsLoggedIn, sendAnalytics]); let form: ReactNode = null; switch (config?.authMethod) { diff --git a/src/hooks/useAnalytics.test.ts b/src/hooks/useAnalytics.test.ts index 1eafe842b..1cf20746b 100644 --- a/src/hooks/useAnalytics.test.ts +++ b/src/hooks/useAnalytics.test.ts @@ -1,9 +1,9 @@ -import { renderHook } from "@testing-library/react"; import ReactGA from "react-ga4"; import type { MockInstance } from "vitest"; import { vi } from "vitest"; import * as store from "store/store"; +import { renderWrappedHook } from "testing/utils"; import useAnalytics from "./useAnalytics"; @@ -38,7 +38,7 @@ describe("useAnalytics", () => { vi.fn().mockReturnValue(true), ); vi.stubEnv("PROD", false); - const { result } = renderHook(() => useAnalytics()); + const { result } = renderWrappedHook(() => useAnalytics()); result.current({ path: "/some/path" }); expect(eventSpy).not.toHaveBeenCalled(); expect(pageviewSpy).not.toHaveBeenCalled(); @@ -48,7 +48,7 @@ describe("useAnalytics", () => { vi.spyOn(store, "useAppSelector").mockImplementation( vi.fn().mockReturnValue(false), ); - const { result } = renderHook(() => useAnalytics()); + const { result } = renderWrappedHook(() => useAnalytics()); result.current({ path: "/some/path" }); expect(eventSpy).not.toHaveBeenCalled(); expect(pageviewSpy).not.toHaveBeenCalled(); @@ -58,10 +58,10 @@ describe("useAnalytics", () => { vi.spyOn(store, "useAppSelector").mockImplementation( vi.fn().mockReturnValue(true), ); - const { result } = renderHook(() => useAnalytics()); + const { result } = renderWrappedHook(() => useAnalytics()); result.current({ path: "/some/path" }); expect(pageviewSpy).toHaveBeenCalledWith({ - hitType: "page_view", + hitType: "pageview", page: "/some/path", }); }); @@ -70,11 +70,14 @@ describe("useAnalytics", () => { vi.spyOn(store, "useAppSelector").mockImplementation( vi.fn().mockReturnValue(true), ); - const { result } = renderHook(() => useAnalytics()); + const { result } = renderWrappedHook(() => useAnalytics()); result.current({ category: "sidebar", action: "toggle" }); expect(eventSpy).toHaveBeenCalledWith({ category: "sidebar", action: "toggle", + controllerVersion: "", + dashboardVersion: "", + isJuju: "false", }); }); }); diff --git a/src/hooks/useAnalytics.tsx b/src/hooks/useAnalytics.tsx index dd687cb1f..4e3d44db1 100644 --- a/src/hooks/useAnalytics.tsx +++ b/src/hooks/useAnalytics.tsx @@ -1,34 +1,46 @@ import ReactGA from "react-ga4"; +import { useSelector } from "react-redux"; -import { getAnalyticsEnabled } from "store/general/selectors"; +import { + getAnalyticsEnabled, + getAppVersion, + getControllerConnection, + getIsJuju, + getWSControllerURL, +} from "store/general/selectors"; import { useAppSelector } from "store/store"; type AnalyticMessage = { path?: string; category?: string; action?: string; - eventParams?: { [key: string]: string }; }; export default function useAnalytics() { const analyticsEnabled = useAppSelector(getAnalyticsEnabled); - return ({ - path, - category = "", - action = "", - eventParams = {}, - }: AnalyticMessage) => { + const isJuju = useSelector(getIsJuju); + const appVersion = useSelector(getAppVersion); + const wsControllerURL = useAppSelector(getWSControllerURL); + const controllerVersion = useAppSelector((state) => + getControllerConnection(state, wsControllerURL), + )?.serverVersion; + + return ({ path, category = "", action = "" }: AnalyticMessage) => { const isProduction = import.meta.env.PROD; if (!isProduction || !analyticsEnabled) { return; } if (path) { - ReactGA.send({ hitType: "page_view", page: path }); + ReactGA.send({ hitType: "pageview", page: path }); } else { ReactGA.event({ category, action, - ...eventParams, + ...{ + dashboardVersion: appVersion ?? "", + controllerVersion: controllerVersion ?? "", + isJuju: (!!isJuju).toString(), + }, }); } };