FN-5640: refresh PR status from Check PR Status footer action

Fusion-Task-Id: FN-5640

Fusion-Task-Lineage: 80074933-2486-4aa6-b23d-1d0e049808cb
This commit is contained in:
gsxdsm
2026-05-28 21:40:01 -07:00
parent f5cf974504
commit 1df1b479dd
3 changed files with 135 additions and 3 deletions

View File

@@ -19,7 +19,7 @@ import {
resolveTaskPlanningModel,
resolveTaskValidatorModel,
} from "@fusion/core";
import { uploadAttachment, deleteAttachment, updateTask, pauseTask, unpauseTask, fetchTaskDetail, fetchSettings, fetchGlobalSettings, requestSpecRevision, rebuildTaskSpec, approvePlan, rejectPlan, refineTask, fetchWorkflowResults, assignTask, fetchAgents, fetchAgent, recoverBranchBinding } from "../api";
import { uploadAttachment, deleteAttachment, updateTask, pauseTask, unpauseTask, fetchTaskDetail, fetchSettings, fetchGlobalSettings, requestSpecRevision, rebuildTaskSpec, approvePlan, rejectPlan, refineTask, fetchWorkflowResults, assignTask, fetchAgents, fetchAgent, recoverBranchBinding, refreshPrStatus } from "../api";
import type { RecoverBranchBindingOutcome } from "../api";
import type { ToastType } from "../hooks/useToast";
import { useAgentLogs } from "../hooks/useAgentLogs";
@@ -649,6 +649,7 @@ export function TaskDetailContent({
const [githubRepoOverrideError, setGithubRepoOverrideError] = useState<string | null>(null);
const [isSavingGithubTracking, setIsSavingGithubTracking] = useState(false);
const [isRecoveringBranchBinding, setIsRecoveringBranchBinding] = useState(false);
const [isCheckingPrStatus, setIsCheckingPrStatus] = useState(false);
const [recoverBranchBindingOutcome, setRecoverBranchBindingOutcome] = useState<RecoverBranchBindingOutcome | null>(null);
const moveMenuRef = useRef<HTMLDivElement>(null);
const activityListRef = useRef<HTMLDivElement>(null);
@@ -1798,6 +1799,25 @@ export function TaskDetailContent({
void handleMerge();
}, [closeMenus, handleMerge]);
const handleCheckPrStatus = useCallback(async () => {
if (isCheckingPrStatus) return;
closeMenus();
setIsCheckingPrStatus(true);
try {
const result = await refreshPrStatus(task.id, projectId);
addToast("PR status refreshed", "success");
onTaskUpdated?.({
...task,
prInfo: result.prInfo,
prInfos: result.all?.map((entry) => entry.prInfo) ?? task.prInfos,
});
} catch (err) {
addToast(getErrorMessage(err), "error");
} finally {
setIsCheckingPrStatus(false);
}
}, [addToast, closeMenus, isCheckingPrStatus, onTaskUpdated, projectId, task]);
const handleCloseRefineModal = useCallback(() => {
setShowRefineModal(false);
setRefineFeedback("");
@@ -3826,7 +3846,11 @@ export function TaskDetailContent({
{prAutomationLabel}
</button>
) : (
<button className="btn btn-primary btn-sm" onClick={handleMergeMenuItemClick}>
<button
className="btn btn-primary btn-sm"
onClick={manualReviewActionLabel === "Check PR Status" ? handleCheckPrStatus : handleMergeMenuItemClick}
disabled={manualReviewActionLabel === "Check PR Status" && isCheckingPrStatus}
>
{manualReviewActionLabel}
</button>
)}

View File

@@ -572,6 +572,114 @@ describe("TaskDetailModal", () => {
});
});
it("refreshes PR status for Check PR Status without merge prompt", async () => {
const { fetchSettings, refreshPrStatus } = await import("../../api");
const addToast = vi.fn();
const onMergeTask = vi.fn(async () => ({ merged: false } as MergeResult));
const onTaskUpdated = vi.fn();
vi.mocked(fetchSettings).mockResolvedValueOnce({
modelPresets: [],
autoSelectModelPreset: false,
defaultPresetBySize: {},
mergeStrategy: "pull-request",
autoMerge: false,
});
vi.mocked(refreshPrStatus).mockResolvedValueOnce({
prInfo: {
url: "https://github.com/owner/repo/pull/42",
number: 42,
status: "open",
title: "Task",
headBranch: "fusion/fn-099",
baseBranch: "main",
commentCount: 1,
},
all: [],
});
render(
<TaskDetailModal
task={makeTask({
column: "in-review" as Column,
prInfo: {
url: "https://github.com/owner/repo/pull/42",
number: 42,
status: "open",
title: "Task",
headBranch: "fusion/fn-099",
baseBranch: "main",
commentCount: 0,
},
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={onMergeTask}
onOpenDetail={noopOpenDetail}
onTaskUpdated={onTaskUpdated}
addToast={addToast}
projectId="project-1"
/>,
);
fireEvent.click(await screen.findByRole("button", { name: "Check PR Status" }));
await waitFor(() => {
expect(refreshPrStatus).toHaveBeenCalledWith("FN-099", "project-1");
});
expect(onMergeTask).not.toHaveBeenCalled();
expect(mockConfirm).not.toHaveBeenCalled();
expect(addToast).toHaveBeenCalledWith("PR status refreshed", "success");
});
it("shows error toast when Check PR Status refresh fails", async () => {
const { fetchSettings, refreshPrStatus } = await import("../../api");
const addToast = vi.fn();
const onMergeTask = vi.fn(async () => ({ merged: false } as MergeResult));
vi.mocked(fetchSettings).mockResolvedValueOnce({
modelPresets: [],
autoSelectModelPreset: false,
defaultPresetBySize: {},
mergeStrategy: "pull-request",
autoMerge: false,
});
vi.mocked(refreshPrStatus).mockRejectedValueOnce(new Error("refresh failed"));
render(
<TaskDetailModal
task={makeTask({
column: "in-review" as Column,
prInfo: {
url: "https://github.com/owner/repo/pull/42",
number: 42,
status: "open",
title: "Task",
headBranch: "fusion/fn-099",
baseBranch: "main",
commentCount: 0,
},
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={onMergeTask}
onOpenDetail={noopOpenDetail}
addToast={addToast}
/>,
);
fireEvent.click(await screen.findByRole("button", { name: "Check PR Status" }));
await waitFor(() => {
expect(refreshPrStatus).toHaveBeenCalledWith("FN-099", undefined);
});
expect(onMergeTask).not.toHaveBeenCalled();
expect(mockConfirm).not.toHaveBeenCalled();
expect(addToast).toHaveBeenCalledWith("refresh failed", "error");
});
it.each([
[{ status: "open" as const }, "Check PR Status"],
[{ status: "merged" as const }, "Finish & Close"],