feat(FN-4378): complete Step 5 — prompt github issue action on task delete
Fusion-Task-Id: FN-4378 Fusion-Task-Lineage: d3867ba8-f852-41e3-9db0-584c0ffc23b1
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { Task, TaskDetail, Column as ColumnType, TaskCreateInput } from "@fusion/core";
|
||||
import type { Task, TaskDetail, Column as ColumnType, TaskCreateInput, GithubIssueAction } from "@fusion/core";
|
||||
import { COLUMNS, DEFAULT_COLUMN, isColumn } from "@fusion/core";
|
||||
import { sortTasksForDisplayColumn } from "./taskSorting";
|
||||
import { Column } from "./Column";
|
||||
@@ -28,7 +28,7 @@ interface BoardProps {
|
||||
onRetryTask?: (id: string) => Promise<Task>;
|
||||
onArchiveTask?: (id: string) => Promise<Task>;
|
||||
onUnarchiveTask?: (id: string) => Promise<Task>;
|
||||
onDeleteTask?: (id: string) => Promise<Task>;
|
||||
onDeleteTask?: (id: string, options?: { removeDependencyReferences?: boolean; githubIssueAction?: GithubIssueAction }) => Promise<Task>;
|
||||
onArchiveAllDone?: () => Promise<Task[]>;
|
||||
/** Lazy-load archived tasks. Called the first time the user expands the archived column. */
|
||||
onLoadArchivedTasks?: () => Promise<void>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { memo, useMemo, useState, useCallback, useEffect, useRef } from "react";
|
||||
import { useFlashOnIncrease } from "../hooks/useFlashOnIncrease";
|
||||
import { useConfirm } from "../hooks/useConfirm";
|
||||
import type { Task, TaskDetail, Column as ColumnType, TaskCreateInput } from "@fusion/core";
|
||||
import type { Task, TaskDetail, Column as ColumnType, TaskCreateInput, GithubIssueAction } from "@fusion/core";
|
||||
import { COLUMN_LABELS, COLUMN_DESCRIPTIONS, getErrorMessage } from "@fusion/core";
|
||||
import { TaskCard } from "./TaskCard";
|
||||
import { WorktreeGroup } from "./WorktreeGroup";
|
||||
@@ -38,7 +38,7 @@ interface ColumnProps {
|
||||
onRetryTask?: (id: string) => Promise<Task>;
|
||||
onArchiveTask?: (id: string) => Promise<Task>;
|
||||
onUnarchiveTask?: (id: string) => Promise<Task>;
|
||||
onDeleteTask?: (id: string) => Promise<Task>;
|
||||
onDeleteTask?: (id: string, options?: { removeDependencyReferences?: boolean; githubIssueAction?: GithubIssueAction }) => Promise<Task>;
|
||||
onArchiveAllDone?: () => Promise<Task[]>;
|
||||
collapsed?: boolean;
|
||||
onToggleCollapse?: () => void;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "./ListView.css";
|
||||
import { useState, useCallback, useMemo, Fragment, useEffect, useRef } from "react";
|
||||
import { ArrowUpDown, ArrowUp, ArrowDown, Link, Columns3, EyeOff, Eye, ChevronRight, Zap, Trash2 } from "lucide-react";
|
||||
import type { Task, TaskDetail, Column, TaskCreateInput, MergeResult } from "@fusion/core";
|
||||
import type { Task, TaskDetail, Column, TaskCreateInput, MergeResult, GithubIssueAction } from "@fusion/core";
|
||||
import { COLUMN_LABELS, COLUMNS, DEFAULT_COLUMN, getErrorMessage, isColumn } from "@fusion/core";
|
||||
import { sortTasksForDisplayColumn } from "./taskSorting";
|
||||
import { batchUpdateTaskModels, fetchNodes, fetchTaskDetail } from "../api";
|
||||
@@ -167,7 +167,7 @@ interface ListViewProps {
|
||||
tasks: Task[];
|
||||
onMoveTask: (id: string, column: Column, optionsOrPosition?: { preserveProgress?: boolean } | number) => Promise<Task>;
|
||||
onRetryTask?: (id: string) => Promise<Task>;
|
||||
onDeleteTask: (id: string, options?: { removeDependencyReferences?: boolean }) => Promise<Task>;
|
||||
onDeleteTask: (id: string, options?: { removeDependencyReferences?: boolean; githubIssueAction?: GithubIssueAction }) => Promise<Task>;
|
||||
onMergeTask: (id: string) => Promise<MergeResult>;
|
||||
onResetTask?: (id: string) => Promise<Task>;
|
||||
onDuplicateTask?: (id: string) => Promise<Task>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "./TaskCard.css";
|
||||
import { memo, useCallback, useState, useRef, useEffect, useMemo } from "react";
|
||||
import { Link, Clock, Layers, Pencil, ChevronDown, Folder, Target, Bot, Trash2, RotateCw, Zap, GitBranch } from "lucide-react";
|
||||
import type { Task, TaskDetail, Column, PrInfo, IssueInfo, TaskPriority } from "@fusion/core";
|
||||
import type { Task, TaskDetail, Column, PrInfo, IssueInfo, TaskPriority, GithubIssueAction } from "@fusion/core";
|
||||
import {
|
||||
COLUMN_LABELS,
|
||||
DEFAULT_TASK_PRIORITY,
|
||||
@@ -271,7 +271,7 @@ interface TaskCardProps {
|
||||
) => Promise<Task>;
|
||||
onArchiveTask?: (id: string) => Promise<Task>;
|
||||
onUnarchiveTask?: (id: string) => Promise<Task>;
|
||||
onDeleteTask?: (id: string, options?: { removeDependencyReferences?: boolean }) => Promise<Task>;
|
||||
onDeleteTask?: (id: string, options?: { removeDependencyReferences?: boolean; githubIssueAction?: GithubIssueAction }) => Promise<Task>;
|
||||
onRetryTask?: (id: string) => Promise<Task>;
|
||||
onOpenDetailWithTab?: (task: Task | TaskDetail, initialTab: "changes") => void;
|
||||
/** Project-level stuck task timeout in milliseconds (undefined = disabled) */
|
||||
@@ -1125,9 +1125,41 @@ function TaskCardComponent({
|
||||
return;
|
||||
}
|
||||
|
||||
const trackedIssue = task.githubTracking?.enabled === true ? task.githubTracking.issue : undefined;
|
||||
let githubIssueAction: GithubIssueAction | undefined;
|
||||
if (trackedIssue?.owner && trackedIssue.repo && trackedIssue.number) {
|
||||
const issueRef = `${trackedIssue.owner}/${trackedIssue.repo}#${trackedIssue.number}`;
|
||||
const shouldCloseIssue = await confirm({
|
||||
title: "Linked GitHub Issue",
|
||||
message: `Choose what to do with ${issueRef} when deleting ${task.id}.\n\nClose the issue?`,
|
||||
confirmLabel: "Close Issue",
|
||||
cancelLabel: "More Options",
|
||||
});
|
||||
|
||||
if (shouldCloseIssue) {
|
||||
githubIssueAction = "close";
|
||||
} else {
|
||||
const shouldDeleteIssue = await confirm({
|
||||
title: "Delete Linked GitHub Issue",
|
||||
message: `Delete ${issueRef} on GitHub, or leave it unchanged?`,
|
||||
confirmLabel: "Delete Issue",
|
||||
cancelLabel: "Leave Unchanged",
|
||||
danger: true,
|
||||
});
|
||||
githubIssueAction = shouldDeleteIssue ? "delete" : "leave";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await onDeleteTask(task.id);
|
||||
addToast(`Deleted ${task.id}`, "success");
|
||||
if (githubIssueAction) {
|
||||
await onDeleteTask(task.id, { githubIssueAction });
|
||||
} else {
|
||||
await onDeleteTask(task.id);
|
||||
}
|
||||
const issueSuffix = trackedIssue?.owner && trackedIssue.repo && trackedIssue.number && githubIssueAction
|
||||
? ` and ${githubIssueAction === "close" ? "closed" : githubIssueAction === "delete" ? "deleted" : "left"} issue ${trackedIssue.owner}/${trackedIssue.repo}#${trackedIssue.number}`
|
||||
: "";
|
||||
addToast(`Deleted ${task.id}${issueSuffix}`, "success");
|
||||
} catch (err) {
|
||||
const conflict = extractDependencyDeleteConflict(err);
|
||||
if (!conflict || conflict.dependentIds.length === 0) {
|
||||
@@ -1148,13 +1180,13 @@ function TaskCardComponent({
|
||||
}
|
||||
|
||||
try {
|
||||
await onDeleteTask(task.id, { removeDependencyReferences: true });
|
||||
await onDeleteTask(task.id, { removeDependencyReferences: true, githubIssueAction });
|
||||
addToast(`Deleted ${task.id} after removing dependency references`, "success");
|
||||
} catch (retryErr) {
|
||||
addToast(`Failed to delete ${task.id}: ${getErrorMessage(retryErr)}`, "error");
|
||||
}
|
||||
}
|
||||
}, [addToast, confirm, onDeleteTask, task.id]);
|
||||
}, [addToast, confirm, onDeleteTask, task.githubTracking?.enabled, task.githubTracking?.issue, task.id]);
|
||||
|
||||
const handleOpenFiles = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -193,16 +193,16 @@
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.detail-provenance-context {
|
||||
max-width: 20ch;
|
||||
}
|
||||
|
||||
.detail-timestamps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.detail-provenance-context {
|
||||
max-width: 20ch;
|
||||
}
|
||||
|
||||
.detail-timestamp-item {
|
||||
align-items: baseline;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useOverlayDismiss } from "../hooks/useOverlayDismiss";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import type { Components } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import type { Task, TaskDetail, TaskAttachment, Column, MergeResult, Settings, GlobalSettings, AgentLogEntry, Agent, TaskPriority, TaskSourceIssue, WorkflowStepResult } from "@fusion/core";
|
||||
import type { Task, TaskDetail, TaskAttachment, Column, MergeResult, Settings, GlobalSettings, AgentLogEntry, Agent, TaskPriority, TaskSourceIssue, WorkflowStepResult, GithubIssueAction } from "@fusion/core";
|
||||
import {
|
||||
COLUMN_LABELS,
|
||||
DEFAULT_TASK_PRIORITY,
|
||||
@@ -267,7 +267,7 @@ export interface TaskDetailModalProps {
|
||||
onClose: () => void;
|
||||
onOpenDetail: (task: Task | TaskDetail) => void; // For clicking dependencies
|
||||
onMoveTask: (id: string, column: Column, optionsOrPosition?: { preserveProgress?: boolean } | number) => Promise<Task>;
|
||||
onDeleteTask: (id: string, options?: { removeDependencyReferences?: boolean }) => Promise<Task>;
|
||||
onDeleteTask: (id: string, options?: { removeDependencyReferences?: boolean; githubIssueAction?: GithubIssueAction }) => Promise<Task>;
|
||||
onMergeTask: (id: string) => Promise<MergeResult>;
|
||||
onRetryTask?: (id: string) => Promise<Task>;
|
||||
onResetTask?: (id: string) => Promise<Task>;
|
||||
@@ -1317,10 +1317,43 @@ export function TaskDetailContent({
|
||||
danger: true,
|
||||
});
|
||||
if (!shouldDelete) return;
|
||||
|
||||
const trackedIssue = task.githubTracking?.enabled === true ? task.githubTracking.issue : undefined;
|
||||
let githubIssueAction: GithubIssueAction | undefined;
|
||||
if (trackedIssue?.owner && trackedIssue.repo && trackedIssue.number) {
|
||||
const issueRef = `${trackedIssue.owner}/${trackedIssue.repo}#${trackedIssue.number}`;
|
||||
const shouldCloseIssue = await confirm({
|
||||
title: "Linked GitHub Issue",
|
||||
message: `Choose what to do with ${issueRef} when deleting ${task.id}.\n\nClose the issue?`,
|
||||
confirmLabel: "Close Issue",
|
||||
cancelLabel: "More Options",
|
||||
});
|
||||
|
||||
if (shouldCloseIssue) {
|
||||
githubIssueAction = "close";
|
||||
} else {
|
||||
const shouldDeleteIssue = await confirm({
|
||||
title: "Delete Linked GitHub Issue",
|
||||
message: `Delete ${issueRef} on GitHub, or leave it unchanged?`,
|
||||
confirmLabel: "Delete Issue",
|
||||
cancelLabel: "Leave Unchanged",
|
||||
danger: true,
|
||||
});
|
||||
githubIssueAction = shouldDeleteIssue ? "delete" : "leave";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await onDeleteTask(task.id);
|
||||
if (githubIssueAction) {
|
||||
await onDeleteTask(task.id, { githubIssueAction });
|
||||
} else {
|
||||
await onDeleteTask(task.id);
|
||||
}
|
||||
requestClose();
|
||||
addToast(`Deleted ${task.id}`, "info");
|
||||
const issueSuffix = trackedIssue?.owner && trackedIssue.repo && trackedIssue.number && githubIssueAction
|
||||
? ` and ${githubIssueAction === "close" ? "closed" : githubIssueAction === "delete" ? "deleted" : "left"} issue ${trackedIssue.owner}/${trackedIssue.repo}#${trackedIssue.number}`
|
||||
: "";
|
||||
addToast(`Deleted ${task.id}${issueSuffix}`, "info");
|
||||
} catch (err) {
|
||||
const conflict = extractDependencyDeleteConflict(err);
|
||||
if (!conflict || conflict.dependentIds.length === 0) {
|
||||
@@ -1341,14 +1374,14 @@ export function TaskDetailContent({
|
||||
}
|
||||
|
||||
try {
|
||||
await onDeleteTask(task.id, { removeDependencyReferences: true });
|
||||
await onDeleteTask(task.id, { removeDependencyReferences: true, githubIssueAction });
|
||||
requestClose();
|
||||
addToast(`Deleted ${task.id} after removing dependency references`, "info");
|
||||
} catch (retryErr) {
|
||||
addToast(getErrorMessage(retryErr), "error");
|
||||
}
|
||||
}
|
||||
}, [task.id, onDeleteTask, requestClose, addToast, confirm]);
|
||||
}, [task.githubTracking?.enabled, task.githubTracking?.issue, task.id, onDeleteTask, requestClose, addToast, confirm]);
|
||||
|
||||
const handleMerge = useCallback(async () => {
|
||||
const shouldMerge = await confirm({
|
||||
|
||||
Reference in New Issue
Block a user