Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions apps/mobile/src/features/inbox/components/FilterSheet.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
25 changes: 14 additions & 11 deletions apps/mobile/src/features/inbox/components/FilterSheet.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -68,17 +69,19 @@ function usePriorityDotColors(): Record<SignalReportPriority, string> {
};
}

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 (
Expand Down
44 changes: 8 additions & 36 deletions apps/mobile/src/features/inbox/components/SignalCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Text } from "@components/text";
import {
EXTERNAL_INBOX_SOURCE_BY_PRODUCT,
type SourceProduct,
} from "@posthog/shared";
import {
ArrowSquareOut,
Bug,
Expand All @@ -11,6 +15,7 @@ import {
FirstAid,
GithubLogo,
LinkSimple,
Plug,
Question,
Robot,
WarningCircle,
Expand All @@ -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<string, string> = {
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,
Expand Down Expand Up @@ -88,6 +58,8 @@ function SourceIcon({
case "health_checks":
return <FirstAid size={size} color={color} />;
default:
if (EXTERNAL_INBOX_SOURCE_BY_PRODUCT[product as SourceProduct])
return <Plug size={size} color={color} />;
return <WarningCircle size={size} color={color} />;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("inboxFilterStore", () => {
useInboxFilterStore.getState().resetFilters();
});

it.each<SourceProduct>(["signals_scout", "error_tracking", "github"])(
it.each<SourceProduct>(["signals_scout", "error_tracking", "sentry"])(
"toggles %s in and out of the source filter",
(source) => {
const { toggleSourceProduct } = useInboxFilterStore.getState();
Expand Down
12 changes: 2 additions & 10 deletions apps/mobile/src/features/inbox/stores/inboxFilterStore.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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",
Expand Down
33 changes: 33 additions & 0 deletions apps/mobile/src/features/inbox/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import type {
AvailableSuggestedReviewer,
Signal,
SignalReport,
SignalReportOrderingField,
SignalReportStatus,
Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
43 changes: 43 additions & 0 deletions apps/mobile/src/features/inbox/utils.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -11,6 +16,44 @@ import type {
SuggestedReviewerWriteEntry,
} from "./types";

const ERROR_TRACKING_TYPE_LABELS: Record<string, string> = {
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",
Expand Down
Loading