mirror of
https://github.com/vee1e/gitivity.git
synced 2026-09-01 19:07:12 +00:00
add better pb and sane defaultsz
This commit is contained in:
parent
635dc571d5
commit
28f3fa9a2c
4 changed files with 366 additions and 108 deletions
96
src/App.tsx
96
src/App.tsx
|
|
@ -36,6 +36,7 @@ import type {
|
|||
} from "./types";
|
||||
import { UserStatsModal } from "./components/UserStatsModal";
|
||||
import { TokenSettings } from "./components/TokenSettings";
|
||||
import { LoadingProgressPanel } from "./components/LoadingProgressPanel";
|
||||
import { hasGitHubToken } from "./utils/env";
|
||||
import { ThemeProvider } from "./contexts/ThemeContext";
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ function AppContent() {
|
|||
const [stats, setStats] = useState<RepoStats | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [timeFilter, setTimeFilter] = useState<TimeFilter>("1m");
|
||||
const [timeFilter, setTimeFilter] = useState<TimeFilter>("6m");
|
||||
const [selectedUser, setSelectedUser] = useState<UserStats | null>(null);
|
||||
const [loadingUser, setLoadingUser] = useState(false);
|
||||
const [showPRs, setShowPRs] = useState(false);
|
||||
|
|
@ -999,65 +1000,17 @@ function AppContent() {
|
|||
border: "1px solid var(--border)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="text-center max-w-md w-full px-4">
|
||||
<Loader2
|
||||
className="w-8 h-8 animate-spin mx-auto mb-4"
|
||||
style={{ color: "var(--text-muted)" }}
|
||||
/>
|
||||
<p
|
||||
className="text-sm font-medium mb-3"
|
||||
style={{ color: "var(--text)" }}
|
||||
>
|
||||
{loadingProgress}
|
||||
</p>
|
||||
|
||||
{structuredProgress && (
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between text-[10px] mb-1.5">
|
||||
<span
|
||||
className="uppercase"
|
||||
style={{ color: "var(--text-muted)" }}
|
||||
>
|
||||
{structuredProgress.stage}
|
||||
</span>
|
||||
<span style={{ color: "var(--text-dim)" }}>
|
||||
{structuredProgress.percentage}%
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="w-full h-1.5 rounded-full overflow-hidden"
|
||||
style={{ background: "var(--bg-tertiary)" }}
|
||||
>
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-300 ease-out"
|
||||
style={{
|
||||
background: "var(--primary)",
|
||||
width: `${structuredProgress.percentage}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{structuredProgress.total > 1 && (
|
||||
<div
|
||||
className="text-[10px] mt-1.5 text-right"
|
||||
style={{ color: "var(--text-dim)" }}
|
||||
>
|
||||
{structuredProgress.completed} /{" "}
|
||||
{structuredProgress.total} units
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!structuredProgress && (
|
||||
<p
|
||||
className="text-xs mt-1"
|
||||
style={{ color: "var(--text-dim)" }}
|
||||
>
|
||||
This may take a moment for large repositories
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-3xl mx-auto py-8 px-2">
|
||||
<LoadingProgressPanel
|
||||
mode={searchMode === "org" ? "org" : "repo"}
|
||||
progress={structuredProgress}
|
||||
fallbackMessage={
|
||||
loadingProgress ||
|
||||
(searchMode === "org"
|
||||
? "Preparing organization-wide analysis..."
|
||||
: "Preparing repository analysis...")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1068,22 +1021,13 @@ function AppContent() {
|
|||
className="fixed inset-0 flex items-center justify-center z-50"
|
||||
style={{ background: "rgba(0,0,0,0.8)" }}
|
||||
>
|
||||
<div
|
||||
className="p-6 rounded-lg"
|
||||
style={{
|
||||
background: "var(--bg-secondary)",
|
||||
border: "1px solid var(--border)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Loader2
|
||||
className="w-5 h-5 animate-spin"
|
||||
style={{ color: "var(--text-muted)" }}
|
||||
/>
|
||||
<p style={{ color: "var(--text)" }}>
|
||||
Loading user statistics...
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full max-w-2xl px-4">
|
||||
<LoadingProgressPanel
|
||||
mode="member"
|
||||
progress={structuredProgress}
|
||||
fallbackMessage={loadingProgress || "Loading user statistics..."}
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
213
src/components/LoadingProgressPanel.tsx
Normal file
213
src/components/LoadingProgressPanel.tsx
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import { Check, Circle, Loader2 } from "lucide-react";
|
||||
import type { LoadingProgressState } from "../utils/github";
|
||||
|
||||
type LoadingMode = "repo" | "org" | "member";
|
||||
|
||||
interface LoadingStep {
|
||||
key: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface LoadingProgressPanelProps {
|
||||
mode: LoadingMode;
|
||||
progress: LoadingProgressState | null;
|
||||
fallbackMessage: string;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const MODE_STEPS: Record<LoadingMode, LoadingStep[]> = {
|
||||
repo: [
|
||||
{ key: "repo", label: "Repository" },
|
||||
{ key: "collaborators", label: "Collaborators" },
|
||||
{ key: "repo-prs", label: "Pull Requests" },
|
||||
{ key: "processing", label: "Contributors" },
|
||||
{ key: "completed", label: "Completed" },
|
||||
],
|
||||
org: [
|
||||
{ key: "org-repos", label: "Repositories" },
|
||||
{ key: "org-scan", label: "Organization Scan" },
|
||||
{ key: "finalizing", label: "Finalizing" },
|
||||
{ key: "completed", label: "Completed" },
|
||||
],
|
||||
member: [
|
||||
{ key: "user", label: "Profile" },
|
||||
{ key: "user-prs", label: "Pull Requests" },
|
||||
{ key: "user-processing", label: "Repositories" },
|
||||
{ key: "maintainer-check", label: "Maintainer Check" },
|
||||
{ key: "completed", label: "Completed" },
|
||||
],
|
||||
};
|
||||
|
||||
const MODE_LABEL: Record<LoadingMode, string> = {
|
||||
repo: "Repository Analysis",
|
||||
org: "Organization Analysis",
|
||||
member: "Member Analysis",
|
||||
};
|
||||
|
||||
const prettifyStage = (stage: string): string =>
|
||||
stage
|
||||
.split("-")
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join(" ");
|
||||
|
||||
const getActiveIndex = (
|
||||
steps: LoadingStep[],
|
||||
stage: string | undefined,
|
||||
percentage: number,
|
||||
): number => {
|
||||
if (stage) {
|
||||
const exactMatchIndex = steps.findIndex((step) => step.key === stage);
|
||||
if (exactMatchIndex >= 0) {
|
||||
return exactMatchIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (percentage >= 100) {
|
||||
return steps.length - 1;
|
||||
}
|
||||
|
||||
if (percentage <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const estimatedIndex = Math.floor((percentage / 100) * steps.length);
|
||||
return Math.min(steps.length - 1, Math.max(0, estimatedIndex));
|
||||
};
|
||||
|
||||
const getStepStatus = (
|
||||
index: number,
|
||||
activeIndex: number,
|
||||
isComplete: boolean,
|
||||
): "pending" | "active" | "completed" => {
|
||||
if (isComplete && index <= activeIndex) {
|
||||
return "completed";
|
||||
}
|
||||
if (index < activeIndex) {
|
||||
return "completed";
|
||||
}
|
||||
if (index === activeIndex) {
|
||||
return "active";
|
||||
}
|
||||
return "pending";
|
||||
};
|
||||
|
||||
export function LoadingProgressPanel({
|
||||
mode,
|
||||
progress,
|
||||
fallbackMessage,
|
||||
compact = false,
|
||||
}: LoadingProgressPanelProps) {
|
||||
const steps = MODE_STEPS[mode];
|
||||
const percentage = progress?.percentage ?? 0;
|
||||
const safePercentage = Math.min(100, Math.max(0, percentage));
|
||||
const activeIndex = getActiveIndex(steps, progress?.stage, safePercentage);
|
||||
const isComplete = progress?.stage === "completed" || safePercentage >= 100;
|
||||
const stageLabel =
|
||||
steps.find((step) => step.key === progress?.stage)?.label ??
|
||||
(progress?.stage ? prettifyStage(progress.stage) : "Initializing");
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`rounded-lg border relative overflow-hidden ${compact ? "p-4" : "p-5"}`}
|
||||
style={{
|
||||
background: "linear-gradient(180deg, var(--bg-secondary), var(--bg-tertiary))",
|
||||
borderColor: "var(--border)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute top-0 left-0 h-px w-full"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(90deg, transparent 0%, var(--text-muted) 30%, var(--primary) 50%, var(--text-muted) 70%, transparent 100%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between gap-3 mb-2">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span className="loading-signal-dot" />
|
||||
<span
|
||||
className="text-[10px] uppercase tracking-[0.12em] truncate"
|
||||
style={{ color: "var(--text-dim)" }}
|
||||
>
|
||||
{MODE_LABEL[mode]}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs font-medium" style={{ color: "var(--text-muted)" }}>
|
||||
{safePercentage}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
className={`${compact ? "text-sm" : "text-base"} font-medium leading-snug`}
|
||||
style={{ color: "var(--text)" }}
|
||||
>
|
||||
{progress?.message || fallbackMessage}
|
||||
</p>
|
||||
|
||||
<div className="mt-3">
|
||||
<div className="h-2 rounded-full overflow-hidden loading-progress-track">
|
||||
<div
|
||||
className="h-full loading-progress-fill"
|
||||
style={{
|
||||
width: `${Math.max(3, safePercentage)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="mt-1.5 text-[10px] flex items-center justify-between"
|
||||
style={{ color: "var(--text-dim)" }}
|
||||
>
|
||||
<span>{stageLabel}</span>
|
||||
<span>
|
||||
{progress && progress.total > 1
|
||||
? `${Math.min(progress.completed, progress.total)}/${progress.total}`
|
||||
: isComplete
|
||||
? "Done"
|
||||
: "In progress"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`mt-4 grid gap-2 ${compact ? "grid-cols-2" : mode === "org" ? "grid-cols-2 md:grid-cols-4" : "grid-cols-2 md:grid-cols-5"}`}
|
||||
>
|
||||
{steps.map((step, index) => {
|
||||
const status = getStepStatus(index, activeIndex, isComplete);
|
||||
const isActive = status === "active";
|
||||
const isCompleted = status === "completed";
|
||||
|
||||
return (
|
||||
<div
|
||||
key={step.key}
|
||||
className="rounded-md px-2.5 py-2 flex items-center gap-2 border min-w-0"
|
||||
style={{
|
||||
background: isActive ? "var(--bg-secondary)" : "var(--bg)",
|
||||
borderColor: isCompleted || isActive ? "var(--text-muted)" : "var(--border-subtle)",
|
||||
opacity: isCompleted || isActive ? 1 : 0.72,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="flex-shrink-0"
|
||||
style={{ color: isCompleted || isActive ? "var(--text-muted)" : "var(--text-dim)" }}
|
||||
>
|
||||
{isCompleted ? (
|
||||
<Check className="w-3.5 h-3.5" />
|
||||
) : isActive ? (
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
) : (
|
||||
<Circle className="w-3.5 h-3.5" />
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className="text-[10px] uppercase tracking-wide truncate"
|
||||
style={{ color: isCompleted || isActive ? "var(--text)" : "var(--text-dim)" }}
|
||||
>
|
||||
{step.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -157,6 +157,66 @@ body {
|
|||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes loadingSweep {
|
||||
0% {
|
||||
transform: translateX(-120%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(220%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loadingPulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.45;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-signal-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 9999px;
|
||||
background: var(--text-muted);
|
||||
animation: loadingPulse 1.15s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.loading-progress-track {
|
||||
background: linear-gradient(180deg, var(--bg) 0%, var(--bg-secondary) 100%);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.loading-progress-fill {
|
||||
position: relative;
|
||||
border-radius: 9999px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--text-dim) 0%,
|
||||
var(--text-muted) 45%,
|
||||
var(--primary) 100%
|
||||
);
|
||||
box-shadow: 0 0 14px rgba(160, 160, 160, 0.2);
|
||||
transition: width 280ms cubic-bezier(0.2, 0.9, 0.2, 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loading-progress-fill::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 36%;
|
||||
background: linear-gradient(
|
||||
110deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.34) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
animation: loadingSweep 1.4s linear infinite;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ async function fetchMaintainers(
|
|||
if (!quiet) {
|
||||
emitLoadingProgress("Fetching collaborators...", {
|
||||
stage: "collaborators",
|
||||
percentage: 5,
|
||||
percentage: 18,
|
||||
completed: 0,
|
||||
total: 1,
|
||||
});
|
||||
|
|
@ -311,7 +311,7 @@ export const fetchRepoStats = async (
|
|||
// Use optimized maintainer fetching
|
||||
emitLoadingProgress("Fetching repository information...", {
|
||||
stage: "repo",
|
||||
percentage: 5,
|
||||
percentage: 8,
|
||||
completed: 0,
|
||||
total: 1,
|
||||
});
|
||||
|
|
@ -327,17 +327,17 @@ export const fetchRepoStats = async (
|
|||
maintainers = new Set();
|
||||
}
|
||||
|
||||
// Single-repo pull request fetch (repo mode)
|
||||
emitLoadingProgress("Fetching pull requests...", {
|
||||
stage: "repo-prs",
|
||||
percentage: 10,
|
||||
completed: 0,
|
||||
total: 1,
|
||||
});
|
||||
const pullRequests: PullRequest[] = [];
|
||||
let page = 1;
|
||||
let hasMore = true;
|
||||
const MAX_PAGES = 5;
|
||||
// Single-repo pull request fetch (repo mode)
|
||||
emitLoadingProgress("Fetching pull requests...", {
|
||||
stage: "repo-prs",
|
||||
percentage: 26,
|
||||
completed: 0,
|
||||
total: MAX_PAGES,
|
||||
});
|
||||
|
||||
try {
|
||||
while (hasMore && page <= MAX_PAGES) {
|
||||
|
|
@ -389,12 +389,16 @@ export const fetchRepoStats = async (
|
|||
}));
|
||||
|
||||
pullRequests.push(...mappedPRs);
|
||||
const pagesFetched = page;
|
||||
page++;
|
||||
const pagePct = Math.min(85, 10 + Math.floor((page / MAX_PAGES) * 70));
|
||||
emitLoadingProgress(`Fetched page ${page - 1} of pull requests...`, {
|
||||
const pagePct = Math.min(
|
||||
82,
|
||||
26 + Math.floor((pagesFetched / MAX_PAGES) * 56),
|
||||
);
|
||||
emitLoadingProgress(`Fetched page ${pagesFetched} of pull requests...`, {
|
||||
stage: "repo-prs",
|
||||
percentage: pagePct,
|
||||
completed: page - 1,
|
||||
completed: pagesFetched,
|
||||
total: MAX_PAGES,
|
||||
});
|
||||
}
|
||||
|
|
@ -404,7 +408,7 @@ export const fetchRepoStats = async (
|
|||
|
||||
emitLoadingProgress("Processing contributor data...", {
|
||||
stage: "processing",
|
||||
percentage: 92,
|
||||
percentage: 90,
|
||||
completed: 1,
|
||||
total: 1,
|
||||
});
|
||||
|
|
@ -439,7 +443,10 @@ export const fetchRepoStats = async (
|
|||
});
|
||||
|
||||
const contributors = Array.from(contributorMap.values()).sort(
|
||||
(a, b) => b.totalPRs - a.totalPRs,
|
||||
(a, b) =>
|
||||
b.mergedPRs - a.mergedPRs ||
|
||||
b.totalPRs - a.totalPRs ||
|
||||
b.openPRs - a.openPRs,
|
||||
);
|
||||
|
||||
for (const contributor of contributors) {
|
||||
|
|
@ -450,6 +457,13 @@ export const fetchRepoStats = async (
|
|||
}
|
||||
}
|
||||
|
||||
emitLoadingProgress("Repository scan completed", {
|
||||
stage: "completed",
|
||||
percentage: 100,
|
||||
completed: 1,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
return {
|
||||
totalPRs: pullRequests.length,
|
||||
contributors,
|
||||
|
|
@ -474,7 +488,7 @@ export const fetchOrganizationActiveUsers = async (
|
|||
|
||||
emitLoadingProgress(`Fetching repositories for ${normalizedOrg}...`, {
|
||||
stage: "org-repos",
|
||||
percentage: 3,
|
||||
percentage: 8,
|
||||
completed: 0,
|
||||
total: 1,
|
||||
});
|
||||
|
|
@ -483,6 +497,15 @@ export const fetchOrganizationActiveUsers = async (
|
|||
const reposToScan = Array.from(new Set(firstPageRepos));
|
||||
const MAX_REPOS = 50;
|
||||
const selectedRepos = reposToScan.slice(0, MAX_REPOS);
|
||||
emitLoadingProgress(
|
||||
`Preparing organization scan across ${selectedRepos.length} repositories...`,
|
||||
{
|
||||
stage: "org-repos",
|
||||
percentage: 18,
|
||||
completed: selectedRepos.length,
|
||||
total: Math.max(1, selectedRepos.length),
|
||||
},
|
||||
);
|
||||
|
||||
const MAX_PAGES_PER_REPO = 5;
|
||||
const pullRequests: PullRequest[] = [];
|
||||
|
|
@ -511,8 +534,8 @@ export const fetchOrganizationActiveUsers = async (
|
|||
);
|
||||
const currentUnit = repoIndex * MAX_PAGES_PER_REPO + page;
|
||||
const scanPct = Math.min(
|
||||
95,
|
||||
5 + Math.floor((currentUnit / totalUnits) * 85),
|
||||
92,
|
||||
20 + Math.floor((currentUnit / totalUnits) * 72),
|
||||
);
|
||||
emitLoadingProgress(
|
||||
`Org scan ${repoIndex + 1}/${selectedRepos.length} • ${repoName} • page ${page}`,
|
||||
|
|
@ -601,9 +624,9 @@ export const fetchOrganizationActiveUsers = async (
|
|||
|
||||
emitLoadingProgress("Finalizing org activity results...", {
|
||||
stage: "finalizing",
|
||||
percentage: 98,
|
||||
percentage: 96,
|
||||
completed: selectedRepos.length,
|
||||
total: selectedRepos.length,
|
||||
total: Math.max(1, selectedRepos.length),
|
||||
});
|
||||
|
||||
const users = aggregateOrganizationActiveUsers(pullRequests, maintainersMap);
|
||||
|
|
@ -611,8 +634,8 @@ export const fetchOrganizationActiveUsers = async (
|
|||
emitLoadingProgress("Org scan completed", {
|
||||
stage: "completed",
|
||||
percentage: 100,
|
||||
completed: selectedRepos.length,
|
||||
total: selectedRepos.length,
|
||||
completed: selectedRepos.length > 0 ? selectedRepos.length : 1,
|
||||
total: Math.max(1, selectedRepos.length),
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
@ -679,7 +702,7 @@ export const fetchUserStats = async (
|
|||
|
||||
emitLoadingProgress(`Fetching user data for ${username}...`, {
|
||||
stage: "user",
|
||||
percentage: 5,
|
||||
percentage: 8,
|
||||
completed: 0,
|
||||
total: 1,
|
||||
});
|
||||
|
|
@ -698,7 +721,7 @@ export const fetchUserStats = async (
|
|||
// Fetch pull requests
|
||||
emitLoadingProgress(`Fetching ${username}'s pull requests...`, {
|
||||
stage: "user-prs",
|
||||
percentage: 10,
|
||||
percentage: 20,
|
||||
completed: 0,
|
||||
total: MAX_PAGES,
|
||||
});
|
||||
|
|
@ -755,15 +778,16 @@ export const fetchUserStats = async (
|
|||
})),
|
||||
);
|
||||
|
||||
const pagesFetched = page;
|
||||
page++;
|
||||
const userPagePct = Math.min(
|
||||
85,
|
||||
10 + Math.floor((page / MAX_PAGES) * 70),
|
||||
82,
|
||||
22 + Math.floor((pagesFetched / MAX_PAGES) * 60),
|
||||
);
|
||||
emitLoadingProgress(`Fetched page ${page - 1} of pull requests...`, {
|
||||
emitLoadingProgress(`Fetched page ${pagesFetched} of pull requests...`, {
|
||||
stage: "user-prs",
|
||||
percentage: userPagePct,
|
||||
completed: page - 1,
|
||||
completed: pagesFetched,
|
||||
total: MAX_PAGES,
|
||||
});
|
||||
}
|
||||
|
|
@ -774,7 +798,7 @@ export const fetchUserStats = async (
|
|||
|
||||
emitLoadingProgress(`Processing repositories for ${username}...`, {
|
||||
stage: "user-processing",
|
||||
percentage: 90,
|
||||
percentage: 86,
|
||||
completed: 1,
|
||||
total: 1,
|
||||
});
|
||||
|
|
@ -828,22 +852,24 @@ export const fetchUserStats = async (
|
|||
.sort(([, a], [, b]) => b.totalPRs - a.totalPRs)
|
||||
.slice(0, 3); // Top 3 repos
|
||||
|
||||
const totalChecks = Math.max(1, repoEntries.length);
|
||||
emitLoadingProgress(`Checking maintainer status...`, {
|
||||
stage: "maintainer-check",
|
||||
percentage: 95,
|
||||
completed: 0,
|
||||
total: repoEntries.length || 1,
|
||||
percentage: repoEntries.length > 0 ? 90 : 97,
|
||||
completed: repoEntries.length > 0 ? 0 : 1,
|
||||
total: totalChecks,
|
||||
});
|
||||
|
||||
// Use Promise.all instead of Promise.any for better performance
|
||||
if (repoEntries.length > 0) {
|
||||
let checksCompleted = 0;
|
||||
const checkResults = await Promise.all(
|
||||
repoEntries.map(async ([repoName]) => {
|
||||
try {
|
||||
const [owner, repo] = repoName.split("/");
|
||||
if (!owner || !repo) return false;
|
||||
|
||||
const maintainers = await getMaintainers(octokit, owner, repo);
|
||||
const maintainers = await getMaintainers(octokit, owner, repo, true);
|
||||
const isMaintainerForRepo = maintainers.has(username);
|
||||
|
||||
console.log(
|
||||
|
|
@ -852,6 +878,21 @@ export const fetchUserStats = async (
|
|||
return isMaintainerForRepo;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
checksCompleted += 1;
|
||||
const checkPct = Math.min(
|
||||
98,
|
||||
90 + Math.floor((checksCompleted / totalChecks) * 8),
|
||||
);
|
||||
emitLoadingProgress(
|
||||
`Checking maintainer status... (${checksCompleted}/${totalChecks})`,
|
||||
{
|
||||
stage: "maintainer-check",
|
||||
percentage: checkPct,
|
||||
completed: checksCompleted,
|
||||
total: totalChecks,
|
||||
},
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue