Skip to content

Commit fcec203

Browse files
Reuse shared inbox transport on mobile
Generated-By: PostHog Code Task-Id: 40c57a59-b4e1-4760-8e56-ecd03e9c2f0f
1 parent 9fa5d73 commit fcec203

5 files changed

Lines changed: 86 additions & 365 deletions

File tree

Lines changed: 5 additions & 336 deletions
Original file line numberDiff line numberDiff line change
@@ -1,342 +1,11 @@
1-
import type { DismissalReasonOptionValue } from "@posthog/shared";
2-
import type {
3-
AnySignalReportArtefact,
4-
AvailableSuggestedReviewer,
5-
AvailableSuggestedReviewersResponse,
6-
CommitDiffResponse,
7-
SignalProcessingStateResponse,
8-
SignalReport,
9-
SignalReportArtefactsResponse,
10-
SignalReportSignalsResponse,
11-
SignalReportsQueryParams,
12-
SignalReportsResponse,
13-
SuggestedReviewerWriteEntry,
14-
} from "@posthog/shared/domain-types";
15-
import { authedFetch, getBaseUrl, getProjectId, HttpError } from "@/lib/api";
16-
import { logger } from "@/lib/logger";
17-
18-
const log = logger.scope("inbox-api");
19-
20-
export async function getSignalReports(
21-
params?: SignalReportsQueryParams,
22-
): Promise<SignalReportsResponse> {
23-
const baseUrl = getBaseUrl();
24-
const projectId = getProjectId();
25-
26-
const url = new URL(`${baseUrl}/api/projects/${projectId}/signals/reports/`);
27-
28-
if (params?.limit != null) {
29-
url.searchParams.set("limit", String(params.limit));
30-
}
31-
if (params?.offset != null) {
32-
url.searchParams.set("offset", String(params.offset));
33-
}
34-
if (params?.status) {
35-
url.searchParams.set("status", params.status);
36-
}
37-
if (params?.ordering) {
38-
url.searchParams.set("ordering", params.ordering);
39-
}
40-
if (params?.source_product) {
41-
url.searchParams.set("source_product", params.source_product);
42-
}
43-
if (params?.suggested_reviewers) {
44-
url.searchParams.set("suggested_reviewers", params.suggested_reviewers);
45-
}
46-
if (params?.priority) {
47-
url.searchParams.set("priority", params.priority);
48-
}
49-
50-
const response = await authedFetch(url.toString());
51-
52-
if (!response.ok) {
53-
throw new HttpError(
54-
response.status,
55-
response.statusText,
56-
"Failed to fetch signal reports",
57-
);
58-
}
59-
60-
const data = await response.json();
61-
return {
62-
results: data.results ?? [],
63-
count: data.count ?? data.results?.length ?? 0,
64-
};
65-
}
66-
67-
export async function getSignalReport(
68-
reportId: string,
69-
): Promise<SignalReport | null> {
70-
const baseUrl = getBaseUrl();
71-
const projectId = getProjectId();
72-
73-
const response = await authedFetch(
74-
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/`,
75-
);
76-
77-
if (response.status === 404 || response.status === 403) {
78-
return null;
79-
}
80-
81-
if (!response.ok) {
82-
throw new HttpError(
83-
response.status,
84-
response.statusText,
85-
"Failed to fetch signal report",
86-
);
87-
}
88-
89-
return await response.json();
90-
}
91-
92-
export async function getSignalProcessingState(): Promise<SignalProcessingStateResponse> {
93-
const baseUrl = getBaseUrl();
94-
const projectId = getProjectId();
95-
96-
const response = await authedFetch(
97-
`${baseUrl}/api/projects/${projectId}/signals/processing_state/`,
98-
);
99-
100-
if (!response.ok) {
101-
throw new HttpError(
102-
response.status,
103-
response.statusText,
104-
"Failed to fetch signal processing state",
105-
);
106-
}
107-
108-
return await response.json();
109-
}
110-
111-
export async function getAvailableSuggestedReviewers(
112-
query?: string,
113-
): Promise<AvailableSuggestedReviewersResponse> {
114-
const baseUrl = getBaseUrl();
115-
const projectId = getProjectId();
116-
117-
const url = new URL(
118-
`${baseUrl}/api/projects/${projectId}/signals/reports/available_reviewers/`,
119-
);
120-
121-
if (query?.trim()) {
122-
url.searchParams.set("query", query.trim());
123-
}
124-
125-
const response = await authedFetch(url.toString());
126-
127-
if (!response.ok) {
128-
throw new HttpError(
129-
response.status,
130-
response.statusText,
131-
"Failed to fetch available suggested reviewers",
132-
);
133-
}
134-
135-
// API returns a dict keyed by UUID: { "uuid": { name, email, github_login } }
136-
const data = await response.json();
137-
const results = Object.entries(data)
138-
.map(([uuid, value]) => {
139-
if (typeof value !== "object" || value === null) return null;
140-
const v = value as Record<string, unknown>;
141-
return {
142-
uuid,
143-
name: typeof v.name === "string" ? v.name : "",
144-
email: typeof v.email === "string" ? v.email : "",
145-
github_login: typeof v.github_login === "string" ? v.github_login : "",
146-
};
147-
})
148-
.filter((r): r is AvailableSuggestedReviewer => r !== null);
149-
150-
return { results, count: results.length };
151-
}
152-
153-
export async function getSignalReportArtefacts(
154-
reportId: string,
155-
): Promise<SignalReportArtefactsResponse> {
156-
const baseUrl = getBaseUrl();
157-
const projectId = getProjectId();
158-
159-
const response = await authedFetch(
160-
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/artefacts/`,
161-
);
162-
163-
if (!response.ok) {
164-
const body = await response.text().catch(() => "");
165-
log.warn("Failed to fetch report artefacts", {
166-
reportId,
167-
status: response.status,
168-
body: body.slice(0, 500),
169-
});
170-
return { results: [], count: 0 };
171-
}
172-
173-
const data = await response.json();
174-
const results: AnySignalReportArtefact[] = data.results ?? [];
175-
return { results, count: data.count ?? results.length };
176-
}
177-
178-
/** Fetch a commit artefact's diff against its parent (lazily, on demand). */
179-
export async function getCommitDiff(
180-
reportId: string,
181-
artefactId: string,
182-
): Promise<CommitDiffResponse> {
183-
const baseUrl = getBaseUrl();
184-
const projectId = getProjectId();
185-
186-
const response = await authedFetch(
187-
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/artefacts/${artefactId}/diff/`,
188-
);
189-
190-
if (!response.ok) {
191-
throw new HttpError(
192-
response.status,
193-
response.statusText,
194-
"Couldn’t load the diff",
195-
);
196-
}
197-
198-
const data = await response.json();
199-
return {
200-
diff: typeof data.diff === "string" ? data.diff : "",
201-
truncated: data.truncated === true,
202-
};
203-
}
204-
205-
/** Replace the content of a report artefact (full PUT, not a partial update). */
206-
export async function updateSignalReportArtefact(
207-
reportId: string,
208-
artefactId: string,
209-
content: SuggestedReviewerWriteEntry[],
210-
): Promise<void> {
211-
const baseUrl = getBaseUrl();
212-
const projectId = getProjectId();
213-
214-
const response = await authedFetch(
215-
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/artefacts/${artefactId}/`,
216-
{
217-
method: "PUT",
218-
body: JSON.stringify({ content }),
219-
},
220-
);
221-
222-
if (!response.ok) {
223-
const errorText = await response.text().catch(() => "");
224-
throw new HttpError(
225-
response.status,
226-
response.statusText,
227-
errorText || "Failed to update suggested reviewers",
228-
);
229-
}
230-
}
231-
232-
export async function getSignalReportSignals(
233-
reportId: string,
234-
): Promise<SignalReportSignalsResponse> {
235-
const baseUrl = getBaseUrl();
236-
const projectId = getProjectId();
237-
238-
const response = await authedFetch(
239-
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/signals/`,
240-
);
241-
242-
if (!response.ok) {
243-
log.warn("Failed to fetch report signals", {
244-
reportId,
245-
status: response.status,
246-
});
247-
return { report: null, signals: [] };
248-
}
249-
250-
const data = await response.json();
251-
return { report: data.report ?? null, signals: data.signals ?? [] };
252-
}
1+
import { selectReportRepository } from "@posthog/core/inbox/reportArtefacts";
2+
import { getPostHogApiClient } from "@/lib/posthogApiClient";
2533

2544
/** Resolve the repository associated with a signal report via its repo_selection artefact. */
2555
export async function getReportRepository(
2566
reportId: string,
2577
): Promise<string | null> {
258-
const { results } = await getSignalReportArtefacts(reportId);
259-
const repoArtefact = results.find((a) => a.type === "repo_selection");
260-
if (!repoArtefact) return null;
261-
262-
let parsed: unknown = repoArtefact.content;
263-
if (typeof parsed === "string") {
264-
try {
265-
parsed = JSON.parse(parsed);
266-
} catch {
267-
return (parsed as string).toLowerCase();
268-
}
269-
}
270-
271-
if (typeof parsed === "object" && parsed !== null) {
272-
const repo =
273-
(parsed as Record<string, unknown>).repository ??
274-
(parsed as Record<string, unknown>).repo;
275-
if (typeof repo === "string") return repo.toLowerCase();
276-
}
277-
278-
return null;
279-
}
280-
281-
export interface DismissSignalReportInput {
282-
reason: DismissalReasonOptionValue;
283-
note?: string;
284-
}
285-
286-
export async function dismissSignalReport(
287-
reportId: string,
288-
input: DismissSignalReportInput,
289-
): Promise<SignalReport> {
290-
const baseUrl = getBaseUrl();
291-
const projectId = getProjectId();
292-
293-
const response = await authedFetch(
294-
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/state/`,
295-
{
296-
method: "POST",
297-
body: JSON.stringify({
298-
state: "suppressed",
299-
dismissal_reason: input.reason,
300-
...(input.note?.trim() ? { dismissal_note: input.note.trim() } : {}),
301-
}),
302-
},
303-
);
304-
305-
if (!response.ok) {
306-
const errorText = await response.text().catch(() => "");
307-
throw new HttpError(
308-
response.status,
309-
response.statusText,
310-
errorText || "Failed to dismiss signal report",
311-
);
312-
}
313-
314-
return await response.json();
315-
}
316-
317-
/** Re-queue a dismissed report into the inbox via the `potential` transition. */
318-
export async function restoreSignalReport(
319-
reportId: string,
320-
): Promise<SignalReport> {
321-
const baseUrl = getBaseUrl();
322-
const projectId = getProjectId();
323-
324-
const response = await authedFetch(
325-
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/state/`,
326-
{
327-
method: "POST",
328-
body: JSON.stringify({ state: "potential" }),
329-
},
330-
);
331-
332-
if (!response.ok) {
333-
const errorText = await response.text().catch(() => "");
334-
throw new HttpError(
335-
response.status,
336-
response.statusText,
337-
errorText || "Failed to restore signal report",
338-
);
339-
}
340-
341-
return await response.json();
8+
const { results } =
9+
await getPostHogApiClient().getSignalReportArtefacts(reportId);
10+
return selectReportRepository(results);
34211
}

apps/mobile/src/features/inbox/hooks/useInboxReports.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ const getAvailableSuggestedReviewers = vi.fn(async (_query?: string) => ({
1515
results: [],
1616
count: 0,
1717
}));
18-
vi.mock("../api", () => ({
19-
getAvailableSuggestedReviewers: (query?: string) =>
20-
getAvailableSuggestedReviewers(query),
18+
vi.mock("@/lib/posthogApiClient", () => ({
19+
getPostHogApiClient: () => ({
20+
getAvailableSuggestedReviewers: (query?: string) =>
21+
getAvailableSuggestedReviewers(query),
22+
}),
2123
}));
2224

2325
import {

0 commit comments

Comments
 (0)