From 69228553d278652741d177e9f2358067982b9806 Mon Sep 17 00:00:00 2001 From: Fusion Date: Mon, 11 May 2026 07:56:43 -0700 Subject: [PATCH] feat(FN-3995): add GitHub tracking disclosure to task detail modal Added GitHub tracking disclosure UI and behavior to the TaskDetailModal with corresponding tests and documentation updates (FN-3995). Fusion-Task-Id: FN-3995 --- docs/dashboard-guide.md | 1 + docs/task-management.md | 2 +- .../app/components/TaskDetailModal.css | 7 +- .../app/components/TaskDetailModal.tsx | 126 ++++++++++-------- ...l.inline-editing-and-integrations.test.tsx | 26 +++- 5 files changed, 103 insertions(+), 59 deletions(-) diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 18ab4956b..69c321d04 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -421,6 +421,7 @@ Inspect task definition, logs, review feedback, comments, documents, workflow ou - Execution mode has a read-mode inline lightning-bolt toggle for Fast mode on/off without opening the full edit form. - These two metadata controls share matched sizing/alignment in read mode (including mobile wrapping) so they behave like a single polished control group. - Eligible existing tasks (triage, todo, in-progress, in-review) expose a **GitHub tracking** section directly in Task Detail, even when tracking is currently disabled. +- The GitHub tracking section now defaults to a compact summary row; use the disclosure arrow to expand linked-issue details plus tracking edit controls. - In shared task edit/create forms, GitHub Tracking appears at the bottom of **More options**, after **Workflow Steps**. - From this section you can explicitly enable/disable tracking and manage a per-task repo override (`owner/repo`). Clearing the override saves `null` and falls back to project/global defaults. - The **Review** tab is separate from **Comments**: Review shows actionable PR/reviewer feedback and same-task revision controls, while Comments remains the general collaboration thread. diff --git a/docs/task-management.md b/docs/task-management.md index cdbc1cc35..d424f9d56 100644 --- a/docs/task-management.md +++ b/docs/task-management.md @@ -401,7 +401,7 @@ Tracking behavior is controlled per task: - `task.githubTracking.enabled` turns tracking on for that task. - `task.githubTracking.repoOverride` optionally forces a specific target repo (`owner/repo`). -- In the dashboard **Task Detail** modal, eligible existing tasks (`triage`, `todo`, `in-progress`, `in-review`) always show GitHub tracking controls so tracking can be enabled, disabled, or retargeted without reopening the task in a creation flow. +- In the dashboard **Task Detail** modal, eligible existing tasks (`triage`, `todo`, `in-progress`, `in-review`) always show a compact GitHub tracking summary row; linked-issue details and tracking controls are behind a disclosure arrow so tracking can still be enabled, disabled, or retargeted without reopening the task in a creation flow. - Clearing the Task Detail repo override stores `null`, which reverts repo resolution to project/global defaults. - Explicit task-level enablement is honored even when project/global GitHub tracking defaults are unset. If `enabled: true` and the repo resolves at task scope (for example via `repoOverride`), Fusion attempts tracking-issue creation on both create-time and eligible edit-time flows. - Explicit manual unlink (`githubTracking.issue: null`) does not recreate a tracking issue in that same update request, and disabling tracking does not create new issues. diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css index 9e3a0b239..19b99477d 100644 --- a/packages/dashboard/app/components/TaskDetailModal.css +++ b/packages/dashboard/app/components/TaskDetailModal.css @@ -449,12 +449,17 @@ padding: var(--space-sm) var(--space-md); } -.detail-github-tracking-grid { +.detail-github-tracking-content { margin-top: var(--space-sm); padding-top: var(--space-sm); border-top: 1px solid var(--border); } +.detail-github-tracking-grid { + margin-top: 0; + padding-top: 0; +} + .detail-github-issue-state { font-weight: 600; text-transform: lowercase; diff --git a/packages/dashboard/app/components/TaskDetailModal.tsx b/packages/dashboard/app/components/TaskDetailModal.tsx index a03b82561..f681971b4 100644 --- a/packages/dashboard/app/components/TaskDetailModal.tsx +++ b/packages/dashboard/app/components/TaskDetailModal.tsx @@ -534,6 +534,7 @@ export function TaskDetailContent({ const [showMoveMenu, setShowMoveMenu] = useState(false); const [showActionsMenu, setShowActionsMenu] = useState(false); const [sourceIssueExpanded, setSourceIssueExpanded] = useState(false); + const [githubTrackingExpanded, setGithubTrackingExpanded] = useState(false); const [githubRepoOverrideDraft, setGithubRepoOverrideDraft] = useState(task.githubTracking?.repoOverride ?? ""); const [githubTrackingEnabledDraft, setGithubTrackingEnabledDraft] = useState(null); const [githubRepoOverrideError, setGithubRepoOverrideError] = useState(null); @@ -584,6 +585,7 @@ export function TaskDetailContent({ setEditSourceIssueUrl(task.sourceIssue?.url ?? ""); setEditExecutionMode(normalizeExecutionModeValue(task.executionMode)); setSourceIssueExpanded(false); + setGithubTrackingExpanded(false); setGithubRepoOverrideDraft(task.githubTracking?.repoOverride ?? ""); setGithubTrackingEnabledDraft(null); setGithubRepoOverrideError(null); @@ -2358,62 +2360,78 @@ export function TaskDetailContent({ )} + - {githubTrackedIssue && ( -
-
-
Issue
-
- {githubTrackedIssue.url ? ( - - {`${githubTrackedIssue.owner}/${githubTrackedIssue.repo}#${githubTrackedIssue.number}`} - - ) : ( - {`${githubTrackedIssue.owner}/${githubTrackedIssue.repo}#${githubTrackedIssue.number}`} - )} -
-
-
-
State
-
- - {task.issueInfo?.state ?? "open"} - -
-
-
- )} - {canEditGithubTracking && ( -
- -
- { - setGithubRepoOverrideDraft(event.target.value); - setGithubRepoOverrideError(null); - }} - placeholder={effectiveGithubRepoDefault || "owner/repo"} - /> - -
- {githubRepoOverrideError && {githubRepoOverrideError}} + {githubTrackingExpanded && ( +
{githubTrackedIssue && ( - +
+
+
Issue
+
+ {githubTrackedIssue.url ? ( + + {`${githubTrackedIssue.owner}/${githubTrackedIssue.repo}#${githubTrackedIssue.number}`} + + ) : ( + {`${githubTrackedIssue.owner}/${githubTrackedIssue.repo}#${githubTrackedIssue.number}`} + )} +
+
+
+
State
+
+ + {task.issueInfo?.state ?? "open"} + +
+
+
+ )} + {canEditGithubTracking && ( +
+ +
+ { + setGithubRepoOverrideDraft(event.target.value); + setGithubRepoOverrideError(null); + }} + placeholder={effectiveGithubRepoDefault || "owner/repo"} + /> + +
+ {githubRepoOverrideError && {githubRepoOverrideError}} + {githubTrackedIssue && ( + + )} +
)}
)} diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx index ad42d6d95..6aba473d8 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx @@ -59,8 +59,8 @@ describe("TaskDetailModal", () => { const toggle = screen.getByRole("button", { name: "Expand source issue details" }); expect(toggle).toHaveAttribute("aria-expanded", "false"); - const chevron = screen.getByTestId("chevron-right-icon"); - expect(chevron.classList.contains("detail-source-chevron--expanded")).toBe(false); + const chevron = toggle.querySelector("svg"); + expect(chevron?.classList.contains("detail-source-chevron--expanded")).toBe(false); await user.click(toggle); @@ -71,7 +71,8 @@ describe("TaskDetailModal", () => { const sourceLink = screen.getByRole("link", { name: "https://github.com/runfusion/fusion/issues/2473" }); expect(sourceLink).toHaveAttribute("href", "https://github.com/runfusion/fusion/issues/2473"); expect(sourceLink).toHaveAttribute("target", "_blank"); - expect(screen.getByTestId("chevron-right-icon").classList.contains("detail-source-chevron--expanded")).toBe(true); + const expandedChevron = screen.getByRole("button", { name: "Collapse source issue details" }).querySelector("svg"); + expect(expandedChevron?.classList.contains("detail-source-chevron--expanded")).toBe(true); }); it("applies compact GitHub source summary styling contracts", () => { @@ -2132,6 +2133,10 @@ describe("TaskDetailModal", () => { }); describe("github tracking section", () => { + const expandGithubTracking = () => { + fireEvent.click(screen.getByRole("button", { name: "Expand GitHub tracking details" })); + }; + it("renders linked issue as link when url exists", () => { render( { ); expect(screen.getByText("GitHub tracking")).toBeTruthy(); + expect(screen.getByLabelText("GitHub tracking status")).toHaveTextContent("Linked"); + expect(screen.queryByRole("link", { name: "runfusion/fusion#123" })).toBeNull(); + + expandGithubTracking(); + + expect(screen.getByRole("button", { name: "Collapse GitHub tracking details" })).toHaveAttribute("aria-expanded", "true"); expect(screen.getByRole("link", { name: "runfusion/fusion#123" })).toHaveAttribute("href", "https://github.com/runfusion/fusion/issues/123"); }); @@ -2176,6 +2187,7 @@ describe("TaskDetailModal", () => { expect(screen.getByText("GitHub tracking")).toBeTruthy(); expect(screen.getByText("Tracking is currently disabled")).toBeTruthy(); + expect(screen.queryByLabelText("Enable GitHub tracking")).toBeNull(); }); it("hides section when tracking is disabled and task is not in an eligible column", () => { @@ -2224,6 +2236,8 @@ describe("TaskDetailModal", () => { />, ); + expandGithubTracking(); + const toggle = screen.getByLabelText("Enable GitHub tracking") as HTMLInputElement; expect(toggle.checked).toBe(false); @@ -2258,6 +2272,8 @@ describe("TaskDetailModal", () => { />, ); + expandGithubTracking(); + fireEvent.click(screen.getByLabelText("Enable GitHub tracking")); await waitFor(() => { expect(mockUpdate).toHaveBeenCalledWith("FN-001", { githubTracking: { enabled: false } }, undefined); @@ -2281,6 +2297,8 @@ describe("TaskDetailModal", () => { />, ); + expandGithubTracking(); + fireEvent.change(screen.getByPlaceholderText("owner/repo"), { target: { value: "runfusion/cli" } }); fireEvent.click(screen.getByRole("button", { name: "Save" })); @@ -2327,6 +2345,8 @@ describe("TaskDetailModal", () => { />, ); + expandGithubTracking(); + fireEvent.click(screen.getByRole("button", { name: "Unlink GitHub issue" })); await waitFor(() => { expect(mockUpdate).not.toHaveBeenCalledWith("FN-001", { githubTracking: { issue: null } }, undefined);