Skip to content

Commit

Permalink
feat(Analytics): implement PostHog (#2543)
Browse files Browse the repository at this point in the history
## πŸ”— Relevant links
- [Preview link](https://dev-portal-git-nels-analyticsadd-posthog-hashicorp.vercel.app/) πŸ”Ž
- [Asana task](https://app.asana.com/0/1202787000646550/1208027559696437/f) 🎟️

## πŸ—’οΈ What
This PR adds `PostHog` to https://developer.hashicorp.com by loading the PostHog script using our `ConsentManager` component.

### PostHog Installation Method

This implementation follows the [`HTML Snippet` PostHog installation](https://posthog.com/docs/libraries/js#option-1-add-the-javascript-snippet-to-your-html-span-classbg-gray-accent-light-darkbg-gray-accent-dark-text-gray-font-semibold-align-middle-text-sm-p-1-roundedrecommendedspan) instead of the Next.js installation. This was done for several reasons:
- We follow this method for loading several other scripts via `ConsentManager`
- There is talk of migrating to a Consent Management Platform which would likely load scripts via GTM. If that's the case, transitioning to GTM would largely just be a matter of copying the HTML snippet into GTM. That said, I could be missing something so very open to a Next.js-specific implementation!

### PostHog Setup

In PostHog, separate projects have been created for [`Development`](https://eu.posthog.com/project/29988) and [`Production`](https://eu.posthog.com/project/29987). 
This follows [PostHog's recommendation](https://posthog.com/tutorials/multiple-environments#using-multiple-projects) and [matches our Segment implementation for hashicorp.com](https://github.com/hashicorp/web/blob/0f7d2ffc8b2381370e98559d39d430c3a0a826cc/apps/www/next.config.js#L8-L10). Having separate projects enables two things: 

1. keeps data from our production instance separate from data in non-production environments
2. allows for testing PostHog in development / preview environments


## πŸ§ͺ Testing
If you need access to PostHog, reach out to me and I can send an invite.

### Validate non-production analytics are sent to the PostHog `Development` project
- [ ] Navigate to an [the v0.1.0 release notes for Boundary in the preview](https://dev-portal-git-nels-analyticsadd-posthog-hashicorp.vercel.app/boundary/docs/release-notes/v0_1_0)
- [ ] Navigate to the activity tab in the [`Development` project in PostHog](https://eu.posthog.com/project/29988/activity/explore)
- [ ] Validate that a `Pageview` event is created for the visit to the above page. It should look like this:
<details>
<summary>Screenshot</summary>


</details>

- [ ] Navigate to the [`Production` project in PostHog](https://eu.posthog.com/project/29987/products)
- [ ] Validate that there is no activity tab and therefore a `Pageview` event is not created
  • Loading branch information
nandereck authored Aug 16, 2024
1 parent 9b16c7b commit 47504d8
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 0 deletions.
4 changes: 4 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ module.exports = withHashicorp({
MKTG_CONTENT_API: process.env.MKTG_CONTENT_API,
// TODO: determine if DevDot needs this or not
SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY,
POSTHOG_PROJECT_API_KEY:
process.env.VERCEL_ENV !== 'production'
? process.env.POSTHOG_PROJECT_API_KEY_DEV
: process.env.POSTHOG_PROJECT_API_KEY_PROD,
},
svgo: {
plugins: [
Expand Down
41 changes: 41 additions & 0 deletions src/hooks/use-posthog-analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect } from 'react'
import { useRouter } from 'next/router'

declare global {
interface Window {
posthog?: $TSFixMe
}
}

function onRouteChangeComplete() {
/**
* PostHog automatically captures a `pageview` for initial page loads.
* Subsequent client-side navigation events have to be captured manually,
* which is why we have to set up this `onRouteChangeComplete` event.
*
* PostHog documentation for capturing pageviews in SPA with the JS web installation:
* https://posthog.com/docs/libraries/js#single-page-apps-and-pageviews
*/
if (window?.posthog?.capture === undefined) return
window.posthog.capture('$pageview')
}

/**
* Enables PostHog page view tracking on route changes
*/
export default function usePostHogPageAnalytics(): void {
const router = useRouter()

useEffect(() => {
// Ensures code only runs if PostHog has been initialized
if (!window?.posthog?.capture) return

// Record a pageview when route changes
router.events.on('routeChangeComplete', onRouteChangeComplete)

// Unassign event listener
return () => {
router.events.off('routeChangeComplete', onRouteChangeComplete)
}
}, [router.events])
}
4 changes: 4 additions & 0 deletions src/layouts/base-layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@ import createConsentManager from '@hashicorp/react-consent-manager/loader'
import useScrollPercentageAnalytics from 'hooks/use-scroll-percentage-analytics'
import { CoreDevDotLayoutWithTheme } from 'layouts/core-dev-dot-layout'
import { CommandBarProvider } from 'components/command-bar'
import developerConsentManagerServices from 'lib/consent-manager-services/developer'
import Footer from 'components/footer'
import NavigationHeader from 'components/navigation-header'
import alertBannerData from 'data/alert-banner.json'
import { SkipLinkContext } from 'contexts'
import SkipToMainContent from 'components/skip-to-main-content'
import usePostHogPageAnalytics from 'hooks/use-posthog-analytics'

// Local imports
import { BaseLayoutProps, AlertBannerProps } from './types'
import s from './base-layout.module.css'

const { ConsentManager, openConsentManager } = createConsentManager({
preset: 'oss',
otherServices: [...developerConsentManagerServices],
})

/**
Expand All @@ -53,6 +56,7 @@ const BaseLayout = ({
siteId: process.env.NEXT_PUBLIC_FATHOM_SITE_ID,
includedDomains: __config.dev_dot.analytics.included_domains,
})
usePostHogPageAnalytics()
useScrollPercentageAnalytics()
const [showSkipLink, setShowSkipLink] = useState(false)

Expand Down
25 changes: 25 additions & 0 deletions src/lib/consent-manager-services/developer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

// `options` are passed to `posthog.init()` in the `body` below.
const options = {
api_host: 'https://eu.i.posthog.com',
person_profiles: 'identified_only',
}

const developerConsentManagerServices = [
{
name: 'PostHog',
description:
'PostHog enables analytics, session replay, feature flags, etc. on our web properties',
category: 'Analytics',
async: true,
body: `!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep onSessionId setPersonProperties".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);posthog.init("${
process.env.POSTHOG_PROJECT_API_KEY
}", ${JSON.stringify(options)});`,
},
]

export default developerConsentManagerServices

0 comments on commit 47504d8

Please sign in to comment.