From a11f789b0cbfec47c317305260eb03057222aa24 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 19 Jul 2026 20:42:05 -0700 Subject: [PATCH] FN-8327: fall back to issues when Discussions are disabled Reports can select a Discussion category and reliably file to Issues when Discussions are unavailable. - Add Discussion-disabled detection across GitHub GraphQL operations. - Let report submission select a category and disclose the actual filing destination. - Preserve duplicate handling and document the fallback behavior. Files changed: .changeset/fn-8327-discussion-fallback.md | 7 +++ docs/dashboard-guide.md | 4 +- packages/dashboard/app/api/report.ts | 22 +++++-- packages/dashboard/app/components/ReportModal.tsx | 7 ++- .../app/components/__tests__/ReportModal.test.tsx | 13 ++++ .../src/__tests__/github-discussions.test.ts | 29 +++++++-- .../src/__tests__/report-pipeline.test.ts | 63 ++++++++++++++----- .../dashboard/src/__tests__/report-routes.test.ts | 15 +++-- packages/dashboard/src/github.ts | 71 ++++++++++++++++------ packages/dashboard/src/report-pipeline.ts | 54 +++++++++++----- .../dashboard/src/routes/register-report-routes.ts | 12 +++- 11 files changed, 227 insertions(+), 70 deletions(-) Fusion-Task-Id: FN-8327 Fusion-Task-Lineage: 510d3d4f-ffc9-4167-bee8-7043e0ddbd9c Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-8327-discussion-fallback.md | 7 ++ docs/dashboard-guide.md | 4 +- packages/dashboard/app/api/report.ts | 22 ++++-- .../dashboard/app/components/ReportModal.tsx | 7 +- .../components/__tests__/ReportModal.test.tsx | 13 ++++ .../src/__tests__/github-discussions.test.ts | 29 ++++++-- .../src/__tests__/report-pipeline.test.ts | 63 ++++++++++++---- .../src/__tests__/report-routes.test.ts | 15 ++-- packages/dashboard/src/github.ts | 71 ++++++++++++++----- packages/dashboard/src/report-pipeline.ts | 54 +++++++++----- .../src/routes/register-report-routes.ts | 12 +++- 11 files changed, 227 insertions(+), 70 deletions(-) create mode 100644 .changeset/fn-8327-discussion-fallback.md diff --git a/.changeset/fn-8327-discussion-fallback.md b/.changeset/fn-8327-discussion-fallback.md new file mode 100644 index 0000000000..0a01096280 --- /dev/null +++ b/.changeset/fn-8327-discussion-fallback.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: File Feedback and Help reports as Issues when GitHub Discussions is disabled. +category: feature +dev: Reuses the reportDiscussionCategory setting and exposes the final filing destination. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 3aa7c66a63..6324c7a744 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -2,9 +2,9 @@ ## In-app report filing targets -In **Settings → General**, operators can choose a default GitHub report target (**Issue** or **Discussion**) and add an optional per-action override for Bug, Feedback, Idea, or Help. Leaving these settings unset preserves the built-in routing: Bug and Idea file Issues; Feedback and Help file Discussions. A Discussion category ID may be configured when a repository requires a specific category. +In **Settings → General**, operators can choose a default GitHub report target (**Issue** or **Discussion**) and add an optional per-action override for Bug, Feedback, Idea, or Help. Leaving these settings unset preserves the built-in routing: Bug and Idea file Issues; Feedback and Help file Discussions. The `reportDiscussionCategory` setting selects the category for Discussion reports. -Discussion filing uses the same scrub-before-egress report pipeline as Issues. Duplicate matching considers open Discussions only; a confirmed duplicate receives an upvote reaction and one scrubbed data-point comment rather than a new Discussion. +Discussion filing uses the same scrub-before-egress report pipeline as Issues. Duplicate matching considers open Discussions only; a confirmed duplicate receives an upvote reaction and one scrubbed data-point comment rather than a new Discussion. If a repository has Discussions disabled, Fusion detects that during either Discussion search or creation, reruns Issue deduplication, and files an Issue instead; the filed result explicitly identifies that fallback destination. [← Docs index](./README.md) diff --git a/packages/dashboard/app/api/report.ts b/packages/dashboard/app/api/report.ts index ce4c8e6949..80d699e8cc 100644 --- a/packages/dashboard/app/api/report.ts +++ b/packages/dashboard/app/api/report.ts @@ -1,15 +1,27 @@ import type { ReportActionType, ReportTarget } from "@fusion/core"; -async function post(path: string, body: unknown) { +async function post(path: string, body: unknown): Promise { const response = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); if (!response.ok) throw new Error((await response.json().catch(() => ({ error: response.statusText }))).error ?? response.statusText); - return response.json(); + return response.json() as Promise; +} + +/** Filed report responses expose the actual GitHub destination after fallback. */ +export interface FiledReportResponse { kind: "filed"; url: string; destination: "issue" | "discussion"; } +export interface ReportResponse { + kind: string; + destination?: "issue" | "discussion"; + url?: string; + message?: string; + report?: { userPrompt: string; sourcePrompt?: string; summary?: string; body?: string; context?: Record; sessionToken?: string }; + issue?: { number: number; url: string; title: string; discussionId?: string; roadmap?: true }; + answer?: { summary?: string; content?: string }; } export interface ReportContextInput { actionType: ReportActionType; userPrompt: string; contextRefs?: { taskId?: string; agentId?: string }; activityTrace?: string[]; screenshotArtifactId?: string; } -export function reportDraft(input: ReportContextInput & { targetType?: ReportTarget }) { return post("/api/report/draft", input); } -export function reportFile(input: { actionType: ReportActionType; report: unknown; targetType?: ReportTarget; endorseIssueNumber?: number; endorseDiscussionId?: string; endorseRoadmapIssueNumber?: number; activityTrace?: string[]; screenshotArtifactId?: string }) { return post("/api/report/file", input); } -export function reportHelp(question: string) { return post("/api/report/help", { question }); } +export function reportDraft(input: ReportContextInput & { targetType?: ReportTarget; discussionCategoryId?: string }) { return post("/api/report/draft", input); } +export function reportFile(input: { actionType: ReportActionType; report: unknown; targetType?: ReportTarget; discussionCategoryId?: string; endorseIssueNumber?: number; endorseDiscussionId?: string; endorseRoadmapIssueNumber?: number; activityTrace?: string[]; screenshotArtifactId?: string }) { return post("/api/report/file", input); } +export function reportHelp(question: string) { return post<{ answered?: boolean; answer?: { summary?: string; content?: string } }>("/api/report/help", { question }); } /** Upload is intentionally multipart: screenshot bytes never join JSON report text. */ export async function reportAttachment(screenshot: Blob): Promise<{ artifactId: string }> { diff --git a/packages/dashboard/app/components/ReportModal.tsx b/packages/dashboard/app/components/ReportModal.tsx index 166d30d604..d09b45e869 100644 --- a/packages/dashboard/app/components/ReportModal.tsx +++ b/packages/dashboard/app/components/ReportModal.tsx @@ -8,7 +8,7 @@ import "./ReportModal.css"; const prompts: Record = { bug: "What went wrong?", feedback: "What would you like to share?", idea: "What would you like Fusion to do?", help: "What would you like help with?" }; -type ModalResult = { kind: string; report?: { userPrompt: string; sourcePrompt?: string; summary?: string; body?: string; context?: Record; sessionToken?: string }; issue?: { number: number; url: string; title: string; discussionId?: string; roadmap?: true }; url?: string; answer?: { summary?: string; content?: string }; message?: string; screenshotNotAttached?: boolean }; +type ModalResult = { kind: string; destination?: "issue" | "discussion"; report?: { userPrompt: string; sourcePrompt?: string; summary?: string; body?: string; context?: Record; sessionToken?: string }; issue?: { number: number; url: string; title: string; discussionId?: string; roadmap?: true }; url?: string; answer?: { summary?: string; content?: string }; message?: string; screenshotNotAttached?: boolean }; /** @@ -119,7 +119,10 @@ setResult(await reportFile({ actionType, targetType, report: result.report, endo } -{(result?.kind === "filed" || result?.kind === "endorsed") && <>

Report sent

View on GitHub{result.report?.body && <>