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,
|
useListAgentsApiV1AgentsGet,
|
||||||
type listAgentsApiV1AgentsGetResponse,
|
type listAgentsApiV1AgentsGetResponse,
|
||||||
} from "@/api/generated/agents/agents";
|
} from "@/api/generated/agents/agents";
|
||||||
|
import {
|
||||||
|
useListBoardsApiV1BoardsGet,
|
||||||
|
type listBoardsApiV1BoardsGetResponse,
|
||||||
|
} from "@/api/generated/boards/boards";
|
||||||
import { ApiError } from "@/api/mutator";
|
import { ApiError } from "@/api/mutator";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
|
@ -20,7 +24,11 @@ import {
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import type { ForgejoIssue, AssignIssueAgentResponse } from "@/lib/api-forgejo";
|
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 };
|
type LinkedBoard = { id: string; name: string };
|
||||||
|
|
||||||
|
|
@ -74,6 +82,8 @@ export function AssignIssueAgentDialog({
|
||||||
const [linkedBoards, setLinkedBoards] = useState<LinkedBoard[]>([]);
|
const [linkedBoards, setLinkedBoards] = useState<LinkedBoard[]>([]);
|
||||||
const [boardsLoading, setBoardsLoading] = useState(false);
|
const [boardsLoading, setBoardsLoading] = useState(false);
|
||||||
const [linkedBoardsLoaded, setLinkedBoardsLoaded] = 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
|
// Fetch only boards linked to this issue's repository — prevents picking a
|
||||||
// board that the backend will reject with "not linked to any board".
|
// 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" } },
|
{ 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 =
|
const agents =
|
||||||
agentsQuery.data?.status === 200
|
agentsQuery.data?.status === 200
|
||||||
? (agentsQuery.data.data.items ?? [])
|
? (agentsQuery.data.data.items ?? [])
|
||||||
: [];
|
: [];
|
||||||
|
const allBoards =
|
||||||
|
boardsQuery.data?.status === 200
|
||||||
|
? (boardsQuery.data.data.items ?? [])
|
||||||
|
: [];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
|
|
@ -124,6 +155,8 @@ export function AssignIssueAgentDialog({
|
||||||
setStartImmediately(true);
|
setStartImmediately(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
setLinkedBoardsLoaded(false);
|
setLinkedBoardsLoaded(false);
|
||||||
|
setLinkBoardId("");
|
||||||
|
setIsLinkingRepository(false);
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [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 = (
|
const toggleSwitch = (
|
||||||
value: boolean,
|
value: boolean,
|
||||||
setter: (v: boolean) => void,
|
setter: (v: boolean) => void,
|
||||||
|
|
@ -227,14 +286,51 @@ export function AssignIssueAgentDialog({
|
||||||
use its Git Project repositories panel, or manage tracked
|
use its Git Project repositories panel, or manage tracked
|
||||||
repositories first.
|
repositories first.
|
||||||
</p>
|
</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
|
<Link
|
||||||
href="/git-projects/repositories"
|
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
|
Manage Git repositories
|
||||||
<ExternalLink className="h-3.5 w-3.5" />
|
<ExternalLink className="h-3.5 w-3.5" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue