Assign Agent no longer strands you at “Repository not linked to any board.”
This commit is contained in:
parent
f71e0767a4
commit
086fc8fc49
|
|
@ -8,6 +8,10 @@ import {
|
|||
useListAgentsApiV1AgentsGet,
|
||||
type listAgentsApiV1AgentsGetResponse,
|
||||
} from "@/api/generated/agents/agents";
|
||||
import {
|
||||
useListBoardsApiV1BoardsGet,
|
||||
type listBoardsApiV1BoardsGetResponse,
|
||||
} from "@/api/generated/boards/boards";
|
||||
import { ApiError } from "@/api/mutator";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -20,7 +24,11 @@ import {
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import type { ForgejoIssue, AssignIssueAgentResponse } from "@/lib/api-forgejo";
|
||||
import { assignIssueToAgent, getLinkedBoardsForRepository } from "@/lib/api-forgejo";
|
||||
import {
|
||||
assignIssueToAgent,
|
||||
getLinkedBoardsForRepository,
|
||||
linkBoardForgejoRepository,
|
||||
} from "@/lib/api-forgejo";
|
||||
|
||||
type LinkedBoard = { id: string; name: string };
|
||||
|
||||
|
|
@ -74,6 +82,8 @@ export function AssignIssueAgentDialog({
|
|||
const [linkedBoards, setLinkedBoards] = useState<LinkedBoard[]>([]);
|
||||
const [boardsLoading, setBoardsLoading] = useState(false);
|
||||
const [linkedBoardsLoaded, setLinkedBoardsLoaded] = useState(false);
|
||||
const [linkBoardId, setLinkBoardId] = useState("");
|
||||
const [isLinkingRepository, setIsLinkingRepository] = useState(false);
|
||||
|
||||
// Fetch only boards linked to this issue's repository — prevents picking a
|
||||
// board that the backend will reject with "not linked to any board".
|
||||
|
|
@ -110,10 +120,31 @@ export function AssignIssueAgentDialog({
|
|||
{ query: { enabled: open && !!boardId, refetchOnMount: "always" } },
|
||||
);
|
||||
|
||||
const boardsQuery = useListBoardsApiV1BoardsGet<
|
||||
listBoardsApiV1BoardsGetResponse,
|
||||
ApiError
|
||||
>(
|
||||
{ limit: 200 },
|
||||
{
|
||||
query: {
|
||||
enabled:
|
||||
open &&
|
||||
linkedBoardsLoaded &&
|
||||
!boardsLoading &&
|
||||
linkedBoards.length === 0,
|
||||
refetchOnMount: "always",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const agents =
|
||||
agentsQuery.data?.status === 200
|
||||
? (agentsQuery.data.data.items ?? [])
|
||||
: [];
|
||||
const allBoards =
|
||||
boardsQuery.data?.status === 200
|
||||
? (boardsQuery.data.data.items ?? [])
|
||||
: [];
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
|
|
@ -124,6 +155,8 @@ export function AssignIssueAgentDialog({
|
|||
setStartImmediately(true);
|
||||
setError(null);
|
||||
setLinkedBoardsLoaded(false);
|
||||
setLinkBoardId("");
|
||||
setIsLinkingRepository(false);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
|
|
@ -164,6 +197,32 @@ export function AssignIssueAgentDialog({
|
|||
}
|
||||
};
|
||||
|
||||
const handleLinkRepositoryToBoard = async () => {
|
||||
if (!issue || !linkBoardId) return;
|
||||
setIsLinkingRepository(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await linkBoardForgejoRepository(linkBoardId, issue.repository_id);
|
||||
const linkedBoard = allBoards.find((board) => board.id === linkBoardId);
|
||||
const nextBoard = {
|
||||
id: linkBoardId,
|
||||
name: linkedBoard?.name ?? "Selected board",
|
||||
};
|
||||
setLinkedBoards([nextBoard]);
|
||||
setBoardId(nextBoard.id);
|
||||
setLinkBoardId("");
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "Failed to link repository to board",
|
||||
);
|
||||
} finally {
|
||||
setIsLinkingRepository(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSwitch = (
|
||||
value: boolean,
|
||||
setter: (v: boolean) => void,
|
||||
|
|
@ -227,14 +286,51 @@ export function AssignIssueAgentDialog({
|
|||
use its Git Project repositories panel, or manage tracked
|
||||
repositories first.
|
||||
</p>
|
||||
<div className="mt-4 space-y-2">
|
||||
<label className="text-sm font-medium text-strong">
|
||||
Link to board <span className="text-[color:var(--danger)]">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={linkBoardId}
|
||||
onChange={(e) => setLinkBoardId(e.target.value)}
|
||||
disabled={boardsQuery.isLoading || isLinkingRepository}
|
||||
className="w-full rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-2 text-sm text-strong focus:outline-none focus:ring-2 focus:ring-[color:var(--accent)]"
|
||||
>
|
||||
<option value="">
|
||||
{boardsQuery.isLoading ? "Loading boards..." : "Select a board..."}
|
||||
</option>
|
||||
{allBoards.map((board) => (
|
||||
<option key={board.id} value={board.id}>
|
||||
{board.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{boardsQuery.isError ? (
|
||||
<p className="text-xs text-[color:var(--danger)]">
|
||||
Unable to load boards. You can still manage repositories
|
||||
from Git Projects.
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleLinkRepositoryToBoard}
|
||||
disabled={
|
||||
!linkBoardId || boardsQuery.isLoading || isLinkingRepository
|
||||
}
|
||||
>
|
||||
{isLinkingRepository ? "Linking..." : "Link and continue"}
|
||||
</Button>
|
||||
<Link
|
||||
href="/git-projects/repositories"
|
||||
className="mt-3 inline-flex h-9 items-center gap-2 rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-3 text-xs font-semibold text-strong transition hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]"
|
||||
className="inline-flex h-9 items-center gap-2 rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-3 text-xs font-semibold text-strong transition hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]"
|
||||
>
|
||||
Manage Git repositories
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="space-y-1.5">
|
||||
|
|
|
|||
Loading…
Reference in New Issue