diff --git a/apps/mobile/src/features/inbox/components/FilterSheet.test.ts b/apps/mobile/src/features/inbox/components/FilterSheet.test.ts new file mode 100644 index 0000000000..3a9749a7a4 --- /dev/null +++ b/apps/mobile/src/features/inbox/components/FilterSheet.test.ts @@ -0,0 +1,18 @@ +import { EXTERNAL_INBOX_SOURCES } from "@posthog/shared"; +import { describe, expect, it } from "vitest"; +import { SOURCE_PRODUCT_OPTIONS } from "./FilterSheet"; + +describe("SOURCE_PRODUCT_OPTIONS", () => { + it("includes every warehouse-backed source from the shared registry", () => { + const values = new Set(SOURCE_PRODUCT_OPTIONS.map((o) => o.value)); + for (const source of EXTERNAL_INBOX_SOURCES) { + expect(values.has(source.product)).toBe(true); + } + }); + + it("keeps the native products", () => { + const values = SOURCE_PRODUCT_OPTIONS.map((o) => o.value); + expect(values).toContain("session_replay"); + expect(values).toContain("signals_scout"); + }); +}); diff --git a/apps/mobile/src/features/inbox/components/FilterSheet.tsx b/apps/mobile/src/features/inbox/components/FilterSheet.tsx index 5ad7d2cce3..11ed58e5af 100644 --- a/apps/mobile/src/features/inbox/components/FilterSheet.tsx +++ b/apps/mobile/src/features/inbox/components/FilterSheet.tsx @@ -1,4 +1,5 @@ import { Text } from "@components/text"; +import { EXTERNAL_INBOX_SOURCES } from "@posthog/shared"; import { Check } from "phosphor-react-native"; import { Modal, Pressable, ScrollView, View } from "react-native"; import { useScreenInsets } from "@/hooks/useScreenInsets"; @@ -68,17 +69,19 @@ function usePriorityDotColors(): Record { }; } -const SOURCE_PRODUCT_OPTIONS: { value: SourceProduct; label: string }[] = [ - { value: "session_replay", label: "Session replay" }, - { value: "error_tracking", label: "Error tracking" }, - { value: "llm_analytics", label: "AI observability" }, - { value: "github", label: "GitHub" }, - { value: "linear", label: "Linear" }, - { value: "zendesk", label: "Zendesk" }, - { value: "conversations", label: "Conversations" }, - { value: "signals_scout", label: "Scout" }, - { value: "health_checks", label: "Health checks" }, -]; +export const SOURCE_PRODUCT_OPTIONS: { value: SourceProduct; label: string }[] = + [ + { value: "session_replay", label: "Session replay" }, + { value: "error_tracking", label: "Error tracking" }, + { value: "llm_analytics", label: "AI observability" }, + { value: "conversations", label: "Conversations" }, + { value: "signals_scout", label: "Scout" }, + { value: "health_checks", label: "Health checks" }, + ...EXTERNAL_INBOX_SOURCES.map((source) => ({ + value: source.product, + label: source.label, + })), + ]; function SectionHeader({ title }: { title: string }) { return ( diff --git a/apps/mobile/src/features/inbox/components/SignalCard.tsx b/apps/mobile/src/features/inbox/components/SignalCard.tsx index 0bf1a595f8..74624989b3 100644 --- a/apps/mobile/src/features/inbox/components/SignalCard.tsx +++ b/apps/mobile/src/features/inbox/components/SignalCard.tsx @@ -1,4 +1,8 @@ import { Text } from "@components/text"; +import { + EXTERNAL_INBOX_SOURCE_BY_PRODUCT, + type SourceProduct, +} from "@posthog/shared"; import { ArrowSquareOut, Bug, @@ -11,6 +15,7 @@ import { FirstAid, GithubLogo, LinkSimple, + Plug, Question, Robot, WarningCircle, @@ -22,45 +27,10 @@ import { formatRelativeTime } from "@/lib/format"; import { openExternalUrl } from "@/lib/openExternalUrl"; import { useThemeColors } from "@/lib/theme"; import type { Signal, SignalFindingContent } from "../types"; +import { sourceLine } from "../utils"; const COLLAPSE_THRESHOLD = 280; -const ERROR_TRACKING_TYPE_LABELS: Record = { - issue_created: "New issue", - issue_reopened: "Issue reopened", - issue_spiking: "Volume spike", -}; - -function sourceLine(signal: Signal): string { - const { source_product, source_type } = signal; - if (source_product === "error_tracking") { - const label = - ERROR_TRACKING_TYPE_LABELS[source_type] ?? source_type.replace(/_/g, " "); - return `Error tracking · ${label}`; - } - if (source_product === "session_replay" && source_type === "session_problem") - return "Session replay · Session problem"; - if (source_product === "llm_analytics" && source_type === "evaluation") - return "AI observability · Evaluation"; - if (source_product === "zendesk" && source_type === "ticket") - return "Zendesk · Ticket"; - if (source_product === "github" && source_type === "issue") - return "GitHub · Issue"; - if (source_product === "linear" && source_type === "issue") - return "Linear · Issue"; - if ( - source_product === "signals_scout" && - source_type === "cross_source_issue" - ) - 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}`; -} - function SourceIcon({ product, size = 14, @@ -88,6 +58,8 @@ function SourceIcon({ case "health_checks": return ; default: + if (EXTERNAL_INBOX_SOURCE_BY_PRODUCT[product as SourceProduct]) + return ; return ; } } diff --git a/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts b/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts index 3875b8f56d..935407c640 100644 --- a/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts +++ b/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts @@ -15,7 +15,7 @@ describe("inboxFilterStore", () => { useInboxFilterStore.getState().resetFilters(); }); - it.each(["signals_scout", "error_tracking", "github"])( + it.each(["signals_scout", "error_tracking", "sentry"])( "toggles %s in and out of the source filter", (source) => { const { toggleSourceProduct } = useInboxFilterStore.getState(); diff --git a/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts b/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts index acad97721c..97c16ccc3f 100644 --- a/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts +++ b/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts @@ -1,3 +1,4 @@ +import type { SourceProduct } from "@posthog/shared"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { create } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; @@ -14,16 +15,7 @@ type SortField = Extract< type SortDirection = "asc" | "desc"; -export type SourceProduct = - | "conversations" - | "error_tracking" - | "github" - | "health_checks" - | "linear" - | "llm_analytics" - | "session_replay" - | "signals_scout" - | "zendesk"; +export type { SourceProduct }; export const DEFAULT_STATUS_FILTER: SignalReportStatus[] = [ "ready", diff --git a/apps/mobile/src/features/inbox/utils.test.ts b/apps/mobile/src/features/inbox/utils.test.ts index d9012c3479..8c1d70a32e 100644 --- a/apps/mobile/src/features/inbox/utils.test.ts +++ b/apps/mobile/src/features/inbox/utils.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import type { AvailableSuggestedReviewer, + Signal, SignalReport, SignalReportOrderingField, SignalReportStatus, @@ -17,9 +18,23 @@ import { isRestorableReport, orderSuggestedReviewers, reviewerMatchesAvailable, + sourceLine, toSuggestedReviewerWriteContent, } from "./utils"; +function signal(source_product: string, source_type: string): Signal { + return { + signal_id: "s1", + content: "", + source_product, + source_type, + source_id: "id", + weight: 1, + timestamp: "", + extra: {}, + }; +} + function reviewer(login: string, uuid?: string): SuggestedReviewer { return { github_login: login, @@ -438,6 +453,24 @@ describe("dismissalReasonLabel", () => { }); }); +describe("sourceLine", () => { + it.each([ + { + product: "error_tracking", + type: "issue_created", + expected: "Error tracking · New issue", + }, + { product: "sentry", type: "issue", expected: "Sentry · issue" }, + { + product: "mystery_source", + type: "thing", + expected: "mystery source · thing", + }, + ])("labels $product", ({ product, type, expected }) => { + expect(sourceLine(signal(product, type))).toBe(expected); + }); +}); + describe("buildReviewerOptions", () => { it("dedupes by uuid and pins the current user first", () => { const options = buildReviewerOptions( diff --git a/apps/mobile/src/features/inbox/utils.ts b/apps/mobile/src/features/inbox/utils.ts index bac0a33aff..52ffb73972 100644 --- a/apps/mobile/src/features/inbox/utils.ts +++ b/apps/mobile/src/features/inbox/utils.ts @@ -1,8 +1,13 @@ +import { + EXTERNAL_INBOX_SOURCE_BY_PRODUCT, + type SourceProduct, +} from "@posthog/shared"; import { differenceInHours, format, formatDistanceToNow } from "date-fns"; import type { InboxViewedProperties } from "@/lib/analytics"; import { DISMISSAL_REASON_OPTIONS } from "./constants"; import type { AvailableSuggestedReviewer, + Signal, SignalReport, SignalReportOrderingField, SignalReportPriority, @@ -11,6 +16,44 @@ import type { SuggestedReviewerWriteEntry, } from "./types"; +const ERROR_TRACKING_TYPE_LABELS: Record = { + issue_created: "New issue", + issue_reopened: "Issue reopened", + issue_spiking: "Volume spike", +}; + +export function sourceLine(signal: Signal): string { + const { source_product, source_type } = signal; + if (source_product === "error_tracking") { + const label = + ERROR_TRACKING_TYPE_LABELS[source_type] ?? source_type.replace(/_/g, " "); + return `Error tracking · ${label}`; + } + if (source_product === "session_replay" && source_type === "session_problem") + return "Session replay · Session problem"; + if (source_product === "llm_analytics" && source_type === "evaluation") + return "AI observability · Evaluation"; + if (source_product === "zendesk" && source_type === "ticket") + return "Zendesk · Ticket"; + if (source_product === "github" && source_type === "issue") + return "GitHub · Issue"; + if (source_product === "linear" && source_type === "issue") + return "Linear · Issue"; + if ( + source_product === "signals_scout" && + source_type === "cross_source_issue" + ) + 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 warehouseSource = + EXTERNAL_INBOX_SOURCE_BY_PRODUCT[source_product as SourceProduct]; + const product = warehouseSource?.label ?? source_product.replace(/_/g, " "); + const type = source_type.replace(/_/g, " "); + return `${product} · ${type}`; +} + const SIGNAL_SUMMARY_SECTION_HEADERS = [ "What's happening", "Root cause",