From c34056555f42c2ddae88e8fd9ec8923d01c1e1e4 Mon Sep 17 00:00:00 2001 From: Arunavo Ray Date: Tue, 24 Feb 2026 09:59:21 +0530 Subject: [PATCH] Add bulk re-run metadata action --- README.md | 3 +- src/components/repositories/Repository.tsx | 91 ++++++++++++++++ src/pages/api/job/reset-metadata.ts | 116 +++++++++++++++++++++ src/types/reset-metadata.ts | 13 +++ 4 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 src/pages/api/job/reset-metadata.ts create mode 100644 src/types/reset-metadata.ts diff --git a/README.md b/README.md index acb50be..13dacf0 100644 --- a/README.md +++ b/README.md @@ -304,7 +304,8 @@ If using a reverse proxy (e.g., nginx proxy manager) and experiencing issues wit If you enable metadata options (issues/PRs/labels/milestones/releases) after repositories were already mirrored: 1. Go to **Repositories**, select the repositories, and click **Sync** to run a fresh sync pass. -2. If some repositories still miss metadata, reset metadata sync state in SQLite and sync again: +2. For a full metadata refresh, use **Re-run Metadata** on selected repositories. This clears metadata sync state for those repos and immediately starts Sync. +3. If some repositories still miss metadata, reset metadata sync state in SQLite and sync again: ```bash sqlite3 data/gitea-mirror.db "UPDATE repositories SET metadata = NULL;" diff --git a/src/components/repositories/Repository.tsx b/src/components/repositories/Repository.tsx index 549abcc..f10e2c7 100644 --- a/src/components/repositories/Repository.tsx +++ b/src/components/repositories/Repository.tsx @@ -44,6 +44,7 @@ import { toast } from "sonner"; import type { SyncRepoRequest, SyncRepoResponse } from "@/types/sync"; import { OwnerCombobox, OrganizationCombobox } from "./RepositoryComboboxes"; import type { RetryRepoRequest, RetryRepoResponse } from "@/types/retry"; +import type { ResetMetadataRequest, ResetMetadataResponse } from "@/types/reset-metadata"; import AddRepositoryDialog from "./AddRepositoryDialog"; import { useLiveRefresh } from "@/hooks/useLiveRefresh"; @@ -378,6 +379,67 @@ export default function Repository() { } }; + const handleBulkRerunMetadata = async () => { + if (selectedRepoIds.size === 0) return; + + const selectedRepos = repositories.filter(repo => repo.id && selectedRepoIds.has(repo.id)); + const eligibleRepos = selectedRepos.filter( + repo => ["mirrored", "synced", "archived"].includes(repo.status) + ); + + if (eligibleRepos.length === 0) { + toast.info("No eligible repositories to re-run metadata in selection"); + return; + } + + const repoIds = eligibleRepos.map(repo => repo.id as string); + + setLoadingRepoIds(prev => { + const newSet = new Set(prev); + repoIds.forEach(id => newSet.add(id)); + return newSet; + }); + + try { + const resetPayload: ResetMetadataRequest = { + userId: user?.id || "", + repositoryIds: repoIds, + }; + + const resetResponse = await apiRequest("/job/reset-metadata", { + method: "POST", + data: resetPayload, + }); + + if (!resetResponse.success) { + showErrorToast(resetResponse.error || "Failed to reset metadata state", toast); + return; + } + + const syncResponse = await apiRequest("/job/sync-repo", { + method: "POST", + data: { userId: user?.id, repositoryIds: repoIds }, + }); + + if (syncResponse.success) { + toast.success(`Re-running metadata for ${repoIds.length} repositories`); + setRepositories(prevRepos => + prevRepos.map(repo => { + const updated = syncResponse.repositories.find(r => r.id === repo.id); + return updated ? updated : repo; + }) + ); + setSelectedRepoIds(new Set()); + } else { + showErrorToast(syncResponse.error || "Error starting metadata re-sync", toast); + } + } catch (error) { + showErrorToast(error, toast); + } finally { + setLoadingRepoIds(new Set()); + } + }; + const handleBulkRetry = async () => { if (selectedRepoIds.size === 0) return; @@ -806,6 +868,10 @@ export default function Repository() { if (selectedRepos.some(repo => repo.status === "mirrored" || repo.status === "synced")) { actions.push('sync'); } + + if (selectedRepos.some(repo => ["mirrored", "synced", "archived"].includes(repo.status))) { + actions.push('rerun-metadata'); + } // Check if any selected repos are failed if (selectedRepos.some(repo => repo.status === "failed")) { @@ -834,6 +900,7 @@ export default function Repository() { return { mirror: selectedRepos.filter(repo => repo.status === "imported" || repo.status === "failed").length, sync: selectedRepos.filter(repo => repo.status === "mirrored" || repo.status === "synced").length, + rerunMetadata: selectedRepos.filter(repo => ["mirrored", "synced", "archived"].includes(repo.status)).length, retry: selectedRepos.filter(repo => repo.status === "failed").length, ignore: selectedRepos.filter(repo => repo.status !== "ignored").length, include: selectedRepos.filter(repo => repo.status === "ignored").length, @@ -1157,6 +1224,18 @@ export default function Repository() { Sync ({actionCounts.sync}) )} + + {availableActions.includes('rerun-metadata') && ( + + )} {availableActions.includes('retry') && ( )} + + {availableActions.includes('rerun-metadata') && ( + + )} {availableActions.includes('retry') && (