-
Notifications
You must be signed in to change notification settings - Fork 160
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #546 from input-output-hk/filip/feat/posthog
filip(feat): add posthog integration
- Loading branch information
Showing
8 changed files
with
292 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import React, { createContext, useCallback, useEffect, useState } from 'react' | ||
import dayjs from 'dayjs' | ||
import { posthog } from 'posthog-js' | ||
import { PostHogProvider } from 'posthog-js/react' | ||
import TrackRoute from './TrackRoute' | ||
import useHasConsent, { ConsentType } from './useHasConsent' | ||
|
||
/** | ||
* @file This file exports an AnalyticsContext and an AnalyticsProvider component. | ||
* The AnalyticsContext is a context object that provides a function to capture analytics events. | ||
* The AnalyticsProvider is a component that provides the PostHog client and wraps its children with the AnalyticsContext. | ||
* @module AnalyticsContext | ||
*/ | ||
|
||
/** | ||
* A context object that provides a function to capture analytics events. | ||
* @typedef {Object} AnalyticsContext | ||
* @property {Function} capture - A function that captures an analytics event. | ||
* @param {string} eventName - The name of the event to capture. | ||
* @param {Object} [eventProps] - An optional object containing additional properties to include in the event. | ||
*/ | ||
|
||
/** | ||
* A component that provides the PostHog client and wraps its children with the AnalyticsContext. | ||
* @typedef {Object} AnalyticsProvider | ||
* @property {Object} children - The child components to wrap with the AnalyticsContext. | ||
*/ | ||
|
||
/** | ||
* The base event properties to include in all analytics events. | ||
* @typedef {Function} BaseEventProps | ||
* @returns {Object} An object containing the base event properties. | ||
*/ | ||
|
||
/** | ||
* A hook that returns the base event properties to include in all analytics events. | ||
* @typedef {Function} UseBaseEventProps | ||
* @returns {BaseEventProps} A function that returns an object containing the base event properties. | ||
*/ | ||
|
||
/** | ||
* A hook that returns a function to capture analytics events. | ||
* @typedef {Function} UseAnalyticsCapture | ||
* @returns {Function} A function that captures an analytics event. | ||
* @param {string} eventName - The name of the event to capture. | ||
* @param {Object} [eventProps] - An optional object containing additional properties to include in the event. | ||
*/ | ||
|
||
/** | ||
* A hook that returns the PostHog client. | ||
* @typedef {Function} UsePostHogClient | ||
* @returns {Object} The PostHog client. | ||
*/ | ||
|
||
/** | ||
* A hook that returns whether the user has accepted analytics consent. | ||
* @typedef {Function} UseHasAnalyticsConsent | ||
* @returns {boolean} Whether the user has accepted analytics consent. | ||
*/ | ||
|
||
/** | ||
* A hook that returns the PostHog client and a function to capture analytics events. | ||
* @typedef {Function} UseAnalytics | ||
* @returns {Array} An array containing the PostHog client and a function to capture analytics events. | ||
* @param {string} eventName - The name of the event to capture. | ||
* @param {Object} [eventProps] - An optional object containing additional properties to include in the event. | ||
*/ | ||
|
||
/** | ||
* The props for the AnalyticsProvider component. | ||
* @typedef {Object} AnalyticsProviderProps | ||
* @property {Object} children - The child components to wrap with the AnalyticsContext. | ||
*/ | ||
|
||
/** | ||
* The props for the AnalyticsContextProvider component. | ||
* @typedef {Object} AnalyticsContextProviderProps | ||
* @property {Function} capture - A function that captures an analytics event. | ||
* @param {string} eventName - The name of the event to capture. | ||
* @param {Object} [eventProps] - An optional object containing additional properties to include in the event. | ||
* @property {Object} children - The child components to wrap with the AnalyticsContext. | ||
*/ | ||
export const AnalyticsContext = createContext(() => {}) | ||
|
||
export function AnalyticsProvider({ children }) { | ||
const [client, setClient] = useState() | ||
|
||
const analyticsAccepted = useHasConsent(ConsentType.ANALYTICS) | ||
|
||
const baseEventProps = useCallback( | ||
() => ({ | ||
sent_at_local: dayjs().format(), | ||
posthog_project_id: process.env.GATSBY_POSTHOG_PROJECT_ID, | ||
}), | ||
[], | ||
) | ||
|
||
const capture = useCallback( | ||
(eventName, eventProperties = {}) => { | ||
if (client) { | ||
client.capture(eventName, { | ||
...baseEventProps(), | ||
...eventProperties, | ||
}) | ||
} | ||
}, | ||
[client, baseEventProps], | ||
) | ||
|
||
useEffect(() => { | ||
const posthogApiKey = process.env.GATSBY_POSTHOG_API_KEY | ||
|
||
const posthogApiHost = process.env.GATSBY_POSTHOG_API_HOST | ||
|
||
const turnOn = | ||
analyticsAccepted === true && | ||
typeof posthogApiKey === 'string' && | ||
posthogApiKey && | ||
typeof posthogApiHost === 'string' && | ||
posthogApiHost | ||
|
||
setClient(oldClient => { | ||
if (turnOn) { | ||
const client = | ||
oldClient ?? | ||
posthog.init(posthogApiKey ?? '', { | ||
api_host: posthogApiHost, | ||
capture_pageleave: false, | ||
capture_pageview: false, | ||
}) | ||
|
||
// clear localStorage state that might have been set by previous clients | ||
client.clear_opt_in_out_capturing() | ||
|
||
// we got consent, start capturing | ||
client.opt_in_capturing({ | ||
capture_properties: baseEventProps(), | ||
}) | ||
// calling a private function as a fix for bug https://github.com/PostHog/posthog-js/issues/336 | ||
client._start_queue_if_opted_in() | ||
|
||
return client | ||
} else { | ||
if (oldClient) { | ||
oldClient.opt_out_capturing() | ||
} | ||
return undefined | ||
} | ||
}) | ||
}, [analyticsAccepted, baseEventProps]) | ||
|
||
return ( | ||
<PostHogProvider client={client}> | ||
<AnalyticsContext.Provider value={capture}> | ||
<TrackRoute /> | ||
{children} | ||
</AnalyticsContext.Provider> | ||
</PostHogProvider> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { useLocation } from '@reach/router' | ||
import { useContext, useEffect } from 'react' | ||
import { AnalyticsContext } from './AnalyticsContext' | ||
|
||
export default function TrackRoute() { | ||
const location = useLocation() | ||
|
||
const capture = useContext(AnalyticsContext) | ||
|
||
useEffect(() => { | ||
capture('$pageview') | ||
}, [capture, location.pathname, location.search]) | ||
|
||
return null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export const OsanoConsentType = { | ||
ESSENTIAL: 'ESSENTIAL', | ||
STORAGE: 'STORAGE', | ||
MARKETING: 'MARKETING', | ||
PERSONALIZATION: 'PERSONALIZATION', | ||
ANALYTICS: 'ANALYTICS', | ||
OPT_OUT: 'OPT_OUT', | ||
} | ||
|
||
export const OsanoConsentDecision = { | ||
ACCEPT: 'ACCEPT', | ||
DENY: 'DENY', | ||
} | ||
|
||
export const OsanoEvent = { | ||
CONSENT_SAVED: 'osano-cm-consent-saved', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { useEffect, useState } from 'react' | ||
import { OsanoConsentDecision, OsanoConsentType, OsanoEvent } from './osano' | ||
|
||
export { OsanoConsentType as ConsentType } | ||
|
||
export default function useHasConsent(type) { | ||
let initialConsent | ||
|
||
try { | ||
initialConsent = | ||
typeof window && typeof window.Osano !== 'undefined' | ||
? window.Osano.cm.getConsent()[type] === OsanoConsentDecision.ACCEPT | ||
: undefined | ||
} catch (err) { | ||
console.log(err) | ||
} | ||
|
||
const [state, setState] = useState(initialConsent) | ||
|
||
useEffect(() => { | ||
const cm = | ||
typeof window && typeof window.Osano !== 'undefined' | ||
? window.Osano.cm | ||
: undefined | ||
|
||
if (!cm) { | ||
return | ||
} | ||
|
||
setState(cm.getConsent()[type] === OsanoConsentDecision.ACCEPT) | ||
|
||
const handler = changed => { | ||
if (type in changed) { | ||
setState(changed[type] === OsanoConsentDecision.ACCEPT) | ||
} | ||
} | ||
|
||
cm.addEventListener(OsanoEvent.CONSENT_SAVED, handler) | ||
return () => { | ||
cm.removeEventListener(OsanoEvent.CONSENT_SAVED, handler) | ||
} | ||
}, [type]) | ||
|
||
return state | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.