From 9b63f71a0ecf7001849d2bea5b6afc4264c143fc Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Thu, 23 Jul 2026 08:14:50 +0100 Subject: [PATCH] feat(inbox): add Google Search Console as a self-driving source Registers Google Search Console in the shared EXTERNAL_INBOX_SOURCES registry so the toggle card, source filter, and source-product unions pick it up. Adds a search_opportunity record kind (append-only incremental sync, like tickets) and a magnifying-glass icon. The generic DynamicSourceSetup already handles the source's OAuth grant and property picker, so no bespoke setup form is needed. Depends on the posthog/posthog backend PR shipping the google_search_console SignalSourceProduct choice first (the toggle create call rejects an unknown source_product with a 400). --- packages/shared/src/inbox-types.ts | 22 ++++++++++++++++--- .../components/utils/source-product-icons.tsx | 6 +++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/inbox-types.ts b/packages/shared/src/inbox-types.ts index f451fbe515..89501ced1c 100644 --- a/packages/shared/src/inbox-types.ts +++ b/packages/shared/src/inbox-types.ts @@ -11,7 +11,8 @@ export type SignalRecordKind = | "ticket" | "scanner_finding" | "feedback" - | "review"; + | "review" + | "search_opportunity"; /** * A warehouse data source the Self-driving inbox can watch. This is the single source of @@ -48,6 +49,7 @@ const ERROR = "Surface new and reopened errors"; const FINDING = "Surface new security and code-quality findings"; const FEEDBACK = "Turn product feedback and feature requests into inputs"; const REVIEW = "Monitor new app and product reviews"; +const SEARCH = "Fix pages that rank in Google but lose clicks"; /** Registry of warehouse-backed inbox sources, alphabetical within each category. */ export const EXTERNAL_INBOX_SOURCES = [ @@ -392,6 +394,16 @@ export const EXTERNAL_INBOX_SOURCES = [ recordKind: "review", setup: "dynamic", }, + // Search analytics + { + product: "google_search_console", + label: "Google Search Console", + description: SEARCH, + dwSourceType: "GoogleSearchConsole", + requiredTables: ["search_analytics_by_query_page"], + recordKind: "search_opportunity", + setup: "dynamic", + }, ] as const satisfies readonly ExternalInboxSource[]; /** Warehouse-backed source products, derived from the registry above. */ @@ -434,9 +446,13 @@ export type SourceType = | "session_analysis_cluster" | SignalRecordKind; -/** Issue-like records mutate (status/votes change), so their table needs full-refresh sync. */ +/** + * Issue-like records mutate (status/votes change), so their table needs full-refresh sync. + * Tickets and search-analytics rows are append-only — existing rows never change once written — + * so they sync incrementally. + */ export function sourceNeedsFullRefresh(recordKind: SignalRecordKind): boolean { - return recordKind !== "ticket"; + return recordKind !== "ticket" && recordKind !== "search_opportunity"; } export const EXTERNAL_INBOX_SOURCE_BY_PRODUCT: Partial< diff --git a/packages/ui/src/features/inbox/components/utils/source-product-icons.tsx b/packages/ui/src/features/inbox/components/utils/source-product-icons.tsx index a480c99c3c..a30a065c50 100644 --- a/packages/ui/src/features/inbox/components/utils/source-product-icons.tsx +++ b/packages/ui/src/features/inbox/components/utils/source-product-icons.tsx @@ -10,6 +10,7 @@ import { KanbanIcon, LifebuoyIcon, LightbulbIcon, + MagnifyingGlassIcon, MegaphoneIcon, ShieldIcon, StarIcon, @@ -178,4 +179,9 @@ export const SOURCE_PRODUCT_META: Partial< }, intercom: { Icon: ChatsIcon, color: "var(--blue-9)", label: "Intercom" }, hubspot: { Icon: LifebuoyIcon, color: "var(--orange-9)", label: "HubSpot" }, + google_search_console: { + Icon: MagnifyingGlassIcon, + color: "var(--sky-9)", + label: "Google Search Console", + }, };