Skip to content

fix(ga4): GA4 sidebar shows generic error for all customers [ES-433]#11065

Merged
Tyler (tylerwashington888) merged 3 commits into
masterfrom
fix/ga4-zod-schema-optional-proto3-fields
Jul 10, 2026
Merged

fix(ga4): GA4 sidebar shows generic error for all customers [ES-433]#11065
Tyler (tylerwashington888) merged 3 commits into
masterfrom
fix/ga4-zod-schema-optional-proto3-fields

Conversation

@tylerwashington888

@tylerwashington888 Tyler (tylerwashington888) commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

Since June 30, every customer using the Google Analytics 4 sidebar sees "We couldn't load Google Analytics data" even though the lambda is returning valid data.

What's happening end-to-end

  1. The GA4 sidebar calls the lambda's /api/run_report endpoint via a signed request
  2. The lambda fetches data from Google's Analytics Data API (gRPC/Protobuf) and returns a 200 with valid rows
  3. The frontend runs the response through a Zod schema (ZRunReportData) before rendering
  4. The schema fails → throws MalformedApiResponse error type → ErrorDisplay has no handler for it → falls to default → shows the generic message

Why the schema fails

PR #11042 introduced ZRunReportData with this metadata shape:

metadata: z.object({
  currencyCode: z.string(),      // required
  dataLossFromOtherRow: z.boolean(),
  timeZone: z.string(),          // required
  _currencyCode: z.string(),     // required — THIS IS THE PROBLEM
  _timeZone: z.string(),         // required — THIS IS THE PROBLEM
}),
propertyQuota: z.null(),

_currencyCode and _timeZone are internal protobuf/protobufjs implementation details — they exist on the in-memory JS object that protobufjs produces when decoding the binary gRPC response, but they are undefined and get silently dropped when the lambda calls res.json(). They are never part of the actual JSON payload the frontend receives. The schema was written by inspecting the raw JS object rather than the actual API contract.

The actual JSON the lambda sends looks like:

"metadata": {
  "dataLossFromOtherRow": false,
  "currencyCode": "USD",
  "timeZone": "America/New_York"
}

_currencyCode and _timeZone are never there. z.string() on a missing field fails for every single customer.

Additionally, propertyQuota: z.null() would fail for any high-traffic property where Google returns a QuotaStatus object instead of null.

Why only reported for some customers

The Zod failure affects all customers — the "3 customers" in the report are just the ones who surfaced it. The bug has been live since June 30.

Fix

Make the fields that are never actually present optional, and widen propertyQuota to accept any value:

metadata: z.object({
  currencyCode: z.string().optional(),     // absent for non-ecommerce properties
  dataLossFromOtherRow: z.boolean(),
  timeZone: z.string().optional(),
  _currencyCode: z.string().optional(),    // protobuf internal — never in JSON
  _timeZone: z.string().optional(),        // protobuf internal — never in JSON
}),
propertyQuota: z.unknown(),               // null normally, object when quota is tracked

The frontend only reads rows and rowCount from the response — none of the loosened fields are accessed anywhere in UI code.

Test plan

  • Open the GA4 sidebar on any entry — analytics data should render instead of the generic error
  • Confirm properties with and without currency tracking both work
  • npm run test:ci in apps/google-analytics-4/frontend passes

🤖 Generated with Claude Code on behalf of Tyler

useCMA() from react-apps-toolkit internally calls createClient() from
contentful-management, which can fail due to CJS/ESM interop issues
introduced in newer app-sdk versions. Mirrors the fix applied to
AssignContentTypeSection in the config screen — use sdk.cma directly
since app-sdk >= 4.20 exposes it on the SDK instance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… [ES-433]

currencyCode and timeZone in ResponseMetaData are optional proto3 fields.
When not set (e.g. non-ecommerce GA4 properties), protobufjs binary decode
produces undefined for both the field value and the oneof discriminator
(_currencyCode / _timeZone). JSON.stringify drops undefined keys entirely,
so the frontend receives a JSON payload where those fields are absent.

The previous strict z.string() on these fields caused ZodError for any
property without currency tracking → malformedApiResponse error type →
generic "We couldn't load Google Analytics data" message.

Also widens propertyQuota from z.null() to z.unknown() since high-traffic
properties return a QuotaStatus object rather than null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tylerwashington888 Tyler (tylerwashington888) changed the title fix(ga4): loosen ZRunReportData Zod schema for optional proto3 fields [ES-433] fix(ga4): GA4 sidebar shows generic error for all customers [ES-433] Jul 10, 2026
rowCount: z.number(),
metadata: z.object({
currencyCode: z.string(),
// currencyCode is an optional proto3 field — absent from JSON when not set (non-ecommerce properties)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment

_currencyCode: z.string(),
_timeZone: z.string(),
timeZone: z.string().optional(),
// _currencyCode/_timeZone are protobuf oneof discriminators — absent from JSON when field is not set

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment

_timeZone: z.string().optional(),
}),
propertyQuota: z.null(),
// null when quota tracking is disabled; object when property has quota data

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment

@ryunsong-contentful ryunsong-contentful left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make code changes in the comments before merging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all three inline comments per your feedback. No other changes.

@ryunsong-contentful ryunsong-contentful left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ship it

@tylerwashington888 Tyler (tylerwashington888) merged commit 18d401c into master Jul 10, 2026
14 checks passed
@tylerwashington888 Tyler (tylerwashington888) deleted the fix/ga4-zod-schema-optional-proto3-fields branch July 10, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants