Skip to content

Commit

Permalink
send tracking metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity committed Dec 18, 2024
1 parent fcfd3e7 commit 0b6d962
Show file tree
Hide file tree
Showing 25 changed files with 373 additions and 256 deletions.
8 changes: 0 additions & 8 deletions packages/ui/app/src/analytics/PosthogContainer.tsx

This file was deleted.

10 changes: 7 additions & 3 deletions packages/ui/app/src/analytics/amplitude.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as amplitude from "@amplitude/analytics-browser";
import { ReactNode } from "react";
import { useIsomorphicLayoutEffect } from "swr/_internal";
import { ReactNode, useEffect } from "react";
import { useSafeListenTrackEvents } from "./track";

export default function AmplitudeScript({ apiKey }: { apiKey: string }): ReactNode {
useIsomorphicLayoutEffect(() => {
useEffect(() => {
try {
amplitude.init(apiKey, undefined, {
autocapture: true,
Expand All @@ -14,5 +14,9 @@ export default function AmplitudeScript({ apiKey }: { apiKey: string }): ReactNo
}
}, [apiKey]);

useSafeListenTrackEvents(({ event, properties }) => {
amplitude.track(event, properties);
});

return false;
}
21 changes: 18 additions & 3 deletions packages/ui/app/src/analytics/datadog-rum.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { datadogRum, type RumInitConfiguration } from "@datadog/browser-rum";
import { ReactNode } from "react";
import { useIsomorphicLayoutEffect } from "swr/_internal";
import { ReactNode, useEffect } from "react";
import { useFernUser } from "../atoms";
import { useSafeListenTrackEvents } from "./track";

export default function DatadogRumScript(props: RumInitConfiguration): ReactNode {
useIsomorphicLayoutEffect(() => {
useEffect(() => {
datadogRum.init(props);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const user = useFernUser();
useEffect(() => {
if (user) {
datadogRum.setUser({ email: user.email, name: user.name });
} else {
datadogRum.clearUser();
}
}, [user]);

useSafeListenTrackEvents(({ event, properties }) => {
datadogRum.addAction(event, properties);
});

return false;
}
10 changes: 7 additions & 3 deletions packages/ui/app/src/analytics/fathom.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import * as Fathom from "fathom-client";
import { ReactNode } from "react";
import { useIsomorphicLayoutEffect } from "swr/_internal";
import { ReactNode, useEffect } from "react";
import { useRouteChangeComplete } from "../hooks/useRouteChanged";
import { useSafeListenTrackEvents } from "./track";

export function FathomScript({ siteId }: { siteId: string }): ReactNode {
useIsomorphicLayoutEffect(() => {
useEffect(() => {
Fathom.load(siteId);
}, [siteId]);

useRouteChangeComplete(() => {
Fathom.trackPageview();
});

useSafeListenTrackEvents(({ event, properties }) => {
Fathom.trackEvent(event, properties);
});

return false;
}
10 changes: 7 additions & 3 deletions packages/ui/app/src/analytics/hotjar.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import Hotjar from "@hotjar/browser";
import { ReactNode } from "react";
import { useIsomorphicLayoutEffect } from "swr/_internal";
import { ReactNode, useEffect } from "react";
import { useRouteChangeComplete } from "../hooks/useRouteChanged";
import { useSafeListenTrackEvents } from "./track";

export default function HotjarScript({ id, version }: { id: string; version: string }): ReactNode {
useIsomorphicLayoutEffect(() => {
useEffect(() => {
Hotjar.init(Number(id), Number(version));
}, [id, version]);

useRouteChangeComplete((route) => {
Hotjar.stateChange(route);
});

useSafeListenTrackEvents(({ event }) => {
Hotjar.event(event);
});

return false;
}
63 changes: 31 additions & 32 deletions packages/ui/app/src/analytics/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,30 @@ import { useAtomValue } from "jotai";
import { selectAtom } from "jotai/utils";
import dynamic from "next/dynamic";
import { ReactElement, memo } from "react";
import { DOCS_ATOM, DOMAIN_ATOM, DocsProps, EMPTY_ANALYTICS_CONFIG, FERN_USER_ATOM } from "../atoms";
import { Posthog } from "./PosthogContainer";
import { DOCS_ATOM, DOMAIN_ATOM, DocsProps, EMPTY_ANALYTICS_CONFIG } from "../atoms";

const IntercomScript = dynamic(() => import("./intercom").then((mod) => mod.default), { ssr: true });
const AmplitudeScript = dynamic(() => import("./amplitude").then((mod) => mod.default), { ssr: true });
const ClearbitScript = dynamic(() => import("./clearbit").then((mod) => mod.default), { ssr: true });
const CustomerPosthogScript = dynamic(() => import("./posthog").then((mod) => mod.CustomerPosthog), { ssr: true });
const DatadogRumScript = dynamic(() => import("./datadog-rum").then((mod) => mod.default), { ssr: true });
const FathomScript = dynamic(() => import("./fathom").then((mod) => mod.FathomScript), { ssr: true });
const FullstoryScript = dynamic(() => import("./fullstory").then((mod) => mod.default), { ssr: true });
const GoogleAnalytics = dynamic(() => import("@next/third-parties/google").then((mod) => mod.GoogleAnalytics), {
ssr: true,
});
const GoogleTagManager = dynamic(() => import("@next/third-parties/google").then((mod) => mod.GoogleTagManager), {
ssr: true,
});
const SegmentScript = dynamic(() => import("./segment").then((mod) => mod.default), { ssr: true });
const AmplitudeScript = dynamic(() => import("./amplitude").then((mod) => mod.default), { ssr: true });
const MixpanelScript = dynamic(() => import("./mixpanel").then((mod) => mod.default), { ssr: true });
const HeapScript = dynamic(() => import("./heap").then((mod) => mod.default), { ssr: true });
const HotjarScript = dynamic(() => import("./hotjar").then((mod) => mod.default), { ssr: true });
const IntercomScript = dynamic(() => import("./intercom").then((mod) => mod.default), { ssr: true });
const KoalaScript = dynamic(() => import("./koala").then((mod) => mod.default), { ssr: true });
const LogRocketScript = dynamic(() => import("./logrocket").then((mod) => mod.default), { ssr: true });
const MixpanelScript = dynamic(() => import("./mixpanel").then((mod) => mod.default), { ssr: true });
const PirschScript = dynamic(() => import("./pirsch").then((mod) => mod.default), { ssr: true });
const PlausibleScript = dynamic(() => import("./plausible").then((mod) => mod.default), { ssr: true });
const FathomScript = dynamic(() => import("./fathom").then((mod) => mod.FathomScript), { ssr: true });
const ClearbitScript = dynamic(() => import("./clearbit").then((mod) => mod.default), { ssr: true });
const HeapScript = dynamic(() => import("./heap").then((mod) => mod.default), { ssr: true });
const DatadogRumScript = dynamic(() => import("./datadog-rum").then((mod) => mod.default), { ssr: true });
const PosthogScript = dynamic(() => import("./posthog").then((mod) => mod.default), { ssr: true });
const SegmentScript = dynamic(() => import("./segment").then((mod) => mod.default), { ssr: true });

const ANALYTICS_CONFIG_ATOM = selectAtom<DocsProps, DocsV1Read.AnalyticsConfig>(
DOCS_ATOM,
Expand All @@ -37,42 +38,40 @@ const ANALYTICS_CONFIG_ATOM = selectAtom<DocsProps, DocsV1Read.AnalyticsConfig>(
export const CustomerAnalytics = memo(function CustomerAnalytics(): ReactElement | null {
const domain = useAtomValue(DOMAIN_ATOM);
const config = useAtomValue(ANALYTICS_CONFIG_ATOM);
const user = useAtomValue(FERN_USER_ATOM);

return (
<>
{/* Always render posthog for internal analytics + optional customer */}
<Posthog customerConfig={config.posthog} />
<PosthogScript />

{/* Additional analytics below are user-configured */}
{config.segment && <SegmentScript apiKey={config.segment.writeKey} host={domain} />}
{config.fullstory && <FullstoryScript orgId={config.fullstory.orgId} />}
{config.intercom && (
<IntercomScript
app_id={config.intercom.appId}
api_base={config.intercom.apiBase}
email={user?.email}
name={user?.name}
/>
)}
{config.gtm && <GoogleTagManager gtmId={config.gtm.containerId} />}
{config.ga4 && <GoogleAnalytics gaId={config.ga4.measurementId} />}
{config.amplitude && <AmplitudeScript apiKey={config.amplitude.apiKey} />}
{config.mixpanel && <MixpanelScript token={config.mixpanel.apiKey} />}
{config.hotjar && <HotjarScript id={config.hotjar.hjid} version={config.hotjar.hjsv} />}
{config.koala && <KoalaScript apiKey={config.koala.apiKey} />}
{config.logrocket && <LogRocketScript appId={config.logrocket.apiKey} />}
{config.pirsch && <PirschScript identificationCode={config.pirsch.id} />}
{config.plausible && <PlausibleScript domain={config.plausible.domain} />}
{config.fathom && <FathomScript siteId={config.fathom.siteId} />}
{config.clearbit && <ClearbitScript apiKey={config.clearbit.apiKey} />}
{config.heap && <HeapScript appId={config.heap.appId} />}
{config.datadog && (
<DatadogRumScript
applicationId={config.datadog.applicationId}
clientToken={config.datadog.clientToken}
site={config.datadog.site}
/>
)}
{config.fathom && <FathomScript siteId={config.fathom.siteId} />}
{config.fullstory && <FullstoryScript orgId={config.fullstory.orgId} />}
{config.ga4 && <GoogleAnalytics gaId={config.ga4.measurementId} />}
{config.gtm && <GoogleTagManager gtmId={config.gtm.containerId} />}
{config.heap && <HeapScript appId={config.heap.appId} />}
{config.hotjar && <HotjarScript id={config.hotjar.hjid} version={config.hotjar.hjsv} />}
{config.intercom && <IntercomScript app_id={config.intercom.appId} api_base={config.intercom.apiBase} />}
{config.koala && <KoalaScript apiKey={config.koala.apiKey} />}
{config.logrocket && <LogRocketScript appId={config.logrocket.apiKey} />}
{config.mixpanel && <MixpanelScript token={config.mixpanel.apiKey} />}
{config.pirsch && <PirschScript identificationCode={config.pirsch.id} />}
{config.plausible && <PlausibleScript domain={config.plausible.domain} />}
{config.posthog && (
<CustomerPosthogScript token={config.posthog.apiKey} api_host={config.posthog.endpoint} />
)}
{config.segment && <SegmentScript apiKey={config.segment.writeKey} host={domain} />}
</>
);
});

export { track, trackInternal } from "./track";
12 changes: 10 additions & 2 deletions packages/ui/app/src/analytics/intercom.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Script from "next/script";
import { ReactElement, useEffect } from "react";
import { useFernUser } from "../atoms";
import { useSafeListenTrackEvents } from "./track";

// copied from @intercom/messenger-js-sdk
interface IntercomSettings {
Expand Down Expand Up @@ -45,16 +47,22 @@ export default function IntercomScript(props: IntercomSettings): ReactElement {
* @param config
*/
function useIntercomInitializer(config: IntercomSettings): void {
const user = useFernUser();

useEffect(() => {
try {
if (window.Intercom) {
window.Intercom("boot", config);
window.Intercom("boot", { ...config, email: user?.email, name: user?.name } satisfies IntercomSettings);
}
} catch (e) {
// eslint-disable-next-line no-console
console.error("Error initializing Intercom", e);
}
}, [config]);
}, [config, user?.email, user?.name]);

useSafeListenTrackEvents(({ event, properties }) => {
window.Intercom("trackEvent", event, properties);
});
}

function widgetBootstrapScript(appId: string) {
Expand Down
18 changes: 17 additions & 1 deletion packages/ui/app/src/analytics/koala.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import Script from "next/script";
import { ReactNode } from "react";
import { ReactNode, useEffect } from "react";
import { useFernUser } from "../atoms";
import { useSafeListenTrackEvents } from "./track";

export default function KoalaScript({ apiKey }: { apiKey: string }): ReactNode {
const user = useFernUser();

useEffect(() => {
if (user && user.email && window.ko) {
window.ko.identify(user.email);
}
}, [user]);

useSafeListenTrackEvents(({ event, properties }) => {
if (window.ko) {
window.ko.track(event, properties);
}
});

return <Script id="koala" type="text/javascript" dangerouslySetInnerHTML={{ __html: initKoala(apiKey) }} />;
}

Expand Down
11 changes: 8 additions & 3 deletions packages/ui/app/src/analytics/logrocket.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import LogRocket from "logrocket";
import { ReactNode } from "react";
import { useIsomorphicLayoutEffect } from "swr/_internal";
import { ReactNode, useEffect } from "react";
import { useSafeListenTrackEvents } from "./track";

export default function LogRocketScript({ appId }: { appId: string }): ReactNode {
useIsomorphicLayoutEffect(() => {
useEffect(() => {
LogRocket.init(appId);
}, [appId]);

useSafeListenTrackEvents(({ event, properties }) => {
LogRocket.track(event, properties);
});

return false;
}
11 changes: 8 additions & 3 deletions packages/ui/app/src/analytics/mixpanel.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import mixpanel from "mixpanel-browser";
import { ReactNode } from "react";
import { useIsomorphicLayoutEffect } from "swr/_internal";
import { ReactNode, useEffect } from "react";
import { useSafeListenTrackEvents } from "./track";

export default function MixpanelScript({ token }: { token: string }): ReactNode {
useIsomorphicLayoutEffect(() => {
useEffect(() => {
mixpanel.init(token, {
track_pageview: true,
});
}, [token]);

useSafeListenTrackEvents(({ event, properties }) => {
mixpanel.track(event, properties);
});

return null;
}
1 change: 1 addition & 0 deletions packages/ui/app/src/analytics/pirsch.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Script from "next/script";
import { ReactNode } from "react";

// TODO: send events to pirsch
export default function PirschScript({ identificationCode }: { identificationCode: string }): ReactNode {
return <Script defer src="https://api.pirsch.io/pa.js" id="pianjs" data-code={identificationCode} />;
}
1 change: 1 addition & 0 deletions packages/ui/app/src/analytics/plausible.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Script from "next/script";
import { ReactNode } from "react";

// TODO: send events to plausible
export default function PlausibleScript({ domain }: { domain: string }): ReactNode {
return <Script id="plausible" defer src="https://plausible.io/js/script.js" data-domain={domain} />;
}
Loading

0 comments on commit 0b6d962

Please sign in to comment.