mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-11-09 13:01:56 -10:00
fix(frontend): load Google Analytics only when a measurement ID is set (#15748)
### Why / What / How **Why:** self-hosted installs report to our Google Analytics property by default. `.env.default` shipped our measurement ID and `layout.tsx` had it as a hardcoded fallback, so when someone running their own install accepted analytics in the cookie banner, their users' page views landed in our GA. This is part of the privacy project (goal: do it legally). **What:** the GA measurement ID now has no default. The Google tag only loads when a GA or Google Ads ID is set, so an install without either loads nothing. **How:** the ID comes from `NEXT_PUBLIC_GA_MEASUREMENT_ID` only (new `environment.getGAMeasurementID()`). `SetupAnalytics` skips the Google tag when there is no ID, and the init script leaves out the GA `config` call when only Google Ads is set. Linear: [SECRT-2714](https://linear.app/autogpt/issue/SECRT-2714) > [!IMPORTANT] > Vercel Production must have `NEXT_PUBLIC_GA_MEASUREMENT_ID` set (to `G-FH2XK2W4GN`) before this deploys, otherwise production GA stops. It is inlined at build time, so a redeploy is needed after changing it. ### Changes 🏗️ - `frontend/.env.default`: `NEXT_PUBLIC_GA_MEASUREMENT_ID` is empty, with a comment explaining it. - `frontend/src/app/layout.tsx`: removed the hardcoded `G-FH2XK2W4GN` fallback. - `frontend/src/services/environment/index.ts`: added `getGAMeasurementID()`. - `frontend/src/services/analytics/index.tsx`: no Google tag without a GA or Ads ID; no GA `config` call without a GA ID. - Tests in `consent-consumers.test.tsx` for: no IDs (no tag), Ads ID only (Ads config only), GA ID (GA config). - Configuration change: `NEXT_PUBLIC_GA_MEASUREMENT_ID` default changed from our ID to empty. No other place in the repo (docker-compose, docs, other env files) referenced the ID. ### Agents and large language models used - Claude Code — Claude Opus 5.5 ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `pnpm vitest run src/services/analytics` passes (new cases for no ID, Ads only, GA ID) - [x] `pnpm lint` and `pnpm types` pass - [ ] After deploy: Production GA still receives page views (needs the Vercel env var above) #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
9e09da8757
commit
7aa5db5817
5 files changed
+56
-10
No files matched your search
@@ -90,7 +90,11 @@ NEXT_PUBLIC_TOKEN_DEVTOOL=true
|
||||
# React render counter for `pnpm perf` (window.__renderCounter). Off unless
|
||||
# exactly "1"; see docs/engineering/frontend-perf-measurement.md.
|
||||
# NEXT_PUBLIC_RENDER_COUNTER=1
|
||||
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-FH2XK2W4GN
|
||||
|
||||
# Google Analytics 4 measurement ID (G-...). Leave empty unless you run your
|
||||
# own GA property: the Google tag only loads when this (or the Google Ads ID
|
||||
# below) is set. Inlined at build time.
|
||||
NEXT_PUBLIC_GA_MEASUREMENT_ID=
|
||||
|
||||
# Google Ads conversion tracking (production only). The account's tag ID and
|
||||
# one label per conversion action, e.g.
|
||||
|
||||
@@ -125,10 +125,7 @@ export default async function RootLayout({
|
||||
>
|
||||
<SetupAnalytics
|
||||
host={host}
|
||||
ga={{
|
||||
gaId:
|
||||
process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || "G-FH2XK2W4GN",
|
||||
}}
|
||||
ga={{ gaId: environment.getGAMeasurementID() }}
|
||||
/>
|
||||
<div className="flex min-h-screen flex-col items-stretch justify-items-stretch supports-[min-height:100dvh]:min-h-dvh">
|
||||
{children}
|
||||
|
||||
+39
-2
@@ -15,20 +15,21 @@ vi.mock("next/script", () => ({
|
||||
id,
|
||||
src,
|
||||
strategy,
|
||||
dangerouslySetInnerHTML: _inline,
|
||||
dangerouslySetInnerHTML: inline,
|
||||
onLoad: _onLoad,
|
||||
...attributes
|
||||
}: {
|
||||
id?: string;
|
||||
src?: string;
|
||||
strategy?: string;
|
||||
dangerouslySetInnerHTML?: unknown;
|
||||
dangerouslySetInnerHTML?: { __html?: string };
|
||||
onLoad?: unknown;
|
||||
}) => (
|
||||
<div
|
||||
data-testid={`script:${id ?? src}`}
|
||||
data-src={src}
|
||||
data-strategy={strategy}
|
||||
data-inline={inline?.__html}
|
||||
{...attributes}
|
||||
/>
|
||||
),
|
||||
@@ -160,6 +161,42 @@ describe("with NEXT_PUBLIC_COOKIEBOT_CBID", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Google tag IDs", () => {
|
||||
beforeEach(() => {
|
||||
configureCookiebot();
|
||||
installCookiebot({ statistics: true, marketing: true });
|
||||
});
|
||||
|
||||
it("loads no Google tag when neither a GA nor a Google Ads ID is set", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_GOOGLE_ADS_ID", "");
|
||||
|
||||
render(<SetupAnalytics host="platform.agpt.co" ga={{ gaId: "" }} />);
|
||||
|
||||
expect(queryScript("_custom-ga")).toBeNull();
|
||||
expect(queryScript("_custom-ga-init")).toBeNull();
|
||||
});
|
||||
|
||||
it("configures only Google Ads when the GA ID is not set", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_GOOGLE_ADS_ID", "AW-TEST");
|
||||
|
||||
render(<SetupAnalytics host="platform.agpt.co" ga={{ gaId: "" }} />);
|
||||
|
||||
const init = queryScript("_custom-ga-init")?.getAttribute("data-inline");
|
||||
expect(queryScript("_custom-ga")).not.toBeNull();
|
||||
expect(init).toContain(`gtag('config', "AW-TEST"`);
|
||||
expect(init).not.toContain(`gtag('config', ""`);
|
||||
});
|
||||
|
||||
it("configures the GA property it is given", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_GOOGLE_ADS_ID", "");
|
||||
|
||||
render(<SetupAnalytics host="platform.agpt.co" ga={GA} />);
|
||||
|
||||
const init = queryScript("_custom-ga-init")?.getAttribute("data-inline");
|
||||
expect(init).toContain(`gtag('config', "G-TEST"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ConsentWithdrawalReload", () => {
|
||||
const reload = vi.fn<() => void>();
|
||||
|
||||
|
||||
@@ -35,8 +35,9 @@ export function SetupAnalytics(props: SetupProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Google tag: GA4 + Google Ads */}
|
||||
{googleTagEnabled ? (
|
||||
{/* Google tag: GA4 + Google Ads. Nothing to load without an ID: a
|
||||
self-hosted install must not report to anyone else's property */}
|
||||
{googleTagEnabled && (gaId || adsID) ? (
|
||||
<>
|
||||
<GoogleConsentModeSync />
|
||||
<Script
|
||||
@@ -94,7 +95,9 @@ function buildGoogleTagInitScript({
|
||||
`window['${DATA_LAYER_NAME}'] = window['${DATA_LAYER_NAME}'] || [];`,
|
||||
`function gtag(){window['${DATA_LAYER_NAME}'].push(arguments);}`,
|
||||
`gtag('js', new Date());`,
|
||||
`gtag('config', ${JSON.stringify(GAID)}${debugMode ? ", { 'debug_mode': true }" : ""});`,
|
||||
GAID
|
||||
? `gtag('config', ${JSON.stringify(GAID)}${debugMode ? ", { 'debug_mode': true }" : ""});`
|
||||
: "",
|
||||
adsID
|
||||
? `gtag('config', ${JSON.stringify(adsID)}, { 'allow_enhanced_conversions': true });`
|
||||
: "",
|
||||
|
||||
@@ -87,6 +87,10 @@ function getLaunchDarklyClientId() {
|
||||
return process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID;
|
||||
}
|
||||
|
||||
function getGAMeasurementID() {
|
||||
return process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID?.trim() || "";
|
||||
}
|
||||
|
||||
function getGoogleAdsID() {
|
||||
return process.env.NEXT_PUBLIC_GOOGLE_ADS_ID || "";
|
||||
}
|
||||
@@ -195,6 +199,7 @@ export const environment = {
|
||||
getRevision,
|
||||
getPostHogCredentials,
|
||||
getLaunchDarklyClientId,
|
||||
getGAMeasurementID,
|
||||
getGoogleAdsID,
|
||||
getGoogleAdsConversionLabels,
|
||||
getCookiebotCBID,
|
||||
|
||||
Reference in new issue
Block a user