feat: add reset action for integration watches (#1381)

This commit is contained in:
Carlos Florêncio
2026-06-15 18:54:16 +01:00
committed by GitHub
parent 214a8f5cce
commit 61acc82b74
60 changed files with 3158 additions and 193 deletions
+133 -6
View File
@@ -19,6 +19,7 @@ import { PRStatsPanel } from "./pr-stats";
import { useReviewWatches } from "@/hooks/domains/github/use-review-watches";
import { useIssueWatches } from "@/hooks/domains/github/use-issue-watches";
import { WorkspaceScopedSection } from "@/components/integrations/workspace-scoped-section";
import { ResetWatchDialog, useWatchResetController } from "@/components/watches/reset-watch-dialog";
import { cleanupMergedReviewTasks, cleanupClosedIssueTasks } from "@/lib/api/domains/github-api";
import type { ReviewWatch, IssueWatch } from "@/lib/types/github";
@@ -66,7 +67,15 @@ function CleanupNowButton({
}
function useWatchActions(workspaceId?: string | null) {
const { items: watches, create, update, remove, trigger } = useReviewWatches(workspaceId);
const {
items: watches,
create,
update,
remove,
trigger,
previewReset,
reset,
} = useReviewWatches(workspaceId);
const { toast } = useToast();
const handleDelete = useCallback(
@@ -116,11 +125,47 @@ function useWatchActions(workspaceId?: string | null) {
[update, toast],
);
return { watches, create, update, handleDelete, handleTrigger, handleToggleEnabled };
const handleReset = useCallback(
async (id: string, workspaceId: string) => {
try {
const { tasksDeleted } = await reset(id, workspaceId);
toast({
description:
tasksDeleted > 0
? `Reset complete — deleted ${tasksDeleted} task(s); next poll will re-import.`
: "Reset complete — next poll will re-import matches.",
variant: "success",
});
} catch {
toast({ description: "Failed to reset review watch", variant: "error" });
throw new Error("reset failed");
}
},
[reset, toast],
);
return {
watches,
create,
update,
previewReset,
handleDelete,
handleTrigger,
handleReset,
handleToggleEnabled,
};
}
function useIssueWatchActions(workspaceId?: string | null) {
const { items: watches, create, update, remove, trigger } = useIssueWatches(workspaceId);
const {
items: watches,
create,
update,
remove,
trigger,
previewReset,
reset,
} = useIssueWatches(workspaceId);
const { toast } = useToast();
const handleDelete = useCallback(
@@ -170,7 +215,35 @@ function useIssueWatchActions(workspaceId?: string | null) {
[update, toast],
);
return { watches, create, update, handleDelete, handleTrigger, handleToggleEnabled };
const handleReset = useCallback(
async (id: string, workspaceId: string) => {
try {
const { tasksDeleted } = await reset(id, workspaceId);
toast({
description:
tasksDeleted > 0
? `Reset complete — deleted ${tasksDeleted} task(s); next poll will re-import.`
: "Reset complete — next poll will re-import matches.",
variant: "success",
});
} catch {
toast({ description: "Failed to reset issue watch", variant: "error" });
throw new Error("reset failed");
}
},
[reset, toast],
);
return {
watches,
create,
update,
previewReset,
handleDelete,
handleTrigger,
handleReset,
handleToggleEnabled,
};
}
export function GitHubConnectionSection() {
@@ -232,17 +305,38 @@ export function GitHubIntegrationPage() {
// single flat table. Pass `undefined` to the hook so it fetches all rows; the
// dialog's create flow asks the user to pick the workspace.
function ReviewWatchSection() {
const { watches, create, update, handleDelete, handleTrigger, handleToggleEnabled } =
useWatchActions(undefined);
const {
watches,
create,
update,
previewReset,
handleDelete,
handleTrigger,
handleReset,
handleToggleEnabled,
} = useWatchActions(undefined);
const { toast } = useToast();
const [dialogOpen, setDialogOpen] = useState(false);
const [editingWatch, setEditingWatch] = useState<ReviewWatch | null>(null);
const resetCtrl = useWatchResetController<ReviewWatch>({
preview: (w) => previewReset(w.id, w.workspace_id),
reset: (w) => handleReset(w.id, w.workspace_id),
});
const handleEdit = useCallback((watch: ReviewWatch) => {
setEditingWatch(watch);
setDialogOpen(true);
}, []);
const { setResetting: setReviewResetting } = resetCtrl;
const onResetClick = useCallback(
(id: string) => {
const w = watches.find((item) => item.id === id);
if (w) setReviewResetting(w);
},
[watches, setReviewResetting],
);
return (
<>
<SettingsSection
@@ -273,6 +367,7 @@ function ReviewWatchSection() {
onEdit={handleEdit}
onDelete={handleDelete}
onTrigger={handleTrigger}
onReset={onResetClick}
onToggleEnabled={handleToggleEnabled}
/>
</CardContent>
@@ -292,6 +387,15 @@ function ReviewWatchSection() {
toast({ description: "Review watch updated", variant: "success" });
}}
/>
{resetCtrl.resetting && (
<ResetWatchDialog
open
onOpenChange={resetCtrl.onOpenChange}
integrationLabel="review watch"
previewLoader={resetCtrl.previewLoader}
onConfirm={resetCtrl.confirmReset}
/>
)}
</>
);
}
@@ -301,12 +405,25 @@ function IssueWatchSection() {
const { toast } = useToast();
const [dialogOpen, setDialogOpen] = useState(false);
const [editingWatch, setEditingIssueWatch] = useState<IssueWatch | null>(null);
const resetCtrl = useWatchResetController<IssueWatch>({
preview: (w) => issueActions.previewReset(w.id, w.workspace_id),
reset: (w) => issueActions.handleReset(w.id, w.workspace_id),
});
const handleEdit = useCallback((watch: IssueWatch) => {
setEditingIssueWatch(watch);
setDialogOpen(true);
}, []);
const { setResetting: setIssueResetting } = resetCtrl;
const onResetClick = useCallback(
(id: string) => {
const w = issueActions.watches.find((item) => item.id === id);
if (w) setIssueResetting(w);
},
[issueActions.watches, setIssueResetting],
);
return (
<>
<SettingsSection
@@ -337,6 +454,7 @@ function IssueWatchSection() {
onEdit={handleEdit}
onDelete={issueActions.handleDelete}
onTrigger={issueActions.handleTrigger}
onReset={onResetClick}
onToggleEnabled={issueActions.handleToggleEnabled}
/>
</CardContent>
@@ -355,6 +473,15 @@ function IssueWatchSection() {
toast({ description: "Issue watch updated", variant: "success" });
}}
/>
{resetCtrl.resetting && (
<ResetWatchDialog
open
onOpenChange={resetCtrl.onOpenChange}
integrationLabel="issue watch"
previewLoader={resetCtrl.previewLoader}
onConfirm={resetCtrl.confirmReset}
/>
)}
</>
);
}