From 655dc57099094798ad8d064e90186d8e514cd5bc Mon Sep 17 00:00:00 2001 From: Nikita <93587872+ncarazon@users.noreply.github.com> Date: Thu, 24 Oct 2024 20:18:29 +0300 Subject: [PATCH] fix(analytics): adjust cookies modal and posthog setup, implement GA custom events for feed search and sidebar --- front_end/package-lock.json | 21 ++++++-- front_end/package.json | 2 +- .../tournament/components/tournament_feed.tsx | 2 + .../components/cookies_banner/index.tsx | 47 +++++++++-------- .../questions/components/question_topics.tsx | 51 +++++++++++++++++-- front_end/src/app/layout.tsx | 9 ++++ front_end/src/app/providers.tsx | 30 ++++++----- .../src/components/posthog_page_view.tsx | 24 +++++++++ .../components/posts_feed/paginated_feed.tsx | 9 +++- 9 files changed, 149 insertions(+), 46 deletions(-) create mode 100644 front_end/src/components/posthog_page_view.tsx diff --git a/front_end/package-lock.json b/front_end/package-lock.json index cc4fa2fe6..f30f5c681 100644 --- a/front_end/package-lock.json +++ b/front_end/package-lock.json @@ -49,7 +49,7 @@ "next-themes": "^0.3.0", "nextjs-toploader": "^1.6.12", "percent-round": "^2.3.1", - "posthog-js": "^1.160.0", + "posthog-js": "^1.175.0", "puppeteer": "^22.12.1", "rc-slider": "^10.6.2", "react": "^18", @@ -6046,6 +6046,16 @@ "cookie": "^0.6.0" } }, + "node_modules/core-js": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", + "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -11998,13 +12008,14 @@ } }, "node_modules/posthog-js": { - "version": "1.160.0", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.160.0.tgz", - "integrity": "sha512-K/RRgmPYIpP69nnveCJfkclb8VU+R+jsgqlrKaLGsM5CtQM9g01WOzAiT3u36WLswi58JiFMXgJtECKQuoqTgQ==", + "version": "1.175.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.175.0.tgz", + "integrity": "sha512-ZPdM7/azh90gqqlYSJ3RIBJVViER8JDR7LtHWatVt2UCsIRvV+FSRQd85n770TFLDQSKAekFVcwCC9t+syTJJA==", "dependencies": { + "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", - "web-vitals": "^4.0.1" + "web-vitals": "^4.2.0" } }, "node_modules/preact": { diff --git a/front_end/package.json b/front_end/package.json index 042418163..be48c8d1e 100644 --- a/front_end/package.json +++ b/front_end/package.json @@ -53,7 +53,7 @@ "next-themes": "^0.3.0", "nextjs-toploader": "^1.6.12", "percent-round": "^2.3.1", - "posthog-js": "^1.160.0", + "posthog-js": "^1.175.0", "puppeteer": "^22.12.1", "rc-slider": "^10.6.2", "react": "^18", diff --git a/front_end/src/app/(main)/(tournaments)/tournament/components/tournament_feed.tsx b/front_end/src/app/(main)/(tournaments)/tournament/components/tournament_feed.tsx index 8be21c506..5191764b9 100644 --- a/front_end/src/app/(main)/(tournaments)/tournament/components/tournament_feed.tsx +++ b/front_end/src/app/(main)/(tournaments)/tournament/components/tournament_feed.tsx @@ -1,5 +1,6 @@ "use client"; +import { sendGAEvent } from "@next/third-parties/google"; import { useSearchParams } from "next/navigation"; import { FC, useEffect, useState } from "react"; @@ -39,6 +40,7 @@ const TournamentFeed: FC = ({ slug }) => { setIsLoading(true); setError(undefined); try { + sendGAEvent("event", "feedSearch", { value: pageFilters }); const { questions } = (await fetchPosts( pageFilters, 0, diff --git a/front_end/src/app/(main)/components/cookies_banner/index.tsx b/front_end/src/app/(main)/components/cookies_banner/index.tsx index 6e3d2df65..8247ccb8c 100644 --- a/front_end/src/app/(main)/components/cookies_banner/index.tsx +++ b/front_end/src/app/(main)/components/cookies_banner/index.tsx @@ -1,7 +1,8 @@ "use client"; + import Link from "next/link"; import { useTranslations } from "next-intl"; -// import posthog from "posthog-js"; +import posthog from "posthog-js"; import { FC, useEffect, useState } from "react"; import Button from "@/components/ui/button"; @@ -15,26 +16,31 @@ const STORAGE_KEY = "analytic_cookie_consent"; const CookiesBanner: FC = () => { const t = useTranslations(); - // const [isModalOpen, setIsModalOpen] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); const [consentGiven, setConsentGiven] = useState(null); const [analyticsCheckboxValue, setAnalyticsCheckboxValue] = useState(true); - // const openModal = () => setIsModalOpen(true); - // const closeModal = () => setIsModalOpen(false); + const openModal = () => setIsModalOpen(true); + const closeModal = () => setIsModalOpen(false); useEffect(() => { setConsentGiven(getAnalyticsCookieConsentGiven()); }, []); - // TODO: uncomment once we want to support cookies configuration - // useEffect(() => { - // if (consentGiven !== null) { - // posthog.set_config({ - // persistence: consentGiven === "yes" ? "localStorage+cookie" : "memory", - // }); - // } - // }, [consentGiven]); + useEffect(() => { + if (consentGiven !== null) { + posthog.set_config({ + persistence: consentGiven === "yes" ? "localStorage+cookie" : "memory", + }); + + if (typeof window !== "undefined" && window.gtag) { + window.gtag("consent", "update", { + analytics_storage: consentGiven === "yes" ? "granted" : "denied", + }); + } + } + }, [consentGiven]); const submitBanner = () => { const consentValue = analyticsCheckboxValue ? "yes" : "no"; @@ -67,21 +73,20 @@ const CookiesBanner: FC = () => {

- {/*TODO: uncomment once we want to support cookies configuration*/} - {/**/} +
- {/**/} + ); }; diff --git a/front_end/src/app/(main)/questions/components/question_topics.tsx b/front_end/src/app/(main)/questions/components/question_topics.tsx index d7220ed81..35461da6b 100644 --- a/front_end/src/app/(main)/questions/components/question_topics.tsx +++ b/front_end/src/app/(main)/questions/components/question_topics.tsx @@ -6,6 +6,7 @@ import { faHome, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { sendGAEvent } from "@next/third-parties/google"; import classNames from "classnames"; import { useTranslations } from "next-intl"; import { FC, useMemo, useState } from "react"; @@ -112,13 +113,23 @@ const QuestionTopics: FC = ({ topics }) => { switchFeed(FeedType.MY_PREDICTIONS)} + onClick={() => { + sendGAEvent("event", "sidebarClick", { + value: t("myPredictions"), + }); + switchFeed(FeedType.MY_PREDICTIONS); + }} isActive={currentFeed === FeedType.MY_PREDICTIONS} /> switchFeed(FeedType.MY_QUESTIONS_AND_POSTS)} + onClick={() => { + sendGAEvent("event", "sidebarClick", { + value: t("myQuestionsAndPosts"), + }); + switchFeed(FeedType.MY_QUESTIONS_AND_POSTS); + }} isActive={currentFeed === FeedType.MY_QUESTIONS_AND_POSTS} /> @@ -128,12 +139,22 @@ const QuestionTopics: FC = ({ topics }) => { emoji="πŸ€–πŸ”­" text="AI Benchmarking" href="/aib" + onClick={() => + sendGAEvent("event", "sidebarClick", { + value: "AI Benchmarking", + }) + } /> + sendGAEvent("event", "sidebarClick", { + value: "2024 US Election Hub", + }) + } /> {!!hotTopics.length && ( <> @@ -144,7 +165,12 @@ const QuestionTopics: FC = ({ topics }) => { isActive={selectedTopic === topic.slug} emoji={topic.emoji} text={topic.name} - onClick={() => selectTopic(topic)} + onClick={() => { + sendGAEvent("event", "sidebarClick", { + value: topic.name, + }); + selectTopic(topic); + }} /> ))} @@ -159,7 +185,12 @@ const QuestionTopics: FC = ({ topics }) => { isActive={selectedTopic === category.slug} emoji={category.emoji} text={category.name} - onClick={() => selectTopic(category)} + onClick={() => { + sendGAEvent("event", "sidebarClick", { + value: category.name, + }); + selectTopic(category); + }} /> ))} @@ -170,13 +201,23 @@ const QuestionTopics: FC = ({ topics }) => { text={t("seeAllCategories")} emoji={} isActive={false} + onClick={() => { + sendGAEvent("event", "sidebarClick", { + value: t("seeAllCategories"), + }); + }} />
{user && ( } - onClick={() => switchFeed(FeedType.IN_REVIEW)} + onClick={() => { + sendGAEvent("event", "sidebarClick", { + value: t("inReview"), + }); + switchFeed(FeedType.IN_REVIEW); + }} isActive={currentFeed === FeedType.IN_REVIEW} /> )} diff --git a/front_end/src/app/layout.tsx b/front_end/src/app/layout.tsx index 02bdbf332..ab9c6f9cc 100644 --- a/front_end/src/app/layout.tsx +++ b/front_end/src/app/layout.tsx @@ -3,6 +3,7 @@ import "@fortawesome/fontawesome-svg-core/styles.css"; import { GoogleAnalytics } from "@next/third-parties/google"; import type { Metadata } from "next"; import "./globals.css"; +import dynamic from "next/dynamic"; import localFont from "next/font/local"; import { NextIntlClientProvider } from "next-intl"; import { getLocale, getMessages } from "next-intl/server"; @@ -19,6 +20,13 @@ import ProfileApi from "@/services/profile"; import { CSPostHogProvider } from "./providers"; +const PostHogPageView = dynamic( + () => import("@/components/posthog_page_view"), + { + ssr: false, + } +); + config.autoAddCss = false; const sourceSerifPro = localFont({ @@ -128,6 +136,7 @@ export default async function RootLayout({ > + diff --git a/front_end/src/app/providers.tsx b/front_end/src/app/providers.tsx index 6e01263dc..6d973b11f 100644 --- a/front_end/src/app/providers.tsx +++ b/front_end/src/app/providers.tsx @@ -1,21 +1,27 @@ "use client"; import posthog from "posthog-js"; import { PostHogProvider } from "posthog-js/react"; +import { useEffect } from "react"; import { getAnalyticsCookieConsentGiven } from "@/app/(main)/components/cookies_banner"; export function CSPostHogProvider({ children }: { children: any }) { - if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_KEY) { - posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { - api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, - // set to 'always' to create profiles for anonymous users as well - person_profiles: "identified_only", - // TODO: uncomment once we want to support cookies configuration - // persistence: - // getAnalyticsCookieConsentGiven() === "yes" - // ? "localStorage+cookie" - // : "memory", - }); - } + useEffect(() => { + if (process.env.NEXT_PUBLIC_POSTHOG_KEY) { + posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { + api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, + // set to 'always' to create profiles for anonymous users as well + person_profiles: "identified_only", + // Disable automatic pageview capture, as we capture manually + capture_pageview: false, + disable_session_recording: true, + persistence: + getAnalyticsCookieConsentGiven() === "yes" + ? "localStorage+cookie" + : "memory", + }); + } + }, []); + return {children}; } diff --git a/front_end/src/components/posthog_page_view.tsx b/front_end/src/components/posthog_page_view.tsx new file mode 100644 index 000000000..6f39f134a --- /dev/null +++ b/front_end/src/components/posthog_page_view.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { usePathname, useSearchParams } from "next/navigation"; +import { usePostHog } from "posthog-js/react"; +import { useEffect } from "react"; + +export default function PostHogPageView() { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const posthog = usePostHog(); + useEffect(() => { + if (pathname && posthog) { + let url = window.origin + pathname; + if (searchParams.toString()) { + url = url + `?${searchParams.toString()}`; + } + posthog.capture("$pageview", { + $current_url: url, + }); + } + }, [pathname, searchParams, posthog]); + + return null; +} diff --git a/front_end/src/components/posts_feed/paginated_feed.tsx b/front_end/src/components/posts_feed/paginated_feed.tsx index d351303d2..5c6c26190 100644 --- a/front_end/src/components/posts_feed/paginated_feed.tsx +++ b/front_end/src/components/posts_feed/paginated_feed.tsx @@ -1,7 +1,7 @@ "use client"; -import * as Sentry from "@sentry/nextjs"; +import { sendGAEvent } from "@next/third-parties/google"; import { useTranslations } from "next-intl"; -import { FC, Fragment, useState } from "react"; +import { FC, Fragment, useEffect, useState } from "react"; import { fetchMorePosts } from "@/app/(main)/questions/actions"; import NewsCard from "@/components/news_card"; @@ -42,11 +42,16 @@ const PaginatedPostsFeed: FC = ({ (Error & { digest?: string }) | undefined >(); + useEffect(() => { + // capture search event from AwaitedPostsFeed + sendGAEvent("event", "feedSearch", { value: filters }); + }, [filters]); const loadMorePosts = async () => { if (hasMoreData) { setIsLoading(true); setError(undefined); try { + sendGAEvent("event", "feedSearch", { value: filters }); const { newPosts, hasNextPage } = await fetchMorePosts( filters, offset,