|
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"; |
253 | 3 |
|
254 | 4 | /** Resolve the repository associated with a signal report via its repo_selection artefact. */ |
255 | 5 | export async function getReportRepository( |
256 | 6 | reportId: string, |
257 | 7 | ): 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); |
342 | 11 | } |
0 commit comments