fix(commits): compute total count on cache miss for pagination
- git_commit_log now computes count when Redis cache misses - Previous: returned total: 0 when cache empty - Now: compute + cache on miss (5min TTL)
This commit is contained in:
parent
ddd24bfb6d
commit
21d0d1eae6
@ -818,6 +818,8 @@ impl AppService {
|
|||||||
"git:commit:count:{}:{}:{:?}:{:?}",
|
"git:commit:count:{}:{}:{:?}:{:?}",
|
||||||
namespace, repo_name, from, to,
|
namespace, repo_name, from, to,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get total count for pagination. Cache miss → compute + cache.
|
||||||
let total: usize = if let Ok(mut conn) = self.cache.conn().await {
|
let total: usize = if let Ok(mut conn) = self.cache.conn().await {
|
||||||
if let Ok(cached) = conn.get::<_, String>(total_cache_key.clone()).await {
|
if let Ok(cached) = conn.get::<_, String>(total_cache_key.clone()).await {
|
||||||
if let Ok(cached) = serde_json::from_str::<CommitCountResponse>(&cached) {
|
if let Ok(cached) = serde_json::from_str::<CommitCountResponse>(&cached) {
|
||||||
@ -826,10 +828,28 @@ impl AppService {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
0
|
// Cache miss: compute count and cache it.
|
||||||
|
let computed = git_spawn!(repo, domain -> {
|
||||||
|
domain.commit_count(from.as_deref(), to.as_deref())
|
||||||
|
}).unwrap_or(0);
|
||||||
|
if let Err(e) = conn
|
||||||
|
.set_ex::<String, String, ()>(
|
||||||
|
total_cache_key.clone(),
|
||||||
|
serde_json::to_string(&CommitCountResponse { count: computed })
|
||||||
|
.unwrap_or_default(),
|
||||||
|
300,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::debug!(error = ?e, "cache set failed (non-fatal)");
|
||||||
|
}
|
||||||
|
computed
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
0
|
// No cache: compute directly.
|
||||||
|
git_spawn!(repo, domain -> {
|
||||||
|
domain.commit_count(from.as_deref(), to.as_deref())
|
||||||
|
}).unwrap_or(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
let total_pages = if total == 0 {
|
let total_pages = if total == 0 {
|
||||||
|
|||||||
@ -52,6 +52,8 @@ export const RepoCommits = () => {
|
|||||||
const [copiedSha, setCopiedSha] = useState<string | null>(null);
|
const [copiedSha, setCopiedSha] = useState<string | null>(null);
|
||||||
const [showBranchDropdown, setShowBranchDropdown] = useState(false);
|
const [showBranchDropdown, setShowBranchDropdown] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState<"list" | "graph" | "reflog">("list");
|
const [activeTab, setActiveTab] = useState<"list" | "graph" | "reflog">("list");
|
||||||
|
const PAGE_SIZE = 50;
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
const { data: branches = [] } = useQuery({
|
const { data: branches = [] } = useQuery({
|
||||||
queryKey: ["repo-branches", namespace, repoName],
|
queryKey: ["repo-branches", namespace, repoName],
|
||||||
@ -156,6 +158,7 @@ export const RepoCommits = () => {
|
|||||||
// Reset to page 1 when search changes
|
// Reset to page 1 when search changes
|
||||||
const handleSearchChange = (value: string) => {
|
const handleSearchChange = (value: string) => {
|
||||||
setSearchQuery(value);
|
setSearchQuery(value);
|
||||||
|
setPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopySha = async (sha: string) => {
|
const handleCopySha = async (sha: string) => {
|
||||||
@ -304,11 +307,11 @@ export const RepoCommits = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="divide-y">
|
<div className="divide-y">
|
||||||
{filteredCommits.slice(0, 100).map((commit, index) => {
|
{filteredCommits.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE).map((commit, index) => {
|
||||||
const { title, description } = getCommitMessageLines(commit.message);
|
const { title, description } = getCommitMessageLines(commit.message);
|
||||||
const isCopied = copiedSha === commit.oid;
|
const isCopied = copiedSha === commit.oid;
|
||||||
const isMergeCommit = commit.parent_ids?.length > 1;
|
const isMergeCommit = commit.parent_ids?.length > 1;
|
||||||
const isLast = index === Math.min(filteredCommits.length, 100) - 1;
|
const isLast = index === Math.min(filteredCommits.length - (page - 1) * PAGE_SIZE, PAGE_SIZE) - 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -442,10 +445,36 @@ export const RepoCommits = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{filteredCommits.length > 100 && activeTab === "list" && (
|
{activeTab === "list" && filteredCommits.length > 0 && (
|
||||||
<p className="text-sm text-muted-foreground text-center py-2">
|
<div className="flex items-center justify-center gap-2 py-4">
|
||||||
Showing 100 of {filteredCommits.length} commits
|
<button
|
||||||
</p>
|
onClick={() => setPage(p => Math.max(1, p - 1))}
|
||||||
|
disabled={page <= 1}
|
||||||
|
className="px-3 py-1.5 text-sm rounded-md border bg-background hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
{Array.from({ length: Math.ceil(filteredCommits.length / PAGE_SIZE) }, (_, i) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
onClick={() => setPage(i + 1)}
|
||||||
|
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
|
||||||
|
page === i + 1
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "border bg-background hover:bg-muted"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{i + 1}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
onClick={() => setPage(p => Math.min(Math.ceil(filteredCommits.length / PAGE_SIZE), p + 1))}
|
||||||
|
disabled={page >= Math.ceil(filteredCommits.length / PAGE_SIZE)}
|
||||||
|
className="px-3 py-1.5 text-sm rounded-md border bg-background hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user