Type fixes and PIT history pagination

This commit is contained in:
carlosmonastyrski
2025-05-21 23:43:41 -03:00
parent f06004370d
commit 2b948a18f3
16 changed files with 383 additions and 162 deletions
@@ -68,13 +68,29 @@ const fetchFolderCommitsCount = async ({
const fetchFolderCommitHistory = async (
workspaceId: string,
environment: string,
directory: string
): Promise<CommitHistoryItem[]> => {
const res = await apiRequest.get<CommitHistoryItem[]>("/api/v1/pit/commits", {
directory: string,
offset: number = 0,
limit: number = 20,
search?: string,
sort: "asc" | "desc" = "desc"
): Promise<{
commits: CommitHistoryItem[];
total: number;
hasMore: boolean;
}> => {
const res = await apiRequest.get<{
commits: CommitHistoryItem[];
total: number;
hasMore: boolean;
}>("/api/v1/pit/commits", {
params: {
environment,
path: directory,
workspaceId
workspaceId,
offset,
limit,
search,
sort
}
});
return res.data;
@@ -225,15 +241,30 @@ export const useGetFolderCommitsCount = ({
export const useGetFolderCommitHistory = ({
workspaceId,
environment,
directory
directory,
offset = 0,
limit = 20,
search,
sort = "desc"
}: {
workspaceId: string;
environment: string;
directory: string;
offset?: number;
limit?: number;
search?: string;
sort?: "asc" | "desc";
}) => {
return useQuery({
queryKey: commitKeys.history({ workspaceId, environment, directory }),
queryFn: () => fetchFolderCommitHistory(workspaceId, environment, directory),
queryKey: [
commitKeys.history({ workspaceId, environment, directory }),
offset,
limit,
search,
sort
],
queryFn: () =>
fetchFolderCommitHistory(workspaceId, environment, directory, offset, limit, search, sort),
enabled: Boolean(workspaceId && environment)
});
};
@@ -11,7 +11,8 @@ export enum ApprovalStatus {
export enum CommitType {
DELETE = "delete",
UPDATE = "update",
CREATE = "create"
CREATE = "create",
ADD = "add"
}
export type TSecretApprovalSecChangeData = {
@@ -22,6 +22,7 @@ import {
import { usePopUp } from "@app/hooks";
import { CommitWithChanges } from "@app/hooks/api/folderCommits";
import { useCommitRevert, useGetCommitDetails } from "@app/hooks/api/folderCommits/queries";
import { CommitType } from "@app/hooks/api/types";
import { SecretVersionDiffView } from "../SecretVersionDiffView";
import { MergedItem } from "./types";
@@ -147,9 +148,9 @@ export const CommitDetailsTab = ({
const commitChanges = parsedCommitDetails.changes?.changes || [];
// Separate changes by type
const addedChanges = commitChanges.filter((c) => c.changeType === "add" && !c.isUpdate);
const updatedChanges = commitChanges.filter((c) => c.changeType === "add" && c.isUpdate);
const deletedChanges = commitChanges.filter((c) => c.changeType === "delete");
const addedChanges = commitChanges.filter((c) => c.changeType === CommitType.ADD && !c.isUpdate);
const updatedChanges = commitChanges.filter((c) => c.changeType === CommitType.ADD && c.isUpdate);
const deletedChanges = commitChanges.filter((c) => c.changeType === CommitType.DELETE);
// Create merged item list from changes only
const changedItems: MergedItem[] = [];
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
faArrowDownWideShort,
faArrowUpWideShort,
@@ -144,41 +144,68 @@ export const CommitHistoryTab = ({
secretPath: string;
}) => {
const [searchTerm, setSearchTerm] = useState("");
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");
const [visibleCommits, setVisibleCommits] = useState(10);
const [offset, setOffset] = useState(0);
const [allCommits, setAllCommits] = useState<Commit[]>([]);
const debounceTimeoutRef = useRef<NodeJS.Timeout>();
const limit = 5;
const { data: commits, isLoading } = useGetFolderCommitHistory({
// Debounce search term
useEffect(() => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
debounceTimeoutRef.current = setTimeout(() => {
setDebouncedSearchTerm(searchTerm);
}, 500);
return () => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
};
}, [searchTerm]);
const {
data: response,
isLoading,
isFetching
} = useGetFolderCommitHistory({
workspaceId: projectId,
environment,
directory: secretPath
directory: secretPath,
offset,
limit,
search: debouncedSearchTerm,
sort: sortDirection
});
const filteredCommits = useMemo(() => {
if (!commits?.length) return [];
const commits = response?.commits || [];
const hasMore = response?.hasMore || false;
return commits.filter(
(commit) =>
commit.id?.includes(searchTerm) ||
commit.message?.toLowerCase().includes(searchTerm.toLowerCase()) ||
commit.actorMetadata?.name?.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [commits, searchTerm]);
// Reset accumulated commits when search or sort changes
useEffect(() => {
setAllCommits([]);
setOffset(0);
}, [debouncedSearchTerm, sortDirection]);
const sortedCommits = useMemo(() => {
if (!filteredCommits?.length) return [];
// Accumulate commits instead of replacing them
useEffect(() => {
if (commits.length > 0) {
if (offset === 0) {
// First load or after search/sort change - replace all commits
setAllCommits(commits);
} else {
// Subsequent loads - append new commits
setAllCommits((prev) => [...prev, ...commits]);
}
}
}, [commits, offset]);
return [...filteredCommits].sort((a, b) => {
const dateA = new Date(a.createdAt).getTime();
const dateB = new Date(b.createdAt).getTime();
return sortDirection === "desc" ? dateB - dateA : dateA - dateB;
});
}, [filteredCommits, sortDirection]);
const displayedCommits = useMemo(() => {
return sortedCommits.slice(0, visibleCommits);
}, [sortedCommits, visibleCommits]);
const groupedCommits = useMemo(() => {
return displayedCommits.reduce(
return allCommits.reduce(
(acc, commit) => {
const date = format(new Date(commit.createdAt), "MMM d, yyyy");
if (!acc[date]) {
@@ -189,25 +216,21 @@ export const CommitHistoryTab = ({
},
{} as Record<string, Commit[]>
);
}, [displayedCommits]);
}, [allCommits]);
const handleSort = useCallback(() => {
setSortDirection((prev) => (prev === "desc" ? "asc" : "desc"));
}, []);
const loadMoreCommits = useCallback(() => {
setVisibleCommits((prev) => prev + 10);
const handleSearch = useCallback((value: string) => {
setSearchTerm(value);
}, []);
if (isLoading) {
return (
<div className="flex h-64 items-center justify-center">
<Spinner size="lg" aria-label="Loading commits" />
</div>
);
}
const hasMoreCommits = sortedCommits.length > visibleCommits;
const loadMoreCommits = useCallback(() => {
if (hasMore && !isFetching) {
setOffset((prev) => prev + limit);
}
}, [hasMore, isFetching, limit]);
return (
<div className="w-full">
@@ -217,7 +240,8 @@ export const CommitHistoryTab = ({
<Input
placeholder="Search commits..."
className="h-10 w-full rounded-md border-transparent bg-zinc-800 pl-9 pr-3 text-sm text-white placeholder-gray-400 focus:border-gray-600 focus:ring-primary-500/20"
onChange={(e) => setSearchTerm(e.target.value)}
onChange={(e) => handleSearch(e.target.value)}
value={searchTerm}
aria-label="Search commits"
/>
<div className="absolute left-3 top-1/2 -translate-y-1/2 transform text-gray-400">
@@ -239,43 +263,57 @@ export const CommitHistoryTab = ({
</div>
</div>
<div className="space-y-8">
{Object.keys(groupedCommits).length > 0 ? (
<>
{Object.entries(groupedCommits).map(([date, dateCommits]) => (
<DateGroup
key={date}
date={date}
commits={dateCommits}
onSelectCommit={onSelectCommit}
{isLoading && offset === 0 ? (
<div className="flex h-64 items-center justify-center">
<Spinner size="lg" aria-label="Loading commits" />
</div>
) : (
<div className="space-y-8">
{Object.keys(groupedCommits).length > 0 ? (
<>
{Object.entries(groupedCommits).map(([date, dateCommits]) => (
<DateGroup
key={date}
date={date}
commits={dateCommits}
onSelectCommit={onSelectCommit}
/>
))}
</>
) : (
<div className="text-white-400 flex min-h-40 flex-col items-center justify-center rounded-lg bg-zinc-900 py-8 text-center">
<FontAwesomeIcon
icon={faSearch}
className="text-white-500 mb-3 text-3xl"
aria-hidden="true"
/>
))}
</>
) : (
<div className="text-white-400 flex min-h-40 flex-col items-center justify-center rounded-lg bg-zinc-900 py-8 text-center">
<FontAwesomeIcon
icon={faSearch}
className="text-white-500 mb-3 text-3xl"
aria-hidden="true"
/>
<p>No matching commits found. Try a different search term.</p>
</div>
)}
<p>No matching commits found. Try a different search term.</p>
</div>
)}
{hasMoreCommits && (
<div className="flex justify-center pb-2">
<Button
variant="outline_bg"
size="md"
className="rounded-md bg-zinc-900 px-6 py-2 text-sm font-medium text-white transition-colors duration-200 hover:bg-zinc-800 focus:outline-none focus:ring-2 focus:ring-primary-500"
onClick={loadMoreCommits}
aria-label="Load more commits"
>
Load more commits
</Button>
</div>
)}
</div>
{hasMore && (
<div className="flex justify-center pb-2">
<Button
variant="outline_bg"
size="md"
className="rounded-md bg-zinc-900 px-6 py-2 text-sm font-medium text-white transition-colors duration-200 hover:bg-zinc-800 focus:outline-none focus:ring-2 focus:ring-primary-500"
onClick={loadMoreCommits}
disabled={isFetching}
aria-label="Load more commits"
>
{isFetching ? (
<>
<Spinner size="sm" className="mr-2" />
Loading...
</>
) : (
"Load more commits"
)}
</Button>
</div>
)}
</div>
)}
</div>
);
};