docs: refactor analytics + remove segment (#13954)
* docs: refactor analytics + remove segment * small refactor of condition
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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])
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user