Files
kandev/apps/web/components/github/pr-ci-automation-controls.tsx

175 lines
5.5 KiB
TypeScript

"use client";
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { useToast } from "@/components/toast-provider";
import { useTaskCIAutomationOptions } from "@/hooks/domains/github/use-task-ci-options";
import {
findCIAutomationStateForPR,
findPRAutomationOptionsForPR,
deriveCIAutomationQueueState,
} from "@/lib/github/ci-automation";
import type { TaskCIAutomationPatch, TaskPR } from "@/lib/types/github";
import { CIAutomationPromptDialog } from "@/components/github/pr-ci-automation-prompt-dialog";
import {
CIAutomationErrorRow,
CIAutomationHeader,
CIAutomationOptionRows,
} from "@/components/github/pr-ci-automation-rows";
export function PRCIAutomationControls({ pr }: { pr: TaskPR }) {
const { options, loading, saving, error, refresh, update, resetPrompt, retryMerge } =
useTaskCIAutomationOptions(pr.task_id);
const { toast } = useToast();
const { t } = useTranslation();
const [promptOpen, setPromptOpen] = useState(false);
const [promptDraft, setPromptDraft] = useState("");
const automationState = findCIAutomationStateForPR(options?.pr_states, pr);
const prOptions = findPRAutomationOptionsForPR(options?.pr_options, pr);
const queueState = deriveCIAutomationQueueState(pr, prOptions, automationState);
const openPromptEditor = useCallback(() => {
setPromptDraft(options?.auto_fix_prompt_override ?? options?.effective_auto_fix_prompt ?? "");
setPromptOpen(true);
}, [options]);
const reportError = useCallback(
(description: string) => {
toast({ description, variant: "error" });
},
[toast],
);
// The five automation switches are scoped to this PR; every patch from the
// switch rows below carries this PR's identity so the backend applies it
// to this PR only instead of fanning out to every linked PR.
const patchOption = useCallback(
(patch: TaskCIAutomationPatch) => {
const scoped: TaskCIAutomationPatch = {
...patch,
repository_id: pr.repository_id ?? "",
pr_number: pr.pr_number,
};
Promise.resolve(update(scoped)).catch(() =>
reportError(t("github:failedToUpdateCiAutomation")),
);
},
[pr, reportError, t, update],
);
// The auto-fix prompt override stays task-level (applies to every linked
// PR), so this patch intentionally omits PR identity.
const savePrompt = useCallback(() => {
const value = promptDraft.trim();
if (!value) return;
Promise.resolve(update({ auto_fix_prompt_override: value }))
.then(() => setPromptOpen(false))
.catch(() => reportError(t("github:failedToSaveAutoFixPrompt")));
}, [promptDraft, reportError, t, update]);
const useDefaultPrompt = useCallback(() => {
Promise.resolve(resetPrompt())
.then(() => setPromptOpen(false))
.catch(() => reportError(t("github:failedToResetAutoFixPrompt")));
}, [reportError, resetPrompt, t]);
const retryLoad = useCallback(() => {
Promise.resolve(refresh()).catch(() =>
reportError(t("github:failedToLoadCiAutomationOptions")),
);
}, [refresh, reportError, t]);
const retryAutoMerge = useCallback(() => {
Promise.resolve(retryMerge(pr.repository_id ?? "", pr.pr_number))
.then(() => toast({ description: t("github:autoMergeRetryAccepted") }))
.catch(() => reportError(t("github:failedToRetryAutoMerge")));
}, [pr.pr_number, pr.repository_id, reportError, retryMerge, t, toast]);
const disabled = loading || saving || !options;
return (
<div
data-testid="pr-ci-automation-controls"
className="flex flex-col gap-1 border-t border-border/50 pt-2"
>
<CIAutomationHeader
pr={pr}
queueState={queueState}
disabled={!options}
onEditPrompt={openPromptEditor}
/>
<CIAutomationOptionRows
pr={pr}
prOptions={prOptions}
autoFixMaxRounds={options?.auto_fix_max_rounds}
disabled={disabled}
patchOption={patchOption}
automationState={automationState}
/>
<PRCIAutomationErrors
storedError={automationState?.last_error}
storedErrorKind={automationState?.last_error_kind}
loadError={error}
loading={loading}
saving={saving}
retryLabel={t("github:retry")}
refreshLabel={t("github:refresh")}
onRetryMerge={retryAutoMerge}
onRefresh={retryLoad}
/>
<CIAutomationPromptDialog
open={promptOpen}
prompt={promptDraft}
saving={saving}
onPromptChange={setPromptDraft}
onClose={() => setPromptOpen(false)}
onSave={savePrompt}
onReset={useDefaultPrompt}
/>
</div>
);
}
function PRCIAutomationErrors({
storedError,
storedErrorKind,
loadError,
loading,
saving,
retryLabel,
refreshLabel,
onRetryMerge,
onRefresh,
}: {
storedError?: string | null;
storedErrorKind?: string;
loadError?: string | null;
loading: boolean;
saving: boolean;
retryLabel: string;
refreshLabel: string;
onRetryMerge: () => void;
onRefresh: () => void;
}) {
const mergeError = storedErrorKind === "auto_merge";
return (
<>
{storedError && (
<CIAutomationErrorRow
error={storedError}
loading={loading || saving}
actionLabel={mergeError ? retryLabel : refreshLabel}
onAction={mergeError ? onRetryMerge : onRefresh}
/>
)}
{loadError && (
<CIAutomationErrorRow
error={loadError}
loading={loading}
actionLabel={refreshLabel}
onAction={onRefresh}
/>
)}
</>
);
}