import { useEffect, useState } from "react"; import { faArrowUpRightFromSquare, faBookOpen, faCheck, faCheckCircle, faChevronDown, faCodeBranch, faCodeMerge, faMagnifyingGlass, faSearch, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useSearch } from "@tanstack/react-router"; import { format, formatDistance } from "date-fns"; import { twMerge } from "tailwind-merge"; import { Badge, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, EmptyState, Input, Pagination, Skeleton, Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionMemberActions, ProjectPermissionSub, useProject, useProjectPermission, useUser } from "@app/context"; import { getUserTablePreference, PreferenceKey, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination } from "@app/hooks"; import { useGetSecretApprovalRequestCount, useGetSecretApprovalRequests, useGetWorkspaceUsers } from "@app/hooks/api"; import { ApprovalStatus } from "@app/hooks/api/types"; import { generateCommitText, SecretApprovalRequestChanges } from "./components/SecretApprovalRequestChanges"; export const SecretApprovalRequest = () => { const { currentProject, projectId } = useProject(); const [selectedApprovalId, setSelectedApprovalId] = useState(null); // filters const [statusFilter, setStatusFilter] = useState<"open" | "close">("open"); const [envFilter, setEnvFilter] = useState(); const [committerFilter, setCommitterFilter] = useState(); const [usingUrlRequestId, setUsingUrlRequestId] = useState(false); const { debouncedSearch: debouncedSearchFilter, search: searchFilter, setSearch: setSearchFilter, setPage, page, perPage, setPerPage, offset, limit } = usePagination("", { initPerPage: getUserTablePreference("changeRequestsTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); setUserTablePreference("changeRequestsTable", PreferenceKey.PerPage, newPerPage); }; const { data, isPending: isApprovalRequestLoading, refetch } = useGetSecretApprovalRequests({ projectId, status: statusFilter, environment: envFilter, committer: committerFilter, search: debouncedSearchFilter, limit, offset }); const totalApprovalCount = data?.totalCount ?? 0; const secretApprovalRequests = data?.approvals ?? []; const { data: secretApprovalRequestCount, isSuccess: isSecretApprovalReqCountSuccess } = useGetSecretApprovalRequestCount({ projectId }); const { user: userSession } = useUser(); const search = useSearch({ from: ROUTE_PATHS.SecretManager.ApprovalPage.id }); const { permission } = useProjectPermission(); const { data: members } = useGetWorkspaceUsers(projectId); const isSecretApprovalScreen = Boolean(selectedApprovalId); const { requestId } = search; useEffect(() => { if (!requestId || usingUrlRequestId) return; setSelectedApprovalId(requestId as string); setUsingUrlRequestId(true); }, [requestId]); const handleGoBackSecretRequestDetail = () => { setSelectedApprovalId(null); refetch(); }; const isRequestListEmpty = !isApprovalRequestLoading && secretApprovalRequests?.length === 0; const isFiltered = Boolean(searchFilter || envFilter || committerFilter); return isSecretApprovalScreen ? ( ) : (

Change Requests

Docs

Review pending and closed change requests

setSearchFilter(e.target.value)} leftIcon={} placeholder="Search change requests by author, environment or policy path..." className="flex-1" containerClassName="mb-4" />
setStatusFilter("open")} onKeyDown={(evt) => { if (evt.key === "Enter") setStatusFilter("open"); }} className={twMerge( "font-medium", statusFilter === "close" && "text-gray-500 duration-100 hover:text-gray-400" )} > {isSecretApprovalReqCountSuccess && secretApprovalRequestCount?.open} Open
setStatusFilter("close")} onKeyDown={(evt) => { if (evt.key === "Enter") setStatusFilter("close"); }} > {isSecretApprovalReqCountSuccess && secretApprovalRequestCount.closed} Closed
Select an Environment {currentProject?.environments.map(({ slug, name }) => ( setEnvFilter((state) => (state === slug ? undefined : slug))} key={`request-filter-${slug}`} icon={envFilter === slug && } iconPos="right" > {name} ))} {!!permission.can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member) && ( Select an Author {members?.map(({ user, id }) => ( setCommitterFilter((state) => (state === user.id ? undefined : user.id)) } key={`request-filter-member-${id}`} icon={committerFilter === user.id && } iconPos="right" > {user.username} ))} )}
{isRequestListEmpty && !isFiltered && (
)} {secretApprovalRequests.map((secretApproval) => { const { id: reqId, commits, createdAt, reviewers, status, committerUser, hasMerged, updatedAt } = secretApproval; const isReviewed = reviewers.some( ({ status: reviewStatus, userId }) => userId === userSession.id && reviewStatus === ApprovalStatus.APPROVED ); return (
setSelectedApprovalId(secretApproval.id)} onKeyDown={(evt) => { if (evt.key === "Enter") setSelectedApprovalId(secretApproval.id); }} >
{secretApproval.isReplicated ? `${commits.length} secret pending import` : generateCommitText(commits)} #{secretApproval.slug}
Opened {formatDistance(new Date(createdAt), new Date())} ago by{" "} {committerUser ? ( <> {committerUser?.firstName || ""} {committerUser?.lastName || ""} ( {committerUser?.email}) ) : ( Deleted User )} {!isReviewed && status === "open" && " - Review required"}
{status === "close" && (
{hasMerged ? "Merged" : "Rejected"}
)}
); })} {Boolean(!secretApprovalRequests.length && isFiltered && !isApprovalRequestLoading) && (
)} {Boolean(totalApprovalCount) && ( )} {isApprovalRequestLoading && (
{Array.apply(0, Array(3)).map((_x, index) => (
))}
)}
); };