case-create: surface Gitea repo result + UI retry button
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m29s

The auto-creation in case_create had two failure modes that combined to
make repos silently missing: a stale GITEA_TOKEN returning 401, and the
outer try/except in case_create that swallowed every exception with a
bare pass. Result: cases like 8174-24 ended up with a local git repo and
Paperclip project but no Gitea repo, with no signal anywhere.

_setup_gitea_remote now returns {ok, url, error} and never raises; the
result is attached to the case JSON and the FastAPI endpoint logs a
warning when ok=false. The UI gets a "צור ריפו ב-Gitea" button on the
case header that appears only when the repo or remote is missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 18:12:05 +00:00
parent 7600810639
commit fa70944ed4
5 changed files with 117 additions and 13 deletions

View File

@@ -4,6 +4,7 @@ import { Badge } from "@/components/ui/badge";
import { StatusBadge } from "@/components/cases/status-badge";
import { SyncIndicator } from "@/components/cases/sync-indicator";
import { CaseArchiveAction } from "@/components/cases/case-archive-action";
import { CreateRepoButton } from "@/components/cases/create-repo-button";
import {
PRACTICE_AREA_LABELS,
APPEAL_SUBTYPE_LABELS,
@@ -67,6 +68,7 @@ export function CaseHeader({ data }: { data?: CaseDetail }) {
archivedAt={data.archived_at}
/>
)}
<CreateRepoButton data={data} />
</div>
<h1 className="text-navy text-xl font-bold leading-snug max-w-2xl mb-0">
{data?.title ?? "טוען…"}

View File

@@ -0,0 +1,59 @@
"use client";
import { toast } from "sonner";
import { Cloud, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
useCreateGiteaRepo,
useGitStatus,
type CaseDetail,
} from "@/lib/api/cases";
export function CreateRepoButton({ data }: { data?: CaseDetail }) {
const { data: gitStatus } = useGitStatus(data?.case_number);
const createRepo = useCreateGiteaRepo(data?.case_number);
if (!data?.case_number || !gitStatus) return null;
/* Show only when something is actually missing — repo, remote, or
* unpushed commits. If everything is in sync, the SyncIndicator already
* communicates that. */
const needsRepo = gitStatus.error === "no_repo" || !gitStatus.has_remote;
if (!needsRepo) return null;
function handleClick() {
if (!data) return;
createRepo.mutate(
{ title: data.title ?? "", description: data.subject ?? "" },
{
onSuccess: (res) =>
toast.success(
res.pushed
? `הריפו נוצר ב-Gitea: ${res.repo_url}`
: `הריפו נוצר אך ה-push נכשל. בדוק את הלוגים של ה-backend.`,
),
onError: (err) =>
toast.error(
err instanceof Error ? err.message : "יצירת הריפו נכשלה",
),
},
);
}
return (
<Button
variant="outline"
size="sm"
onClick={handleClick}
disabled={createRepo.isPending}
title="יצירת ריפו ב-Gitea וקישור התיק המקומי אליו"
>
{createRepo.isPending ? (
<Loader2 className="me-1 h-3.5 w-3.5 animate-spin" />
) : (
<Cloud className="me-1 h-3.5 w-3.5" />
)}
צור ריפו ב-Gitea
</Button>
);
}

View File

@@ -217,6 +217,30 @@ export function useGitStatus(caseNumber: string | undefined) {
});
}
export type CreateGiteaRepoResult = {
repo_url: string;
clone_url: string;
pushed: boolean;
};
export function useCreateGiteaRepo(caseNumber: string | undefined) {
const qc = useQueryClient();
return useMutation({
mutationFn: (input: { title: string; description?: string }) =>
apiRequest<CreateGiteaRepoResult>(`/api/integrations/gitea/create-repo`, {
method: "POST",
body: {
case_number: caseNumber,
title: input.title,
description: input.description ?? "",
},
}),
onSuccess: () => {
qc.invalidateQueries({ queryKey: [...casesKeys.all, "git-status", caseNumber ?? ""] });
},
});
}
export type StartWorkflowResult = {
case_number: string;
status: string;