docs: refactor analytics + remove segment (#13954)

* docs: refactor analytics + remove segment

* small refactor of condition
This commit is contained in:
Shahed Nasser
2025-11-04 14:53:56 +02:00
committed by GitHub
parent baec64d4f1
commit 32f9c15b1b
11 changed files with 146 additions and 129 deletions

View File

@@ -16,10 +16,7 @@ type ProvidersProps = {
const Providers = ({ children }: ProvidersProps) => { const Providers = ({ children }: ProvidersProps) => {
return ( return (
<AnalyticsProvider <AnalyticsProvider reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}>
segmentWriteKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}
reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}
>
<SiteConfigProvider config={config}> <SiteConfigProvider config={config}>
<PageLoadingProvider> <PageLoadingProvider>
<ScrollControllerProvider scrollableSelector="#main"> <ScrollControllerProvider scrollableSelector="#main">

View File

@@ -23,10 +23,7 @@ type ProvidersProps = {
const Providers = ({ children, aiAssistantProps = {} }: ProvidersProps) => { const Providers = ({ children, aiAssistantProps = {} }: ProvidersProps) => {
return ( return (
<AnalyticsProvider <AnalyticsProvider reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}>
segmentWriteKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}
reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}
>
<SiteConfigProvider config={config}> <SiteConfigProvider config={config}>
<LearningPathProvider> <LearningPathProvider>
<NotificationProvider> <NotificationProvider>

View File

@@ -24,10 +24,7 @@ type ProvidersProps = {
const Providers = ({ children }: ProvidersProps) => { const Providers = ({ children }: ProvidersProps) => {
return ( return (
<AnalyticsProvider <AnalyticsProvider reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}>
segmentWriteKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}
reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}
>
<SiteConfigProvider config={config}> <SiteConfigProvider config={config}>
<MobileProvider> <MobileProvider>
<ColorModeProvider> <ColorModeProvider>

View File

@@ -21,10 +21,7 @@ type ProvidersProps = {
const Providers = ({ children }: ProvidersProps) => { const Providers = ({ children }: ProvidersProps) => {
return ( return (
<AnalyticsProvider <AnalyticsProvider reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}>
segmentWriteKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}
reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}
>
<SiteConfigProvider config={config}> <SiteConfigProvider config={config}>
<LearningPathProvider <LearningPathProvider
baseUrl={process.env.NEXT_PUBLIC_BASE_PATH || "/resources"} baseUrl={process.env.NEXT_PUBLIC_BASE_PATH || "/resources"}

View File

@@ -25,10 +25,7 @@ type ProvidersProps = {
const Providers = ({ children }: ProvidersProps) => { const Providers = ({ children }: ProvidersProps) => {
return ( return (
<AnalyticsProvider <AnalyticsProvider reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}>
segmentWriteKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}
reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}
>
<SiteConfigProvider config={config}> <SiteConfigProvider config={config}>
<MobileProvider> <MobileProvider>
<ColorModeProvider> <ColorModeProvider>

View File

@@ -24,10 +24,7 @@ type ProvidersProps = {
const Providers = ({ children }: ProvidersProps) => { const Providers = ({ children }: ProvidersProps) => {
return ( return (
<AnalyticsProvider <AnalyticsProvider reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}>
segmentWriteKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}
reoDevKey={process.env.NEXT_PUBLIC_REO_DEV_CLIENT_ID}
>
<SiteConfigProvider config={config}> <SiteConfigProvider config={config}>
<MobileProvider> <MobileProvider>
<ColorModeProvider> <ColorModeProvider>

View File

@@ -90,12 +90,9 @@ export const Feedback = ({
: showForm : showForm
? inlineQuestionRef ? inlineQuestionRef
: inlineFeedbackRef : inlineFeedbackRef
const { loaded, track } = useAnalytics() const { track } = useAnalytics()
function handleFeedback(feedback: boolean) { function handleFeedback(feedback: boolean) {
if (!loaded) {
return
}
setPositiveFeedback(feedback) setPositiveFeedback(feedback)
setShowForm(true) setShowForm(true)
submitFeedback(feedback) submitFeedback(feedback)

View File

@@ -7,20 +7,16 @@ import React, {
useEffect, useEffect,
useState, useState,
} from "react" } from "react"
import { Analytics, AnalyticsBrowser } from "@segment/analytics-next" import { useSegmentAnalytics } from "./providers/segment"
import posthog from "posthog-js" import { usePostHogAnalytics } from "./providers/posthog"
import { useReoDevAnalytics } from "./providers/reo-dev"
// @ts-expect-error Doesn't have a types package
import { loadReoScript } from "reodotdev"
export type ExtraData = { export type ExtraData = {
section?: string section?: string
[key: string]: any [key: string]: unknown
} }
export type AnalyticsContextType = { export type AnalyticsContextType = {
loaded: boolean
analytics: Analytics | null
track: ({ track: ({
event, event,
instant, instant,
@@ -34,7 +30,7 @@ type Trackers = "segment" | "posthog"
export type TrackedEvent = { export type TrackedEvent = {
event: string event: string
options?: Record<string, any> options?: Record<string, unknown>
callback?: () => void callback?: () => void
tracker?: Trackers | Trackers[] tracker?: Trackers | Trackers[]
} }
@@ -47,73 +43,18 @@ export type AnalyticsProviderProps = {
children?: React.ReactNode children?: React.ReactNode
} }
const LOCAL_STORAGE_KEY = "ajs_anonymous_id"
export const AnalyticsProvider = ({ export const AnalyticsProvider = ({
segmentWriteKey = "temp", segmentWriteKey,
reoDevKey, reoDevKey,
children, children,
}: AnalyticsProviderProps) => { }: AnalyticsProviderProps) => {
// loaded is used to ensure that a connection has been made to segment const [eventsQueue, setEventsQueue] = useState<TrackedEvent[]>([])
// even if it failed. This is to ensure that the connection isn't const { track: trackWithSegment } = useSegmentAnalytics({
// continuously retried segmentWriteKey,
const [loaded, setLoaded] = useState<boolean>(false) setEventsQueue,
const [analytics, setAnalytics] = useState<Analytics | null>(null) })
const analyticsBrowser = new AnalyticsBrowser() const { track: trackWithPostHog } = usePostHogAnalytics()
const [queue, setQueue] = useState<TrackedEvent[]>([]) useReoDevAnalytics({ reoDevKey })
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 processEvent = useCallback( const processEvent = useCallback(
async (event: TrackedEvent) => { async (event: TrackedEvent) => {
@@ -137,7 +78,7 @@ export const AnalyticsProvider = ({
const track = ({ event }: { event: TrackedEvent }) => { const track = ({ event }: { event: TrackedEvent }) => {
// Always queue events - this makes tracking non-blocking // Always queue events - this makes tracking non-blocking
setQueue((prevQueue) => [...prevQueue, event]) setEventsQueue((prevQueue) => [...prevQueue, event])
// Process event callback immediately // Process event callback immediately
// This ensures that the callback is called even if the event is queued // This ensures that the callback is called even if the event is queued
@@ -145,14 +86,10 @@ export const AnalyticsProvider = ({
} }
useEffect(() => { useEffect(() => {
initSegment() if (eventsQueue.length) {
}, [initSegment])
useEffect(() => {
if (analytics && queue.length) {
// Process queue in background without blocking // Process queue in background without blocking
const currentQueue = [...queue] const currentQueue = [...eventsQueue]
setQueue([]) setEventsQueue([])
// Process events asynchronously in batches to avoid overwhelming the system // Process events asynchronously in batches to avoid overwhelming the system
const batchSize = 5 const batchSize = 5
@@ -163,32 +100,12 @@ export const AnalyticsProvider = ({
}, i * 10) // Small delay between batches }, i * 10) // Small delay between batches
} }
} }
}, [analytics, queue, trackWithSegment, trackWithPostHog, processEvent]) }, [eventsQueue, 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])
return ( return (
<AnalyticsContext.Provider <AnalyticsContext.Provider
value={{ value={{
analytics,
track, track,
loaded,
}} }}
> >
{children} {children}

View File

@@ -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,
}
}

View File

@@ -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])
}

View File

@@ -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<React.SetStateAction<TrackedEvent[]>>
}
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<boolean>(false)
const [segmentAnalytics, setAnalytics] = useState<Analytics | null>(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,
}
}