Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit c61baa1

Browse files
authored
feat(mobile): show warehouse-backed sources in inbox Source filter
Port PostHog/code #3597 to mobile: the inbox Source filter and signal cards only handled ~9 native products, so signals from warehouse-backed sources (Stripe, Sentry, Zendesk, etc.) could not be filtered by source and rendered with a crude fallback label and no icon. - Derive the Source filter's warehouse entries from the shared EXTERNAL_INBOX_SOURCES registry instead of a hardcoded list. - Give warehouse sources a registry label and a generic Plug icon fallback in SignalCard (no per-source mobile icon assets, matching desktop's PlugIcon fallback). - Re-export the shared SourceProduct union from the mobile filter store so the wider set typechecks. - Move the pure sourceLine helper into the inbox utils and cover it, the widened source filter, and the derived options list with tests. Generated-By: PostHog Code Task-Id: 4fcd2e1a-e78a-4ca8-b939-fd728279884d
1 parent d2afabe commit c61baa1

7 files changed

Lines changed: 119 additions & 58 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { EXTERNAL_INBOX_SOURCES } from "@posthog/shared";
2+
import { describe, expect, it } from "vitest";
3+
import { SOURCE_PRODUCT_OPTIONS } from "./FilterSheet";
4+
5+
describe("SOURCE_PRODUCT_OPTIONS", () => {
6+
it("includes every warehouse-backed source from the shared registry", () => {
7+
const values = new Set(SOURCE_PRODUCT_OPTIONS.map((o) => o.value));
8+
for (const source of EXTERNAL_INBOX_SOURCES) {
9+
expect(values.has(source.product)).toBe(true);
10+
}
11+
});
12+
13+
it("keeps the native products", () => {
14+
const values = SOURCE_PRODUCT_OPTIONS.map((o) => o.value);
15+
expect(values).toContain("session_replay");
16+
expect(values).toContain("signals_scout");
17+
});
18+
});

apps/mobile/src/features/inbox/components/FilterSheet.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Text } from "@components/text";
2+
import { EXTERNAL_INBOX_SOURCES } from "@posthog/shared";
23
import { Check } from "phosphor-react-native";
34
import { Modal, Pressable, ScrollView, View } from "react-native";
45
import { useScreenInsets } from "@/hooks/useScreenInsets";
@@ -68,17 +69,19 @@ function usePriorityDotColors(): Record<SignalReportPriority, string> {
6869
};
6970
}
7071

71-
const SOURCE_PRODUCT_OPTIONS: { value: SourceProduct; label: string }[] = [
72-
{ value: "session_replay", label: "Session replay" },
73-
{ value: "error_tracking", label: "Error tracking" },
74-
{ value: "llm_analytics", label: "AI observability" },
75-
{ value: "github", label: "GitHub" },
76-
{ value: "linear", label: "Linear" },
77-
{ value: "zendesk", label: "Zendesk" },
78-
{ value: "conversations", label: "Conversations" },
79-
{ value: "signals_scout", label: "Scout" },
80-
{ value: "health_checks", label: "Health checks" },
81-
];
72+
export const SOURCE_PRODUCT_OPTIONS: { value: SourceProduct; label: string }[] =
73+
[
74+
{ value: "session_replay", label: "Session replay" },
75+
{ value: "error_tracking", label: "Error tracking" },
76+
{ value: "llm_analytics", label: "AI observability" },
77+
{ value: "conversations", label: "Conversations" },
78+
{ value: "signals_scout", label: "Scout" },
79+
{ value: "health_checks", label: "Health checks" },
80+
...EXTERNAL_INBOX_SOURCES.map((source) => ({
81+
value: source.product,
82+
label: source.label,
83+
})),
84+
];
8285

