diff --git a/apps/mobile/src/features/inbox/components/FilterSheet.tsx b/apps/mobile/src/features/inbox/components/FilterSheet.tsx index 5243c67648..f9109ac16a 100644 --- a/apps/mobile/src/features/inbox/components/FilterSheet.tsx +++ b/apps/mobile/src/features/inbox/components/FilterSheet.tsx @@ -77,6 +77,7 @@ const SOURCE_PRODUCT_OPTIONS: { value: SourceProduct; label: string }[] = [ { value: "zendesk", label: "Zendesk" }, { value: "conversations", label: "Conversations" }, { value: "signals_scout", label: "Scout" }, + { value: "health_checks", label: "Health checks" }, ]; function SectionHeader({ title }: { title: string }) { diff --git a/apps/mobile/src/features/inbox/components/SignalCard.tsx b/apps/mobile/src/features/inbox/components/SignalCard.tsx index a32ff002d1..2027494bcb 100644 --- a/apps/mobile/src/features/inbox/components/SignalCard.tsx +++ b/apps/mobile/src/features/inbox/components/SignalCard.tsx @@ -9,6 +9,7 @@ import { Code, Compass, GithubLogo, + Heartbeat, LinkSimple, Question, Robot, @@ -53,6 +54,8 @@ function sourceLine(signal: Signal): string { ) return "Scout · Cross-source issue"; if (source_product === "signals_scout") return "Scout"; + if (source_product === "health_checks" && source_type === "health_issue") + return "Health checks · Issue"; const product = source_product.replace(/_/g, " "); const type = source_type.replace(/_/g, " "); return `${product} · ${type}`; @@ -82,6 +85,8 @@ function SourceIcon({ return ; case "signals_scout": return ; + case "health_checks": + return ; default: return ; } diff --git a/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts b/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts index ea363b4e42..ed4bd36da7 100644 --- a/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts +++ b/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts @@ -22,7 +22,8 @@ export type SourceProduct = | "linear" | "zendesk" | "conversations" - | "signals_scout"; + | "signals_scout" + | "health_checks"; export const DEFAULT_STATUS_FILTER: SignalReportStatus[] = [ "ready", diff --git a/packages/api-client/src/posthog-client.ts b/packages/api-client/src/posthog-client.ts index e4e76e63eb..4ea049d8c7 100644 --- a/packages/api-client/src/posthog-client.ts +++ b/packages/api-client/src/posthog-client.ts @@ -201,7 +201,8 @@ export interface SignalSourceConfig { | "conversations" | "error_tracking" | "pganalyze" - | "signals_scout"; + | "signals_scout" + | "health_checks"; source_type: | "session_analysis_cluster" | "evaluation" @@ -210,7 +211,8 @@ export interface SignalSourceConfig { | "issue_created" | "issue_reopened" | "issue_spiking" - | "cross_source_issue"; + | "cross_source_issue" + | "health_issue"; enabled: boolean; config: Record; created_at: string; diff --git a/packages/core/src/inbox/signalSourceService.test.ts b/packages/core/src/inbox/signalSourceService.test.ts index ff06734571..0844cdf2d7 100644 --- a/packages/core/src/inbox/signalSourceService.test.ts +++ b/packages/core/src/inbox/signalSourceService.test.ts @@ -60,6 +60,13 @@ describe("computeSourceValues", () => { const values = computeSourceValues([config("github", "issue", true)]); expect(values.github).toBe(true); }); + + it("enables health_checks when its config is enabled", () => { + const values = computeSourceValues([ + config("health_checks", "health_issue", true), + ]); + expect(values.health_checks).toBe(true); + }); }); describe("deriveSourceStates", () => { @@ -106,6 +113,17 @@ describe("SignalSourceService.toggleSource", () => { expect(client.createSignalSourceConfig).toHaveBeenCalledTimes(1); }); + it("creates a health_checks config with the health_issue source type", async () => { + const client = fakeClient(); + const service = new SignalSourceService(); + await service.toggleSource(client, 1, "health_checks", true, [], []); + expect(client.createSignalSourceConfig).toHaveBeenCalledWith(1, { + source_product: "health_checks", + source_type: "health_issue", + enabled: true, + }); + }); + it("ensures the issues table syncs with full_refresh for github before enabling", async () => { const client = fakeClient(); const service = new SignalSourceService(); diff --git a/packages/core/src/inbox/signalSourceService.ts b/packages/core/src/inbox/signalSourceService.ts index 7b180808fb..26e39b358a 100644 --- a/packages/core/src/inbox/signalSourceService.ts +++ b/packages/core/src/inbox/signalSourceService.ts @@ -15,6 +15,7 @@ export interface SignalSourceValues { zendesk: boolean; conversations: boolean; pganalyze: boolean; + health_checks: boolean; } export type SignalSourceProduct = keyof SignalSourceValues; @@ -48,6 +49,7 @@ const SOURCE_TYPE_MAP: Record< zendesk: "ticket", conversations: "ticket", pganalyze: "issue", + health_checks: "health_issue", }; const ERROR_TRACKING_SOURCE_TYPES: SourceType[] = [ @@ -74,6 +76,7 @@ const ALL_SOURCE_PRODUCTS: SignalSourceProduct[] = [ "zendesk", "conversations", "pganalyze", + "health_checks", ]; function isWarehouseSource( @@ -109,6 +112,7 @@ export function computeSourceValues( zendesk: false, conversations: false, pganalyze: false, + health_checks: false, }; if (!configs?.length) { return result; diff --git a/packages/shared/src/analytics-events.ts b/packages/shared/src/analytics-events.ts index c1e875f563..e7f61903da 100644 --- a/packages/shared/src/analytics-events.ts +++ b/packages/shared/src/analytics-events.ts @@ -731,7 +731,8 @@ export interface SignalSourceConnectedProperties { | "zendesk" | "conversations" | "pganalyze" - | "llm_analytics"; + | "llm_analytics" + | "health_checks"; /** True when this is a brand-new createSignalSourceConfig, false for re-enable of an existing config. */ is_first_connection: boolean; /** True when the connection went through the DataSourceSetup wizard (warehouse OAuth path). */ diff --git a/packages/shared/src/inbox-types.ts b/packages/shared/src/inbox-types.ts index 578c1b2eb9..1a8a959268 100644 --- a/packages/shared/src/inbox-types.ts +++ b/packages/shared/src/inbox-types.ts @@ -14,4 +14,5 @@ export type SourceProduct = | "zendesk" | "conversations" | "pganalyze" - | "signals_scout"; + | "signals_scout" + | "health_checks"; diff --git a/packages/ui/src/features/inbox/components/SignalSourceToggles.tsx b/packages/ui/src/features/inbox/components/SignalSourceToggles.tsx index ad20f2904f..15b9e86e03 100644 --- a/packages/ui/src/features/inbox/components/SignalSourceToggles.tsx +++ b/packages/ui/src/features/inbox/components/SignalSourceToggles.tsx @@ -6,6 +6,7 @@ import { CircleNotchIcon, CompassIcon, GithubLogoIcon, + HeartbeatIcon, KanbanIcon, TicketIcon, VideoIcon, @@ -26,6 +27,7 @@ export interface SignalSourceValues { zendesk: boolean; conversations: boolean; pganalyze: boolean; + health_checks: boolean; } interface SignalSourceToggleCardProps { @@ -300,6 +302,10 @@ export function SignalSourceToggles({ (checked: boolean) => onToggle("conversations", checked), [onToggle], ); + const toggleHealthChecks = useCallback( + (checked: boolean) => onToggle("health_checks", checked), + [onToggle], + ); const toggleScouts = useCallback( (checked: boolean) => onToggle("signals_scout", checked), [onToggle], @@ -332,6 +338,17 @@ export function SignalSourceToggles({ docsUrl="https://posthog.com/docs/error-tracking" docsLabel="Error Tracking" /> + } + label="Health checks" + description="Recommended — detects problems with your PostHog integration (missing events, proxy gaps, outdated SDKs) and automatically suggests fixes." + checked={value.health_checks} + onCheckedChange={toggleHealthChecks} + disabled={disabled} + syncStatus={sourceStates?.health_checks?.syncStatus} + docsUrl="https://posthog.com/docs/sdk-health" + docsLabel="Health checks" + /> } label="Support" diff --git a/packages/ui/src/features/inbox/components/detail/SignalCard.tsx b/packages/ui/src/features/inbox/components/detail/SignalCard.tsx index 9ee7609100..4b34728209 100644 --- a/packages/ui/src/features/inbox/components/detail/SignalCard.tsx +++ b/packages/ui/src/features/inbox/components/detail/SignalCard.tsx @@ -86,6 +86,9 @@ function signalCardSourceLine(signal: { if (source_product === "pganalyze" && source_type === "issue") { return "pganalyze · Issue"; } + if (source_product === "health_checks" && source_type === "health_issue") { + return "Health checks · Issue"; + } if ( source_product === "signals_scout" && source_type === "cross_source_issue" diff --git a/packages/ui/src/features/inbox/components/responderAgentMeta.ts b/packages/ui/src/features/inbox/components/responderAgentMeta.ts index bddd18fbf5..5db40cf6cd 100644 --- a/packages/ui/src/features/inbox/components/responderAgentMeta.ts +++ b/packages/ui/src/features/inbox/components/responderAgentMeta.ts @@ -30,6 +30,15 @@ export const RESPONDER_AGENT_GROUPS: ResponderAgentGroup[] = [ docsUrl: "https://posthog.com/docs/error-tracking", docsLabel: "Error Tracking", }, + { + source: "health_checks", + sourceProduct: "health_checks", + label: "Health checks", + description: + "Recommended — detects problems with your PostHog integration and suggests fixes automatically.", + docsUrl: "https://posthog.com/docs/sdk-health", + docsLabel: "Health checks", + }, { source: "conversations", sourceProduct: "conversations", 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 248ef1d926..ff768c6c2e 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 @@ -4,6 +4,7 @@ import { BugIcon, CompassIcon, GithubLogoIcon, + HeartbeatIcon, KanbanIcon, LifebuoyIcon, TicketIcon, @@ -95,4 +96,9 @@ export const SOURCE_PRODUCT_META: Partial< color: "var(--iris-9)", label: "Scout", }, + health_checks: { + Icon: HeartbeatIcon, + color: "var(--crimson-9)", + label: "Health checks", + }, }; diff --git a/packages/ui/src/features/inbox/filterOptions.tsx b/packages/ui/src/features/inbox/filterOptions.tsx index 691169be53..363b3c12dc 100644 --- a/packages/ui/src/features/inbox/filterOptions.tsx +++ b/packages/ui/src/features/inbox/filterOptions.tsx @@ -5,6 +5,7 @@ import { Clock, CompassIcon, GithubLogoIcon, + HeartbeatIcon, KanbanIcon, LifebuoyIcon, ListNumbers, @@ -101,6 +102,11 @@ export const INBOX_SOURCE_OPTIONS: { }, { value: "pganalyze", label: "pganalyze", icon: }, { value: "signals_scout", label: "Scouts", icon: }, + { + value: "health_checks", + label: "Health checks", + icon: , + }, ]; export function inboxSortOptionKey( diff --git a/packages/ui/src/features/inbox/hooks/useSignalSourceToggles.ts b/packages/ui/src/features/inbox/hooks/useSignalSourceToggles.ts index 9ea49ee860..06594c83c0 100644 --- a/packages/ui/src/features/inbox/hooks/useSignalSourceToggles.ts +++ b/packages/ui/src/features/inbox/hooks/useSignalSourceToggles.ts @@ -25,6 +25,7 @@ const SOURCE_TYPE_MAP: Record< zendesk: "ticket", conversations: "ticket", pganalyze: "issue", + health_checks: "health_issue", }; const ERROR_TRACKING_SOURCE_TYPES: SourceType[] = [ @@ -42,6 +43,7 @@ const SOURCE_LABELS: Record = { zendesk: "Zendesk Tickets", conversations: "PostHog Support", pganalyze: "pganalyze", + health_checks: "Health checks", }; const DATA_WAREHOUSE_SOURCES: Record< @@ -63,6 +65,7 @@ const ALL_SOURCE_PRODUCTS: (keyof SignalSourceValues)[] = [ "zendesk", "conversations", "pganalyze", + "health_checks", ]; function isSetupSourceProduct( @@ -83,6 +86,7 @@ function computeValues( zendesk: false, conversations: false, pganalyze: false, + health_checks: false, }; if (!configs?.length) return result; for (const product of ALL_SOURCE_PRODUCTS) {