improvements: address feedback and fix bugs

This commit is contained in:
Scott Wilson
2025-06-20 12:25:28 -07:00
parent 7e9743b4c2
commit 7c7af347fc
10 changed files with 146 additions and 86 deletions
@@ -89,7 +89,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
schema: { schema: {
querystring: z.object({ querystring: z.object({
projectSlug: z.string().trim(), projectSlug: z.string().trim(),
authorProjectMembershipId: z.string().trim().optional(), authorUserId: z.string().trim().optional(),
envSlug: z.string().trim().optional() envSlug: z.string().trim().optional()
}), }),
response: { response: {
@@ -143,7 +143,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
handler: async (req) => { handler: async (req) => {
const { requests } = await server.services.accessApprovalRequest.listApprovalRequests({ const { requests } = await server.services.accessApprovalRequest.listApprovalRequests({
projectSlug: req.query.projectSlug, projectSlug: req.query.projectSlug,
authorProjectMembershipId: req.query.authorProjectMembershipId, authorUserId: req.query.authorUserId,
envSlug: req.query.envSlug, envSlug: req.query.envSlug,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
@@ -725,16 +725,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
) )
.where(`${TableName.Environment}.projectId`, projectId) .where(`${TableName.Environment}.projectId`, projectId)
.where(`${TableName.AccessApprovalPolicy}.deletedAt`, null)
.select(selectAllTableCols(TableName.AccessApprovalRequest)) .select(selectAllTableCols(TableName.AccessApprovalRequest))
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")) .select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
.select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId")); .select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"))
.select(db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt"));
const formattedRequests = sqlNestRelationships({ const formattedRequests = sqlNestRelationships({
data: accessRequests, data: accessRequests,
key: "id", key: "id",
parentMapper: (doc) => ({ parentMapper: (doc) => ({
...AccessApprovalRequestsSchema.parse(doc) ...AccessApprovalRequestsSchema.parse(doc),
isPolicyDeleted: Boolean(doc.policyDeletedAt)
}), }),
childrenMapper: [ childrenMapper: [
{ {
@@ -751,7 +752,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
(req) => (req) =>
!req.privilegeId && !req.privilegeId &&
!req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) && !req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) &&
req.status === ApprovalStatus.PENDING req.status === ApprovalStatus.PENDING &&
!req.isPolicyDeleted
); );
// an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required. // an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required.
@@ -759,7 +761,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
(req) => (req) =>
req.privilegeId || req.privilegeId ||
req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) || req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) ||
req.status !== ApprovalStatus.PENDING req.status !== ApprovalStatus.PENDING ||
req.isPolicyDeleted
); );
return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length }; return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length };
@@ -275,7 +275,7 @@ export const accessApprovalRequestServiceFactory = ({
const listApprovalRequests: TAccessApprovalRequestServiceFactory["listApprovalRequests"] = async ({ const listApprovalRequests: TAccessApprovalRequestServiceFactory["listApprovalRequests"] = async ({
projectSlug, projectSlug,
authorProjectMembershipId, authorUserId,
envSlug, envSlug,
actor, actor,
actorOrgId, actorOrgId,
@@ -300,8 +300,8 @@ export const accessApprovalRequestServiceFactory = ({
const policies = await accessApprovalPolicyDAL.find({ projectId: project.id }); const policies = await accessApprovalPolicyDAL.find({ projectId: project.id });
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id)); let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
if (authorProjectMembershipId) { if (authorUserId) {
requests = requests.filter((request) => request.requestedByUserId === actorId); requests = requests.filter((request) => request.requestedByUserId === authorUserId);
} }
if (envSlug) { if (envSlug) {
@@ -31,7 +31,7 @@ export type TCreateAccessApprovalRequestDTO = {
export type TListApprovalRequestsDTO = { export type TListApprovalRequestsDTO = {
projectSlug: string; projectSlug: string;
authorProjectMembershipId?: string; authorUserId?: string;
envSlug?: string; envSlug?: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
@@ -40,7 +40,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
status: null, status: null,
trial_end: null, trial_end: null,
has_used_trial: true, has_used_trial: true,
secretApproval: false, secretApproval: true,
secretRotation: false, secretRotation: false,
caCrl: false, caCrl: false,
instanceUserManagement: false, instanceUserManagement: false,
@@ -315,7 +315,6 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId) .where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId)
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId) .orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId)
) )
.andWhere((bd) => void bd.where(`${TableName.SecretApprovalPolicy}.deletedAt`, null))
.select("status", `${TableName.SecretApprovalRequest}.id`) .select("status", `${TableName.SecretApprovalRequest}.id`)
.groupBy(`${TableName.SecretApprovalRequest}.id`, "status") .groupBy(`${TableName.SecretApprovalRequest}.id`, "status")
.count("status") .count("status")
@@ -347,7 +346,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
try { try {
// akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination // akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination
// this is the place u wanna look at. // this is the place u wanna look at.
const query = (tx || db.replicaNode())(TableName.SecretApprovalRequest) const innerQuery = (tx || db.replicaNode())(TableName.SecretApprovalRequest)
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`) .join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.join( .join(
@@ -434,24 +433,31 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("email").withSchema("committerUser").as("committerUserEmail"), db.ref("email").withSchema("committerUser").as("committerUserEmail"),
db.ref("username").withSchema("committerUser").as("committerUserUsername"), db.ref("username").withSchema("committerUser").as("committerUserUsername"),
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"), db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
db.ref("lastName").withSchema("committerUser").as("committerUserLastName"), db.ref("lastName").withSchema("committerUser").as("committerUserLastName")
db.raw(`count(*) OVER() as total_count`)
) )
.orderBy("createdAt", "desc"); .distinctOn(`${TableName.SecretApprovalRequest}.id`)
.as("inner");
const query = (tx || db)
.select("*")
.select(db.raw("count(*) OVER() as total_count"))
.from(innerQuery)
.orderBy("createdAt", "desc") as typeof innerQuery;
if (search) { if (search) {
void query void query.where((qb) => {
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [ void qb
db.ref("firstName").withSchema("committerUser"), .whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
db.ref("lastName").withSchema("committerUser"), db.ref("firstName").withSchema("committerUser"),
`%${search}%` db.ref("lastName").withSchema("committerUser"),
]) `%${search}%`
.orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`]) ])
.orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`]) .orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`])
.orWhereILike(`${TableName.Environment}.name`, `%${search}%`) .orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`])
.orWhereILike(`${TableName.Environment}.slug`, `%${search}%`) .orWhereILike(`${TableName.Environment}.name`, `%${search}%`)
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`); .orWhereILike(`${TableName.Environment}.slug`, `%${search}%`)
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`);
});
} }
const docs = await (tx || db) const docs = await (tx || db)
@@ -544,7 +550,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
try { try {
// akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination // akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination
// this is the place u wanna look at. // this is the place u wanna look at.
const query = (tx || db.replicaNode())(TableName.SecretApprovalRequest) const innerQuery = (tx || db.replicaNode())(TableName.SecretApprovalRequest)
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`) .join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.join( .join(
@@ -631,24 +637,31 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("email").withSchema("committerUser").as("committerUserEmail"), db.ref("email").withSchema("committerUser").as("committerUserEmail"),
db.ref("username").withSchema("committerUser").as("committerUserUsername"), db.ref("username").withSchema("committerUser").as("committerUserUsername"),
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"), db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
db.ref("lastName").withSchema("committerUser").as("committerUserLastName"), db.ref("lastName").withSchema("committerUser").as("committerUserLastName")
db.raw(`count(*) OVER() as total_count`)
) )
.orderBy("createdAt", "desc"); .distinctOn(`${TableName.SecretApprovalRequest}.id`)
.as("inner");
const query = (tx || db)
.select("*")
.select(db.raw("count(*) OVER() as total_count"))
.from(innerQuery)
.orderBy("createdAt", "desc") as typeof innerQuery;
if (search) { if (search) {
void query void query.where((qb) => {
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [ void qb
db.ref("firstName").withSchema("committerUser"), .whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
db.ref("lastName").withSchema("committerUser"), db.ref("firstName").withSchema("committerUser"),
`%${search}%` db.ref("lastName").withSchema("committerUser"),
]) `%${search}%`
.orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`]) ])
.orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`]) .orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`])
.orWhereILike(`${TableName.Environment}.name`, `%${search}%`) .orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`])
.orWhereILike(`${TableName.Environment}.slug`, `%${search}%`) .orWhereILike(`${TableName.Environment}.name`, `%${search}%`)
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`); .orWhereILike(`${TableName.Environment}.slug`, `%${search}%`)
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`);
});
} }
const rankOffset = offset + 1; const rankOffset = offset + 1;
@@ -65,11 +65,11 @@ const fetchApprovalPolicies = async ({ projectSlug }: TGetAccessApprovalRequests
const fetchApprovalRequests = async ({ const fetchApprovalRequests = async ({
projectSlug, projectSlug,
envSlug, envSlug,
authorProjectMembershipId authorUserId
}: TGetAccessApprovalRequestsDTO) => { }: TGetAccessApprovalRequestsDTO) => {
const { data } = await apiRequest.get<{ requests: TAccessApprovalRequest[] }>( const { data } = await apiRequest.get<{ requests: TAccessApprovalRequest[] }>(
"/api/v1/access-approvals/requests", "/api/v1/access-approvals/requests",
{ params: { projectSlug, envSlug, authorProjectMembershipId } } { params: { projectSlug, envSlug, authorUserId } }
); );
return data.requests.map((request) => ({ return data.requests.map((request) => ({
@@ -109,12 +109,12 @@ export const useGetAccessRequestsCount = ({
export const useGetAccessApprovalPolicies = ({ export const useGetAccessApprovalPolicies = ({
projectSlug, projectSlug,
envSlug, envSlug,
authorProjectMembershipId, authorUserId,
options = {} options = {}
}: TGetAccessApprovalRequestsDTO & TReactQueryOptions) => }: TGetAccessApprovalRequestsDTO & TReactQueryOptions) =>
useQuery({ useQuery({
queryKey: accessApprovalKeys.getAccessApprovalPolicies(projectSlug), queryKey: accessApprovalKeys.getAccessApprovalPolicies(projectSlug),
queryFn: () => fetchApprovalPolicies({ projectSlug, envSlug, authorProjectMembershipId }), queryFn: () => fetchApprovalPolicies({ projectSlug, envSlug, authorUserId }),
...options, ...options,
enabled: Boolean(projectSlug) && (options?.enabled ?? true) enabled: Boolean(projectSlug) && (options?.enabled ?? true)
}); });
@@ -122,16 +122,13 @@ export const useGetAccessApprovalPolicies = ({
export const useGetAccessApprovalRequests = ({ export const useGetAccessApprovalRequests = ({
projectSlug, projectSlug,
envSlug, envSlug,
authorProjectMembershipId, authorUserId,
options = {} options = {}
}: TGetAccessApprovalRequestsDTO & TReactQueryOptions) => }: TGetAccessApprovalRequestsDTO & TReactQueryOptions) =>
useQuery({ useQuery({
queryKey: accessApprovalKeys.getAccessApprovalRequests( queryKey: accessApprovalKeys.getAccessApprovalRequests(projectSlug, envSlug, authorUserId),
projectSlug, queryFn: () => fetchApprovalRequests({ projectSlug, envSlug, authorUserId }),
envSlug,
authorProjectMembershipId
),
queryFn: () => fetchApprovalRequests({ projectSlug, envSlug, authorProjectMembershipId }),
...options, ...options,
enabled: Boolean(projectSlug) && (options?.enabled ?? true) enabled: Boolean(projectSlug) && (options?.enabled ?? true),
placeholderData: (previousData) => previousData
}); });
@@ -148,7 +148,7 @@ export type TCreateAccessRequestDTO = {
export type TGetAccessApprovalRequestsDTO = { export type TGetAccessApprovalRequestsDTO = {
projectSlug: string; projectSlug: string;
envSlug?: string; envSlug?: string;
authorProjectMembershipId?: string; authorUserId?: string;
}; };
export type TGetAccessPolicyApprovalCountDTO = { export type TGetAccessPolicyApprovalCountDTO = {
@@ -3,6 +3,7 @@
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { import {
faArrowUpRightFromSquare, faArrowUpRightFromSquare,
faBan,
faBookOpen, faBookOpen,
faCheck, faCheck,
faCheckCircle, faCheckCircle,
@@ -12,10 +13,12 @@ import {
faMagnifyingGlass, faMagnifyingGlass,
faPlus, faPlus,
faSearch, faSearch,
faUser faStopwatch,
faUser,
IconDefinition
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { formatDistance } from "date-fns"; import { format, formatDistance } from "date-fns";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -133,7 +136,7 @@ export const AccessApprovalRequest = ({
isPending: areRequestsPending isPending: areRequestsPending
} = useGetAccessApprovalRequests({ } = useGetAccessApprovalRequests({
projectSlug, projectSlug,
authorProjectMembershipId: requestedByFilter, authorUserId: requestedByFilter,
envSlug: envFilter envSlug: envFilter
}); });
@@ -203,9 +206,15 @@ export const AccessApprovalRequest = ({
const canBypass = const canBypass =
!request.policy.bypassers.length || request.policy.bypassers.includes(user.id); !request.policy.bypassers.length || request.policy.bypassers.includes(user.id);
let displayData: { label: string; type: "primary" | "danger" | "success" } = { let displayData: {
label: string;
type: "primary" | "danger" | "success";
tooltipContent?: string;
icon: IconDefinition | null;
} = {
label: "", label: "",
type: "primary" type: "primary",
icon: null
}; };
const isExpired = const isExpired =
@@ -213,20 +222,42 @@ export const AccessApprovalRequest = ({
request.isApproved && request.isApproved &&
new Date() > new Date(request.privilege.temporaryAccessEndTime || ("" as string)); new Date() > new Date(request.privilege.temporaryAccessEndTime || ("" as string));
if (isExpired) displayData = { label: "Access Expired", type: "danger" }; if (isExpired)
else if (isAccepted) displayData = { label: "Access Granted", type: "success" }; displayData = {
else if (isRejectedByAnyone) displayData = { label: "Rejected", type: "danger" }; label: "Access Expired",
type: "danger",
icon: faStopwatch,
tooltipContent: request.privilege?.temporaryAccessEndTime
? `Expired ${format(request.privilege.temporaryAccessEndTime, "M/d/yyyy h:mm aa")}`
: undefined
};
else if (isAccepted)
displayData = {
label: "Access Granted",
type: "success",
icon: faCheck,
tooltipContent: `Granted ${format(request.updatedAt, "M/d/yyyy h:mm aa")}`
};
else if (isRejectedByAnyone)
displayData = {
label: "Rejected",
type: "danger",
icon: faBan,
tooltipContent: `Rejected ${format(request.updatedAt, "M/d/yyyy h:mm aa")}`
};
else if (userReviewStatus === ApprovalStatus.APPROVED) { else if (userReviewStatus === ApprovalStatus.APPROVED) {
displayData = { displayData = {
label: `Pending ${request.policy.approvals - request.reviewers.length} review${ label: `Pending ${request.policy.approvals - request.reviewers.length} review${
request.policy.approvals - request.reviewers.length > 1 ? "s" : "" request.policy.approvals - request.reviewers.length > 1 ? "s" : ""
}`, }`,
type: "primary" type: "primary",
icon: faClipboardCheck
}; };
} else if (!isReviewedByUser) } else if (!isReviewedByUser)
displayData = { displayData = {
label: "Review Required", label: "Review Required",
type: "primary" type: "primary",
icon: faClipboardCheck
}; };
return { return {
@@ -266,6 +297,8 @@ export const AccessApprovalRequest = ({
[generateRequestDetails, membersGroupById, user, setSelectedRequest, handlePopUpOpen] [generateRequestDetails, membersGroupById, user, setSelectedRequest, handlePopUpOpen]
); );
const isFiltered = Boolean(search || envFilter || requestedByFilter);
return ( return (
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<motion.div <motion.div
@@ -357,7 +390,7 @@ export const AccessApprovalRequest = ({
}} }}
> >
<FontAwesomeIcon icon={faCheck} className="mr-2" /> <FontAwesomeIcon icon={faCheck} className="mr-2" />
{!!requestCount && requestCount.finalizedCount} Completed {!!requestCount && requestCount.finalizedCount} Closed
</div> </div>
<div className="flex flex-grow justify-end space-x-8"> <div className="flex flex-grow justify-end space-x-8">
<DropdownMenu> <DropdownMenu>
@@ -365,7 +398,7 @@ export const AccessApprovalRequest = ({
<Button <Button
variant="plain" variant="plain"
colorSchema="secondary" colorSchema="secondary"
className="text-bunker-300" className={envFilter ? "text-white" : "text-bunker-300"}
rightIcon={<FontAwesomeIcon icon={faChevronDown} size="sm" className="ml-2" />} rightIcon={<FontAwesomeIcon icon={faChevronDown} size="sm" className="ml-2" />}
> >
Environments Environments
@@ -419,10 +452,16 @@ export const AccessApprovalRequest = ({
{members?.map(({ user: membershipUser, id }) => ( {members?.map(({ user: membershipUser, id }) => (
<DropdownMenuItem <DropdownMenuItem
onClick={() => onClick={() =>
setRequestedByFilter((state) => (state === id ? undefined : id)) setRequestedByFilter((state) =>
state === membershipUser.id ? undefined : membershipUser.id
)
} }
key={`request-filter-member-${id}`} key={`request-filter-member-${id}`}
icon={requestedByFilter === id && <FontAwesomeIcon icon={faCheckCircle} />} icon={
requestedByFilter === membershipUser.id && (
<FontAwesomeIcon icon={faCheckCircle} />
)
}
iconPos="right" iconPos="right"
> >
{membershipUser.username} {membershipUser.username}
@@ -434,16 +473,16 @@ export const AccessApprovalRequest = ({
</div> </div>
</div> </div>
<div className="flex flex-col rounded-b-md border-x border-b border-t border-mineshaft-600 bg-mineshaft-800"> <div className="flex flex-col rounded-b-md border-x border-b border-t border-mineshaft-600 bg-mineshaft-800">
{filteredRequests?.length === 0 && !search && ( {filteredRequests?.length === 0 && !isFiltered && (
<div className="py-12"> <div className="py-12">
<EmptyState <EmptyState
title={`No ${statusFilter === "open" ? "Pending" : "Completed"} Access Requests`} title={`No ${statusFilter === "open" ? "Pending" : "Closed"} Access Requests`}
/> />
</div> </div>
)} )}
{Boolean(!filteredRequests?.length && search && !areRequestsPending) && ( {Boolean(!filteredRequests?.length && isFiltered && !areRequestsPending) && (
<div className="py-12"> <div className="py-12">
<EmptyState title="No Requests Match Search" icon={faSearch} /> <EmptyState title="No Requests Match Filters" icon={faSearch} />
</div> </div>
)} )}
{!!filteredRequests?.length && {!!filteredRequests?.length &&
@@ -494,13 +533,19 @@ export const AccessApprovalRequest = ({
<span>Requested By You</span> <span>Requested By You</span>
</div> </div>
)} )}
<Badge <Tooltip content={details.displayData.tooltipContent}>
className="flex items-center gap-1.5 whitespace-nowrap" <div>
variant={details.displayData.type} <Badge
> className="flex items-center gap-1.5 whitespace-nowrap"
<FontAwesomeIcon icon={faClipboardCheck} /> variant={details.displayData.type}
<span>{details.displayData.label}</span> >
</Badge> {details.displayData.icon && (
<FontAwesomeIcon icon={details.displayData.icon} />
)}
<span>{details.displayData.label}</span>
</Badge>
</div>
</Tooltip>
</div> </div>
</div> </div>
</div> </div>
@@ -126,6 +126,8 @@ export const SecretApprovalRequest = () => {
const isRequestListEmpty = !isApprovalRequestLoading && secretApprovalRequests?.length === 0; const isRequestListEmpty = !isApprovalRequestLoading && secretApprovalRequests?.length === 0;
const isFiltered = Boolean(searchFilter || envFilter || committerFilter);
return ( return (
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
{isSecretApprovalScreen ? ( {isSecretApprovalScreen ? (
@@ -292,7 +294,7 @@ export const SecretApprovalRequest = () => {
</div> </div>
</div> </div>
<div className="flex flex-col rounded-b-md border-x border-b border-t border-mineshaft-600 bg-mineshaft-800"> <div className="flex flex-col rounded-b-md border-x border-b border-t border-mineshaft-600 bg-mineshaft-800">
{isRequestListEmpty && !searchFilter && ( {isRequestListEmpty && !isFiltered && (
<div className="py-12"> <div className="py-12">
<EmptyState <EmptyState
title={`No ${statusFilter === "open" ? "Open" : "Closed"} Change Requests`} title={`No ${statusFilter === "open" ? "Open" : "Closed"} Change Requests`}
@@ -344,10 +346,10 @@ export const SecretApprovalRequest = () => {
); );
})} })}
{Boolean( {Boolean(
!secretApprovalRequests.length && searchFilter && !isApprovalRequestLoading !secretApprovalRequests.length && isFiltered && !isApprovalRequestLoading
) && ( ) && (
<div className="py-12"> <div className="py-12">
<EmptyState title="No Requests Match Search" icon={faSearch} /> <EmptyState title="No Requests Match Filters" icon={faSearch} />
</div> </div>
)} )}
{Boolean(totalApprovalCount) && ( {Boolean(totalApprovalCount) && (