diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f00117c..0533fd3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Changed the default `/repos` pagination size to 20. [#706](https://github.com/sourcebot-dev/sourcebot/pull/706) +- Added Trigger Sync to /repos dropdown menu [#710](https://github.com/sourcebot-dev/sourcebot/pull/710) ## [4.10.7] - 2025-12-29 diff --git a/packages/web/src/app/[domain]/repos/components/repoActionsDropdown.tsx b/packages/web/src/app/[domain]/repos/components/repoActionsDropdown.tsx new file mode 100644 index 00000000..11653a76 --- /dev/null +++ b/packages/web/src/app/[domain]/repos/components/repoActionsDropdown.tsx @@ -0,0 +1,83 @@ +"use client" + +import { Button } from "@/components/ui/button" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants" +import { getCodeHostInfoForRepo, isServiceError } from "@/lib/utils" +import { ExternalLink, MoreHorizontal } from "lucide-react" +import Link from "next/link" +import { useState } from "react" +import { indexRepo } from "@/features/workerApi/actions" +import { useRouter } from "next/navigation" +import { useToast } from "@/components/hooks/use-toast" +import type { Repo } from "./reposTable" + +interface RepoActionsDropdownProps { + repo: Repo +} + +export const RepoActionsDropdown = ({ repo }: RepoActionsDropdownProps) => { + const [isSyncing, setIsSyncing] = useState(false) + const router = useRouter() + const { toast } = useToast() + + const codeHostInfo = getCodeHostInfoForRepo({ + codeHostType: repo.codeHostType, + name: repo.name, + displayName: repo.displayName ?? undefined, + webUrl: repo.webUrl ?? undefined, + }) + + const handleTriggerSync = async () => { + setIsSyncing(true) + const response = await indexRepo(repo.id) + + if (!isServiceError(response)) { + const { jobId } = response + toast({ + description: `✅ Repository sync triggered successfully. Job ID: ${jobId}`, + }) + router.refresh() + } else { + toast({ + description: `❌ Failed to sync repository. ${response.message}`, + }) + } + + setIsSyncing(false) + } + + return ( + + + + + + Actions + + View details + + + Trigger sync + + {repo.webUrl && ( + <> + + + + Open in {codeHostInfo.codeHostName} + + + + + )} + + + ) +} diff --git a/packages/web/src/app/[domain]/repos/components/reposTable.tsx b/packages/web/src/app/[domain]/repos/components/reposTable.tsx index f75af96a..0578b95e 100644 --- a/packages/web/src/app/[domain]/repos/components/reposTable.tsx +++ b/packages/web/src/app/[domain]/repos/components/reposTable.tsx @@ -2,19 +2,11 @@ import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants" -import { cn, getCodeHostCommitUrl, getCodeHostIcon, getCodeHostInfoForRepo, getRepoImageSrc } from "@/lib/utils" +import { cn, getCodeHostCommitUrl, getCodeHostIcon, getRepoImageSrc, isServiceError } from "@/lib/utils" import { type ColumnDef, type VisibilityState, @@ -23,7 +15,7 @@ import { useReactTable, } from "@tanstack/react-table" import { cva } from "class-variance-authority" -import { ArrowDown, ArrowUp, ArrowUpDown, ExternalLink, Loader2, MoreHorizontal, RefreshCwIcon } from "lucide-react" +import { ArrowDown, ArrowUp, ArrowUpDown, Loader2, RefreshCwIcon } from "lucide-react" import Image from "next/image" import Link from "next/link" import { useEffect, useRef, useState } from "react" @@ -35,6 +27,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip import { NotificationDot } from "../../components/notificationDot" import { CodeHostType } from "@sourcebot/db" import { useHotkeys } from "react-hotkeys-hook" +import { indexRepo } from "@/features/workerApi/actions" +import { RepoActionsDropdown } from "./repoActionsDropdown" // @see: https://v0.app/chat/repo-indexing-status-uhjdDim8OUS @@ -84,6 +78,7 @@ interface ColumnsContext { onSortChange: (sortBy: string) => void; currentSortBy?: string; currentSortOrder: string; + onTriggerSync: (repoId: number) => void; } export const getColumns = (context: ColumnsContext): ColumnDef[] => [ @@ -253,40 +248,7 @@ export const getColumns = (context: ColumnsContext): ColumnDef[] => [ enableHiding: false, cell: ({ row }) => { const repo = row.original - const codeHostInfo = getCodeHostInfoForRepo({ - codeHostType: repo.codeHostType, - name: repo.name, - displayName: repo.displayName ?? undefined, - webUrl: repo.webUrl ?? undefined, - }); - - return ( - - - - - - Actions - - View details - - {repo.webUrl && ( - <> - - - - Open in {codeHostInfo.codeHostName} - - - - - )} - - - ) + return }, }, ] @@ -386,12 +348,29 @@ export const ReposTable = ({ router.replace(`${pathname}?${params.toString()}`); }; + const handleTriggerSync = async (repoId: number) => { + const response = await indexRepo(repoId); + + if (!isServiceError(response)) { + const { jobId } = response; + toast({ + description: `✅ Repository sync triggered successfully. Job ID: ${jobId}`, + }); + router.refresh(); + } else { + toast({ + description: `❌ Failed to sync repository. ${response.message}`, + }); + } + }; + const totalPages = Math.ceil(totalCount / pageSize); const columns = getColumns({ onSortChange: handleSortChange, currentSortBy: initialSortBy, currentSortOrder: initialSortOrder, + onTriggerSync: handleTriggerSync }); const table = useReactTable({