8386
function SectionHeader({ title }: { title: string }) {
8487
return (

apps/mobile/src/features/inbox/components/SignalCard.tsx

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { Text } from "@components/text";
2+
import {
3+
EXTERNAL_INBOX_SOURCE_BY_PRODUCT,
4+
type SourceProduct,
5+
} from "@posthog/shared";
26
import {
37
ArrowSquareOut,
48
Bug,
@@ -11,6 +15,7 @@ import {
1115
FirstAid,
1216
GithubLogo,
1317
LinkSimple,
18+
Plug,
1419
Question,
1520
Robot,
1621
WarningCircle,
@@ -22,45 +27,10 @@ import { formatRelativeTime } from "@/lib/format";
2227
import { openExternalUrl } from "@/lib/openExternalUrl";
2328
import { useThemeColors } from "@/lib/theme";
2429
import type { Signal, SignalFindingContent } from "../types";
30+
import { sourceLine } from "../utils";
2531

2632
const COLLAPSE_THRESHOLD = 280;
2733

28-
const ERROR_TRACKING_TYPE_LABELS: Record<string, string> = {
29-
issue_created: "New issue",
30-
issue_reopened: "Issue reopened",
31-
issue_spiking: "Volume spike",
32-
};
33-
34-
function sourceLine(signal: Signal): string {
35-
const { source_product, source_type } = signal;
36-
if (source_product === "error_tracking") {
37-
const label =
38-
ERROR_TRACKING_TYPE_LABELS[source_type] ?? source_type.replace(/_/g, " ");
39-
return `Error tracking · ${label}`;
40-
}
41-
if (source_product === "session_replay" && source_type === "session_problem")
42-
return "Session replay · Session problem";
43-
if (source_product === "llm_analytics" && source_type === "evaluation")
44-
return "AI observability · Evaluation";
45-
if (source_product === "zendesk" && source_type === "ticket")
46-
return "Zendesk · Ticket";
47-
if (source_product === "github" && source_type === "issue")
48-
return "GitHub · Issue";
49-
if (source_product === "linear" && source_type === "issue")
50-
return "Linear · Issue";
51-
if (
52-
source_product === "signals_scout" &&
53-
source_type === "cross_source_issue"
54-
)
55-
return "Scout · Cross-source issue";
56-
if (source_product === "signals_scout") return "Scout";
57-
if (source_product === "health_checks" && source_type === "health_issue")
58-
return "Health checks · Issue";
59-
const product = source_product.replace(/_/g, " ");
60-
const type = source_type.replace(/_/g, " ");
61-
return `${product} · ${type}`;
62-
}
63-
6434
function SourceIcon({
6535
product,
6636
size = 14,
@@ -88,6 +58,8 @@ function SourceIcon({
8858
case "health_checks":
8959
return <FirstAid size={size} color={color} />;
9060
default:
61+
if (EXTERNAL_INBOX_SOURCE_BY_PRODUCT[product as SourceProduct])
62+
return <Plug size={size} color={color} />;
9163
return <WarningCircle size={size} color={color} />;
9264
}
9365
}

apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe("inboxFilterStore", () => {
1515
useInboxFilterStore.getState().resetFilters();
1616
});
1717

18-
it.each<SourceProduct>(["signals_scout", "error_tracking", "github"])(
18+
it.each<SourceProduct>(["signals_scout", "error_tracking", "sentry"])(
1919
"toggles %s in and out of the source filter",
2020
(source) => {
2121
const { toggleSourceProduct } = useInboxFilterStore.getState();

apps/mobile/src/features/inbox/stores/inboxFilterStore.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { SourceProduct } from "@posthog/shared";
12
import AsyncStorage from "@react-native-async-storage/async-storage";
23
import { create } from "zustand";
34
import { createJSONStorage, persist } from "zustand/middleware";
@@ -14,16 +15,7 @@ type SortField = Extract<
1415

1516
type SortDirection = "asc" | "desc";
1617

17-
export type SourceProduct =
18-
| "conversations"
19-
| "error_tracking"
20-
| "github"
21-
| "health_checks"
22-
| "linear"
23-
| "llm_analytics"
24-
| "session_replay"
25-
| "signals_scout"
26-
| "zendesk";
18+
export type { SourceProduct };
2719

2820
export const DEFAULT_STATUS_FILTER: SignalReportStatus[] = [
2921
"ready",

apps/mobile/src/features/inbox/utils.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, it } from "vitest";
22
import type {
33
AvailableSuggestedReviewer,
4+
Signal,
45
SignalReport,
56
SignalReportOrderingField,
67
SignalReportStatus,
@@ -17,9 +18,23 @@ import {
1718
isRestorableReport,
1819
orderSuggestedReviewers,
1920
reviewerMatchesAvailable,
21+
sourceLine,
2022
toSuggestedReviewerWriteContent,
2123
} from "./utils";
2224

25+
function signal(source_product: string, source_type: string): Signal {
26+
return {
27+
signal_id: "s1",
28+
content: "",
29+
source_product,
30+
source_type,
31+
source_id: "id",
32+
weight: 1,
33+
timestamp: "",
34+
extra: {},
35+
};
36+
}
37+
2338
function reviewer(login: string, uuid?: string): SuggestedReviewer {
2439
return {
2540
github_login: login,
@@ -438,6 +453,24 @@ describe("dismissalReasonLabel", () => {
438453
});
439454
});
440455

456+
describe("sourceLine", () => {
457+
it.each([
458+
{
459+
product: "error_tracking",
460+
type: "issue_created",
461+
expected: "Error tracking · New issue",
462+
},
463+
{ product: "sentry", type: "issue", expected: "Sentry · issue" },
464+
{
465+
product: "mystery_source",
466+
type: "thing",
467+
expected: "mystery source · thing",
468+
},
469+
])("labels $product", ({ product, type, expected }) => {
470+
expect(sourceLine(signal(product, type))).toBe(expected);
471+
});
472+
});
473+
441474
describe("buildReviewerOptions", () => {
442475
it("dedupes by uuid and pins the current user first", () => {
443476
const options = buildReviewerOptions(

apps/mobile/src/features/inbox/utils.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
import {
2+
EXTERNAL_INBOX_SOURCE_BY_PRODUCT,
3+
type SourceProduct,
4+
} from "@posthog/shared";
15
import { differenceInHours, format, formatDistanceToNow } from "date-fns";
26
import type { InboxViewedProperties } from "@/lib/analytics";
37
import { DISMISSAL_REASON_OPTIONS } from "./constants";
48
import type {
59
AvailableSuggestedReviewer,
10+
Signal,
611
SignalReport,
712
SignalReportOrderingField,
813
SignalReportPriority,
@@ -11,6 +16,44 @@ import type {
1116
SuggestedReviewerWriteEntry,
1217
} from "./types";
1318

19+
const ERROR_TRACKING_TYPE_LABELS: Record<string, string> = {
20+
issue_created: "New issue",
21+
issue_reopened: "Issue reopened",
22+
issue_spiking: "Volume spike",
23+
};
24+
25+
export function sourceLine(signal: Signal): string {
26+
const { source_product, source_type } = signal;
27+
if (source_product === "error_tracking") {
28+
const label =
29+
ERROR_TRACKING_TYPE_LABELS[source_type] ?? source_type.replace(/_/g, " ");
30+
return `Error tracking · ${label}`;
31+
}
32+
if (source_product === "session_replay" && source_type === "session_problem")
33+
return "Session replay · Session problem";
34+
if (source_product === "llm_analytics" && source_type === "evaluation")
35+
return "AI observability · Evaluation";
36+
if (source_product === "zendesk" && source_type === "ticket")
37+
return "Zendesk · Ticket";
38+
if (source_product === "github" && source_type === "issue")
39+
return "GitHub · Issue";
40+
if (source_product === "linear" && source_type === "issue")
41+
return "Linear · Issue";
42+
if (
43+
source_product === "signals_scout" &&
44+
source_type === "cross_source_issue"
45+
)
46+
return "Scout · Cross-source issue";
47+
if (source_product === "signals_scout") return "Scout";
48+
if (source_product === "health_checks" && source_type === "health_issue")
49+
return "Health checks · Issue";
50+
const warehouseSource =
51+
EXTERNAL_INBOX_SOURCE_BY_PRODUCT[source_product as SourceProduct];
52+
const product = warehouseSource?.label ?? source_product.replace(/_/g, " ");
53+
const type = source_type.replace(/_/g, " ");
54+
return `${product} · ${type}`;
55+
}
56+
1457
const SIGNAL_SUMMARY_SECTION_HEADERS = [
1558
"What's happening",
1659
"Root cause",

0 commit comments

Comments
 (0)