From 32f9c15b1ba098568952bc6063472e1f15f741fa Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Tue, 4 Nov 2025 14:53:56 +0200 Subject: [PATCH] docs: refactor analytics + remove segment (#13954) * docs: refactor analytics + remove segment * small refactor of condition --- www/apps/api-reference/providers/index.tsx | 5 +- www/apps/book/providers/index.tsx | 5 +- www/apps/cloud/providers/index.tsx | 5 +- www/apps/resources/providers/index.tsx | 5 +- www/apps/ui/providers/index.tsx | 5 +- www/apps/user-guide/providers/index.tsx | 5 +- .../docs-ui/src/components/Feedback/index.tsx | 5 +- .../docs-ui/src/providers/Analytics/index.tsx | 119 +++--------------- .../providers/Analytics/providers/posthog.tsx | 12 ++ .../providers/Analytics/providers/reo-dev.tsx | 27 ++++ .../providers/Analytics/providers/segment.tsx | 82 ++++++++++++ 11 files changed, 146 insertions(+), 129 deletions(-) create mode 100644 www/packages/docs-ui/src/providers/Analytics/providers/posthog.tsx create mode 100644 www/packages/docs-ui/src/providers/Analytics/providers/reo-dev.tsx create mode 100644 www/packages/docs-ui/src/providers/Analytics/providers/segment.tsx diff --git a/www/apps/api-reference/providers/index.tsx b/www/apps/api-reference/providers/index.tsx index f7e979a458..e2ea9a7ae6 100644 --- a/www/apps/api-reference/providers/index.tsx +++ b/www/apps/api-reference/providers/index.tsx @@ -16,10 +16,7 @@ type ProvidersProps = { const Providers = ({ children }: ProvidersProps) => { return ( - + diff --git a/www/apps/book/providers/index.tsx b/www/apps/book/providers/index.tsx index d115ddea6b..bd5c7657a9 100644 --- a/www/apps/book/providers/index.tsx +++ b/www/apps/book/providers/index.tsx @@ -23,10 +23,7 @@ type ProvidersProps = { const Providers = ({ children, aiAssistantProps = {} }: ProvidersProps) => { return ( - + diff --git a/www/apps/cloud/providers/index.tsx b/www/apps/cloud/providers/index.tsx index e9c344b269..d0bc40f390 100644 --- a/www/apps/cloud/providers/index.tsx +++ b/www/apps/cloud/providers/index.tsx @@ -24,10 +24,7 @@ type ProvidersProps = { const Providers = ({ children }: ProvidersProps) => { return ( - + diff --git a/www/apps/resources/providers/index.tsx b/www/apps/resources/providers/index.tsx index a92d2ef7b0..db04b05da5 100644 --- a/www/apps/resources/providers/index.tsx +++ b/www/apps/resources/providers/index.tsx @@ -21,10 +21,7 @@ type ProvidersProps = { const Providers = ({ children }: ProvidersProps) => { return ( - + { return ( - + diff --git a/www/apps/user-guide/providers/index.tsx b/www/apps/user-guide/providers/index.tsx index e9c344b269..d0bc40f390 100644 --- a/www/apps/user-guide/providers/index.tsx +++ b/www/apps/user-guide/providers/index.tsx @@ -24,10 +24,7 @@ type ProvidersProps = { const Providers = ({ children }: ProvidersProps) => { return ( - + diff --git a/www/packages/docs-ui/src/components/Feedback/index.tsx b/www/packages/docs-ui/src/components/Feedback/index.tsx index dbcd32ac60..133b373155 100644 --- a/www/packages/docs-ui/src/components/Feedback/index.tsx +++ b/www/packages/docs-ui/src/components/Feedback/index.tsx @@ -90,12 +90,9 @@ export const Feedback = ({ : showForm ? inlineQuestionRef : inlineFeedbackRef - const { loaded, track } = useAnalytics() + const { track } = useAnalytics() function handleFeedback(feedback: boolean) { - if (!loaded) { - return - } setPositiveFeedback(feedback) setShowForm(true) submitFeedback(feedback) diff --git a/www/packages/docs-ui/src/providers/Analytics/index.tsx b/www/packages/docs-ui/src/providers/Analytics/index.tsx index 634b6e851c..68f95a638f 100644 --- a/www/packages/docs-ui/src/providers/Analytics/index.tsx +++ b/www/packages/docs-ui/src/providers/Analytics/index.tsx @@ -7,20 +7,16 @@ import React, { useEffect, useState, } from "react" -import { Analytics, AnalyticsBrowser } from "@segment/analytics-next" -import posthog from "posthog-js" - -// @ts-expect-error Doesn't have a types package -import { loadReoScript } from "reodotdev" +import { useSegmentAnalytics } from "./providers/segment" +import { usePostHogAnalytics } from "./providers/posthog" +import { useReoDevAnalytics } from "./providers/reo-dev" export type ExtraData = { section?: string - [key: string]: any + [key: string]: unknown } export type AnalyticsContextType = { - loaded: boolean - analytics: Analytics | null track: ({ event, instant, @@ -34,7 +30,7 @@ type Trackers = "segment" | "posthog" export type TrackedEvent = { event: string - options?: Record + options?: Record callback?: () => void tracker?: Trackers | Trackers[] } @@ -47,73 +43,18 @@ export type AnalyticsProviderProps = { children?: React.ReactNode } -const LOCAL_STORAGE_KEY = "ajs_anonymous_id" - export const AnalyticsProvider = ({ - segmentWriteKey = "temp", + segmentWriteKey, reoDevKey, children, }: AnalyticsProviderProps) => { - // loaded is used to ensure that a connection has been made to segment - // even if it failed. This is to ensure that the connection isn't - // continuously retried - const [loaded, setLoaded] = useState(false) - const [analytics, setAnalytics] = useState(null) - const analyticsBrowser = new AnalyticsBrowser() - const [queue, setQueue] = useState([]) - - const initSegment = useCallback(() => { - if (!loaded) { - analyticsBrowser - .load( - { writeKey: segmentWriteKey }, - { - initialPageview: true, - user: { - localStorage: { - key: LOCAL_STORAGE_KEY, - }, - }, - } - ) - .then((instance) => { - setAnalytics(instance[0]) - }) - .catch((e) => - console.error(`Could not connect to Segment. Error: ${e}`) - ) - .finally(() => setLoaded(true)) - } - }, [loaded, segmentWriteKey]) - - const trackWithSegment = useCallback( - async ({ event, options }: TrackedEvent) => { - if (analytics) { - void analytics.track(event, { - ...options, - uuid: analytics.user().anonymousId(), - }) - } else { - // push the event into the queue - setQueue((prevQueue) => [ - ...prevQueue, - { - event, - options, - tracker: "segment", - }, - ]) - console.warn( - "Segment is either not installed or not configured. Simulating success..." - ) - } - }, - [analytics, loaded] - ) - - const trackWithPostHog = async ({ event, options }: TrackedEvent) => { - posthog.capture(event, options) - } + const [eventsQueue, setEventsQueue] = useState([]) + const { track: trackWithSegment } = useSegmentAnalytics({ + segmentWriteKey, + setEventsQueue, + }) + const { track: trackWithPostHog } = usePostHogAnalytics() + useReoDevAnalytics({ reoDevKey }) const processEvent = useCallback( async (event: TrackedEvent) => { @@ -137,7 +78,7 @@ export const AnalyticsProvider = ({ const track = ({ event }: { event: TrackedEvent }) => { // Always queue events - this makes tracking non-blocking - setQueue((prevQueue) => [...prevQueue, event]) + setEventsQueue((prevQueue) => [...prevQueue, event]) // Process event callback immediately // This ensures that the callback is called even if the event is queued @@ -145,14 +86,10 @@ export const AnalyticsProvider = ({ } useEffect(() => { - initSegment() - }, [initSegment]) - - useEffect(() => { - if (analytics && queue.length) { + if (eventsQueue.length) { // Process queue in background without blocking - const currentQueue = [...queue] - setQueue([]) + const currentQueue = [...eventsQueue] + setEventsQueue([]) // Process events asynchronously in batches to avoid overwhelming the system const batchSize = 5 @@ -163,32 +100,12 @@ export const AnalyticsProvider = ({ }, i * 10) // Small delay between batches } } - }, [analytics, queue, trackWithSegment, trackWithPostHog, processEvent]) - - useEffect(() => { - if (!reoDevKey) { - return - } - - loadReoScript({ - clientID: reoDevKey, - }) - .then((Reo: unknown) => { - ;(Reo as { init: (config: { clientID: string }) => void }).init({ - clientID: reoDevKey, - }) - }) - .catch((e: Error) => { - console.error(`Could not connect to Reodotdev. Error: ${e}`) - }) - }, [reoDevKey]) + }, [eventsQueue, processEvent]) return ( {children} diff --git a/www/packages/docs-ui/src/providers/Analytics/providers/posthog.tsx b/www/packages/docs-ui/src/providers/Analytics/providers/posthog.tsx new file mode 100644 index 0000000000..48af781f60 --- /dev/null +++ b/www/packages/docs-ui/src/providers/Analytics/providers/posthog.tsx @@ -0,0 +1,12 @@ +import posthog from "posthog-js" +import { TrackedEvent } from ".." + +export const usePostHogAnalytics = () => { + const track = async ({ event, options }: TrackedEvent) => { + posthog.capture(event, options) + } + + return { + track, + } +} diff --git a/www/packages/docs-ui/src/providers/Analytics/providers/reo-dev.tsx b/www/packages/docs-ui/src/providers/Analytics/providers/reo-dev.tsx new file mode 100644 index 0000000000..ae76302862 --- /dev/null +++ b/www/packages/docs-ui/src/providers/Analytics/providers/reo-dev.tsx @@ -0,0 +1,27 @@ +import { useEffect } from "react" +// @ts-expect-error Doesn't have a types package +import { loadReoScript } from "reodotdev" + +type UseReoDevAnalyticsProps = { + reoDevKey: string | undefined +} + +export const useReoDevAnalytics = ({ reoDevKey }: UseReoDevAnalyticsProps) => { + useEffect(() => { + if (!reoDevKey) { + return + } + + loadReoScript({ + clientID: reoDevKey, + }) + .then((Reo: unknown) => { + ;(Reo as { init: (config: { clientID: string }) => void }).init({ + clientID: reoDevKey, + }) + }) + .catch((e: Error) => { + console.error(`Could not connect to Reodotdev. Error: ${e}`) + }) + }, [reoDevKey]) +} diff --git a/www/packages/docs-ui/src/providers/Analytics/providers/segment.tsx b/www/packages/docs-ui/src/providers/Analytics/providers/segment.tsx new file mode 100644 index 0000000000..40f5a5e551 --- /dev/null +++ b/www/packages/docs-ui/src/providers/Analytics/providers/segment.tsx @@ -0,0 +1,82 @@ +import { Analytics, AnalyticsBrowser } from "@segment/analytics-next" +import { useCallback, useEffect, useState } from "react" +import { TrackedEvent } from ".." + +type UseSegmentAnalyticsProps = { + segmentWriteKey: string | undefined + setEventsQueue: React.Dispatch> +} + +const LOCAL_STORAGE_KEY = "ajs_anonymous_id" + +export const useSegmentAnalytics = ({ + segmentWriteKey, + setEventsQueue, +}: UseSegmentAnalyticsProps) => { + // loaded is used to ensure that a connection has been made to segment + // even if it failed. This is to ensure that the connection isn't + // continuously retried + const [loaded, setLoaded] = useState(false) + const [segmentAnalytics, setAnalytics] = useState(null) + const segmentAnalyticsBrowser = new AnalyticsBrowser() + + const initSegment = useCallback(() => { + if (!segmentWriteKey || !loaded) { + return + } + segmentAnalyticsBrowser + .load( + { writeKey: segmentWriteKey }, + { + initialPageview: true, + user: { + localStorage: { + key: LOCAL_STORAGE_KEY, + }, + }, + } + ) + .then((instance) => { + setAnalytics(instance[0]) + }) + .catch((e) => console.error(`Could not connect to Segment. Error: ${e}`)) + .finally(() => setLoaded(true)) + }, [loaded, segmentWriteKey]) + + const track = useCallback( + async ({ event, options }: TrackedEvent) => { + if (!loaded) { + return + } + if (segmentAnalytics) { + void segmentAnalytics.track(event, { + ...options, + uuid: segmentAnalytics.user().anonymousId(), + }) + } else { + // push the event into the queue + setEventsQueue((prevQueue) => [ + ...prevQueue, + { + event, + options, + tracker: "segment", + }, + ]) + console.warn( + "Segment is either not installed or not configured. Simulating success..." + ) + } + }, + [segmentAnalytics, loaded] + ) + + useEffect(() => { + initSegment() + }, [initSegment]) + + return { + loaded, + track, + } +}