Skip to content

Commit 8e45f1c

Browse files
QuiltSimonsmkohnstammnl0claude
authored
catalog: add HubSpot tracking script support (#4807)
Co-authored-by: Simon Kohnstamm <kohnsts@gmail.com> Co-authored-by: Alexei Mochalov <nl_0@quiltdata.io> Co-authored-by: Alexei Mochalov <alexei.a.mochalov@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d0fd0dc commit 8e45f1c

6 files changed

Lines changed: 72 additions & 0 deletions

File tree

catalog/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ where verb is one of
1818

1919
## Changes
2020

21+
- [Added] HubSpot tracking ([#4807](https://github.com/quiltdata/quilt/pull/4807))
2122
- [Changed] Migrate `package-lock.json` from lockfileVersion 2 to 3 ([#4812](https://github.com/quiltdata/quilt/pull/4812))
2223
- [Changed] Replace `jsonpath` with `jsonpath-plus` for JSONPath evaluation ([#4811](https://github.com/quiltdata/quilt/pull/4811))
2324
- [Fixed] Fix "Error resolving revision" flash when navigating to a just-created package ([#4778](https://github.com/quiltdata/quilt/pull/4778))

catalog/app/app.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import 'sanitize.css'
2323

2424
// Import the rest of our modules
2525
import * as Assistant from 'components/Assistant'
26+
import HubSpot from 'components/HubSpot'
2627
import * as Intercom from 'components/Intercom'
2728
import Placeholder from 'components/Placeholder'
2829
import App from 'containers/App'
@@ -114,6 +115,7 @@ const render = () => {
114115
},
115116
],
116117
[Tracking.Provider, { userSelector: Auth.selectors.username }],
118+
HubSpot,
117119
AWS.Credentials.Provider,
118120
AWS.Config.Provider,
119121
AWS.Athena.Provider,

catalog/app/components/HubSpot.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as React from 'react'
2+
import * as redux from 'react-redux'
3+
import { useLocation } from 'react-router-dom'
4+
5+
import cfg from 'constants/config'
6+
import * as Auth from 'containers/Auth'
7+
import usePrevious from 'utils/usePrevious'
8+
9+
type HsqCommand =
10+
| ['setPath', string]
11+
| ['trackPageView']
12+
| ['identify', { email: string }]
13+
14+
function hsq(...cmd: HsqCommand[]) {
15+
// eslint-disable-next-line no-underscore-dangle
16+
const q = ((window as any)._hsq = (window as any)._hsq || [])
17+
cmd.forEach((c) => q.push(c))
18+
}
19+
20+
function HubSpotTracker() {
21+
const location = useLocation()
22+
const email: string | undefined = redux.useSelector(Auth.selectors.email)
23+
const path = `${location.pathname}${location.search}`
24+
25+
React.useEffect(() => {
26+
const script = document.createElement('script')
27+
script.type = 'text/javascript'
28+
script.id = 'hs-script-loader'
29+
script.async = true
30+
script.defer = true
31+
script.src = `https://js.hs-scripts.com/${cfg.hubspotId}.js`
32+
document.head.appendChild(script)
33+
return () => {
34+
script.remove()
35+
}
36+
}, [])
37+
38+
// Track SPA navigations (initial page load is auto-tracked by HubSpot)
39+
usePrevious(path, (prevPath) => {
40+
if (prevPath !== undefined && prevPath !== path) {
41+
hsq(['setPath', path], ['trackPageView'])
42+
}
43+
})
44+
45+
// Identify contact on sign-in
46+
usePrevious(email, (prevEmail) => {
47+
if (email && email !== prevEmail) {
48+
hsq(['identify', { email }], ['trackPageView'])
49+
}
50+
})
51+
52+
return null
53+
}
54+
55+
export default function HubSpot({ children }: { children?: React.ReactNode }) {
56+
if (!cfg.hubspotId) return <>{children}</>
57+
return (
58+
<>
59+
<HubSpotTracker />
60+
{children}
61+
</>
62+
)
63+
}

catalog/app/utils/Config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface ConfigJson {
2525
registryUrl: string
2626
s3Proxy: string
2727

28+
hubspotId?: string
2829
intercomAppId?: string
2930
mixpanelToken: string
3031
sentryDSN?: string

catalog/config-schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
"type": "string",
1414
"description": "Endpoint to use for previews and other things. Should be auto-populated by your stack."
1515
},
16+
"hubspotId": {
17+
"type": "string",
18+
"description": "HubSpot tracking ID for marketing analytics. If absent or empty, tracking is disabled."
19+
},
1620
"intercomAppId": {
1721
"type": "string",
1822
"description": "Connects orange chat icon to our Intercom. If absent, icon does not show."

catalog/config.json.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"alwaysRequiresAuth": ${ALWAYS_REQUIRE_AUTH},
55
"noDownload": ${NO_DOWNLOAD},
66
"s3Proxy": "${S3_PROXY_URL}",
7+
"hubspotId": "${HUBSPOT_ID}",
78
"intercomAppId": "${INTERCOM_APP_ID}",
89
"registryUrl": "${REGISTRY_URL}",
910
"passwordAuth": "${PASSWORD_AUTH}",

0 commit comments

Comments
 (0)