mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 04:29:36 +00:00
Merge pull request #3886 from Infisical/policy-delete-requests-warning
improvement(approval-policies): Add open request warning to remove policy modal
This commit is contained in:
@@ -20,6 +20,7 @@ type Props = {
|
||||
children?: ReactNode;
|
||||
deletionMessage?: ReactNode;
|
||||
buttonColorSchema?: "danger" | "primary" | "secondary" | "gray" | null;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
export const DeleteActionModal = ({
|
||||
@@ -34,6 +35,7 @@ export const DeleteActionModal = ({
|
||||
formContent,
|
||||
deletionMessage,
|
||||
buttonColorSchema = "danger",
|
||||
isDisabled,
|
||||
children
|
||||
}: Props): JSX.Element => {
|
||||
const [inputData, setInputData] = useState("");
|
||||
@@ -70,7 +72,7 @@ export const DeleteActionModal = ({
|
||||
<Button
|
||||
className="mr-4"
|
||||
colorSchema={buttonColorSchema}
|
||||
isDisabled={!(deleteKey === inputData) || isLoading}
|
||||
isDisabled={!(deleteKey === inputData) || isLoading || isDisabled}
|
||||
onClick={onDelete}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
|
||||
@@ -25,8 +25,8 @@ export const accessApprovalKeys = {
|
||||
requestedBy?: string,
|
||||
bypassReason?: string
|
||||
) => [{ projectSlug, envSlug, requestedBy, bypassReason }, "access-approvals-requests"] as const,
|
||||
getAccessApprovalRequestCount: (projectSlug: string) =>
|
||||
[{ projectSlug }, "access-approval-request-count"] as const
|
||||
getAccessApprovalRequestCount: (projectSlug: string, policyId?: string) =>
|
||||
[{ projectSlug }, "access-approval-request-count", ...(policyId ? [policyId] : [])] as const
|
||||
};
|
||||
|
||||
export const fetchPolicyApprovalCount = async ({
|
||||
@@ -87,21 +87,22 @@ const fetchApprovalRequests = async ({
|
||||
}));
|
||||
};
|
||||
|
||||
const fetchAccessRequestsCount = async (projectSlug: string) => {
|
||||
const fetchAccessRequestsCount = async (projectSlug: string, policyId?: string) => {
|
||||
const { data } = await apiRequest.get<TAccessRequestCount>(
|
||||
"/api/v1/access-approvals/requests/count",
|
||||
{ params: { projectSlug } }
|
||||
{ params: { projectSlug, policyId } }
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useGetAccessRequestsCount = ({
|
||||
projectSlug,
|
||||
policyId,
|
||||
options = {}
|
||||
}: TGetAccessApprovalRequestsDTO & TReactQueryOptions) =>
|
||||
useQuery({
|
||||
queryKey: accessApprovalKeys.getAccessApprovalRequestCount(projectSlug),
|
||||
queryFn: () => fetchAccessRequestsCount(projectSlug),
|
||||
queryKey: accessApprovalKeys.getAccessApprovalRequestCount(projectSlug, policyId),
|
||||
queryFn: () => fetchAccessRequestsCount(projectSlug, policyId),
|
||||
...options,
|
||||
enabled: Boolean(projectSlug) && (options?.enabled ?? true)
|
||||
});
|
||||
|
||||
@@ -147,6 +147,7 @@ export type TCreateAccessRequestDTO = {
|
||||
|
||||
export type TGetAccessApprovalRequestsDTO = {
|
||||
projectSlug: string;
|
||||
policyId?: string;
|
||||
envSlug?: string;
|
||||
authorUserId?: string;
|
||||
};
|
||||
|
||||
@@ -34,9 +34,10 @@ export const secretApprovalRequestKeys = {
|
||||
] as const,
|
||||
detail: ({ id }: Omit<TGetSecretApprovalRequestDetails, "decryptKey">) =>
|
||||
[{ id }, "secret-approval-request-detail"] as const,
|
||||
count: ({ workspaceId }: TGetSecretApprovalRequestCount) => [
|
||||
count: ({ workspaceId, policyId }: TGetSecretApprovalRequestCount) => [
|
||||
{ workspaceId },
|
||||
"secret-approval-request-count"
|
||||
"secret-approval-request-count",
|
||||
...(policyId ? [policyId] : [])
|
||||
]
|
||||
};
|
||||
|
||||
@@ -204,10 +205,13 @@ export const useGetSecretApprovalRequestDetails = ({
|
||||
enabled: Boolean(id) && (options?.enabled ?? true)
|
||||
});
|
||||
|
||||
const fetchSecretApprovalRequestCount = async ({ workspaceId }: TGetSecretApprovalRequestCount) => {
|
||||
const fetchSecretApprovalRequestCount = async ({
|
||||
workspaceId,
|
||||
policyId
|
||||
}: TGetSecretApprovalRequestCount) => {
|
||||
const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequestCount }>(
|
||||
"/api/v1/secret-approval-requests/count",
|
||||
{ params: { workspaceId } }
|
||||
{ params: { workspaceId, policyId } }
|
||||
);
|
||||
|
||||
return data.approvals;
|
||||
@@ -215,6 +219,7 @@ const fetchSecretApprovalRequestCount = async ({ workspaceId }: TGetSecretApprov
|
||||
|
||||
export const useGetSecretApprovalRequestCount = ({
|
||||
workspaceId,
|
||||
policyId,
|
||||
options = {}
|
||||
}: TGetSecretApprovalRequestCount & {
|
||||
options?: Omit<
|
||||
@@ -228,8 +233,8 @@ export const useGetSecretApprovalRequestCount = ({
|
||||
>;
|
||||
}) =>
|
||||
useQuery({
|
||||
queryKey: secretApprovalRequestKeys.count({ workspaceId }),
|
||||
queryKey: secretApprovalRequestKeys.count({ workspaceId, policyId }),
|
||||
refetchInterval: 15000,
|
||||
queryFn: () => fetchSecretApprovalRequestCount({ workspaceId }),
|
||||
queryFn: () => fetchSecretApprovalRequestCount({ workspaceId, policyId }),
|
||||
enabled: Boolean(workspaceId) && (options?.enabled ?? true)
|
||||
});
|
||||
|
||||
@@ -118,6 +118,7 @@ export type TGetSecretApprovalRequestList = {
|
||||
|
||||
export type TGetSecretApprovalRequestCount = {
|
||||
workspaceId: string;
|
||||
policyId?: string;
|
||||
};
|
||||
|
||||
export type TGetSecretApprovalRequestDetails = {
|
||||
|
||||
+10
-45
@@ -16,11 +16,9 @@ import { AnimatePresence, motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -54,8 +52,6 @@ import {
|
||||
} from "@app/helpers/userTablePreferences";
|
||||
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
||||
import {
|
||||
useDeleteAccessApprovalPolicy,
|
||||
useDeleteSecretApprovalPolicy,
|
||||
useGetSecretApprovalPolicies,
|
||||
useGetWorkspaceUsers,
|
||||
useListWorkspaceGroups
|
||||
@@ -67,6 +63,7 @@ import { TAccessApprovalPolicy, Workspace } from "@app/hooks/api/types";
|
||||
|
||||
import { AccessPolicyForm } from "./components/AccessPolicyModal";
|
||||
import { ApprovalPolicyRow } from "./components/ApprovalPolicyRow";
|
||||
import { RemoveApprovalPolicyModal } from "./components/RemoveApprovalPolicyModal";
|
||||
|
||||
interface IProps {
|
||||
workspaceId: string;
|
||||
@@ -122,7 +119,7 @@ const useApprovalPolicies = (permission: TProjectPermission, currentWorkspace?:
|
||||
};
|
||||
|
||||
export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
|
||||
const { handlePopUpToggle, handlePopUpOpen, handlePopUpClose, popUp } = usePopUp([
|
||||
const { handlePopUpToggle, handlePopUpOpen, popUp } = usePopUp([
|
||||
"policyForm",
|
||||
"deletePolicy",
|
||||
"upgradePlan"
|
||||
@@ -213,39 +210,6 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
|
||||
setPage
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteSecretApprovalPolicy } = useDeleteSecretApprovalPolicy();
|
||||
const { mutateAsync: deleteAccessApprovalPolicy } = useDeleteAccessApprovalPolicy();
|
||||
|
||||
const handleDeletePolicy = async () => {
|
||||
const { id, policyType } = popUp.deletePolicy.data as TAccessApprovalPolicy;
|
||||
if (!currentWorkspace?.slug) return;
|
||||
|
||||
try {
|
||||
if (policyType === PolicyType.ChangePolicy) {
|
||||
await deleteSecretApprovalPolicy({
|
||||
workspaceId,
|
||||
id
|
||||
});
|
||||
} else {
|
||||
await deleteAccessApprovalPolicy({
|
||||
projectSlug: currentWorkspace?.slug,
|
||||
id
|
||||
});
|
||||
}
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully deleted policy"
|
||||
});
|
||||
handlePopUpClose("deletePolicy");
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to delete policy"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isTableFiltered = filters.type !== null || Boolean(filters.environmentIds.length);
|
||||
|
||||
const handleSort = (column: PolicyOrderBy) => {
|
||||
@@ -528,13 +492,14 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
|
||||
members={members}
|
||||
editValues={popUp.policyForm.data as TAccessApprovalPolicy}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deletePolicy.isOpen}
|
||||
deleteKey="remove"
|
||||
title="Do you want to remove this policy?"
|
||||
onChange={(isOpen) => handlePopUpToggle("deletePolicy", isOpen)}
|
||||
onDeleteApproved={handleDeletePolicy}
|
||||
/>
|
||||
{popUp.deletePolicy.data && (
|
||||
<RemoveApprovalPolicyModal
|
||||
isOpen={popUp.deletePolicy.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("deletePolicy", isOpen)}
|
||||
policyType={popUp.deletePolicy.data.policyType}
|
||||
policyId={popUp.deletePolicy.data.id}
|
||||
/>
|
||||
)}
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
import { faCheck, faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { DeleteActionModal, Spinner } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import {
|
||||
useDeleteAccessApprovalPolicy,
|
||||
useDeleteSecretApprovalPolicy,
|
||||
useGetAccessRequestsCount,
|
||||
useGetSecretApprovalRequestCount
|
||||
} from "@app/hooks/api";
|
||||
import { PolicyType } from "@app/hooks/api/policies/enums";
|
||||
|
||||
type Props = {
|
||||
policyId: string;
|
||||
policyType: PolicyType;
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
};
|
||||
|
||||
export const RemoveApprovalPolicyModal = ({
|
||||
policyId,
|
||||
policyType,
|
||||
isOpen,
|
||||
onOpenChange
|
||||
}: Props) => {
|
||||
const { mutateAsync: deleteSecretApprovalPolicy } = useDeleteSecretApprovalPolicy();
|
||||
const { mutateAsync: deleteAccessApprovalPolicy } = useDeleteAccessApprovalPolicy();
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const handleDeletePolicy = async () => {
|
||||
try {
|
||||
if (policyType === PolicyType.ChangePolicy) {
|
||||
await deleteSecretApprovalPolicy({
|
||||
workspaceId: currentWorkspace.id,
|
||||
id: policyId
|
||||
});
|
||||
} else {
|
||||
await deleteAccessApprovalPolicy({
|
||||
projectSlug: currentWorkspace.slug,
|
||||
id: policyId
|
||||
});
|
||||
}
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully deleted policy"
|
||||
});
|
||||
onOpenChange(false);
|
||||
} catch {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to delete policy"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSecretApprovalData = useGetSecretApprovalRequestCount({
|
||||
policyId,
|
||||
workspaceId: currentWorkspace.id,
|
||||
options: {
|
||||
enabled: Boolean(policyId) && policyType === PolicyType.ChangePolicy
|
||||
}
|
||||
});
|
||||
|
||||
const deleteAccessApprovalData = useGetAccessRequestsCount({
|
||||
projectSlug: currentWorkspace.slug,
|
||||
policyId,
|
||||
options: {
|
||||
enabled: Boolean(policyId) && policyType === PolicyType.AccessPolicy
|
||||
}
|
||||
});
|
||||
|
||||
let openCount: number | undefined;
|
||||
let isPending: boolean;
|
||||
if (policyType === PolicyType.ChangePolicy) {
|
||||
openCount = deleteSecretApprovalData.data?.open;
|
||||
isPending = deleteSecretApprovalData.isPending;
|
||||
} else {
|
||||
openCount = deleteAccessApprovalData.data?.pendingCount;
|
||||
isPending = deleteAccessApprovalData.isPending;
|
||||
}
|
||||
|
||||
return (
|
||||
<DeleteActionModal
|
||||
isOpen={isOpen}
|
||||
deleteKey="remove"
|
||||
title="Do you want to remove this policy?"
|
||||
onChange={onOpenChange}
|
||||
onDeleteApproved={handleDeletePolicy}
|
||||
isDisabled={isPending}
|
||||
>
|
||||
{isPending ? (
|
||||
<div className="mt-4 flex w-full items-center gap-2 p-2">
|
||||
<Spinner size="xs" className="text-mineshaft-600" />
|
||||
<span className="text-sm text-mineshaft-400">Checking for open requests...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={twMerge(
|
||||
"mt-4 flex w-full items-start gap-2 rounded border p-2 text-sm",
|
||||
(openCount ?? 0) > 0
|
||||
? "border-yellow/20 bg-yellow/10 text-yellow"
|
||||
: "border-green/20 bg-green/10 text-green"
|
||||
)}
|
||||
>
|
||||
{(openCount ?? 0) > 0 ? (
|
||||
<>
|
||||
<FontAwesomeIcon className="mt-1" icon={faWarning} />
|
||||
<div className="flex flex-col">
|
||||
<span>
|
||||
This policy has {openCount} open request
|
||||
{(openCount ?? 0) > 1 ? "s" : ""}
|
||||
</span>
|
||||
<p className="text-xs text-mineshaft-200">
|
||||
Removing this policy will close all open requests.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FontAwesomeIcon className="mt-1" icon={faCheck} />
|
||||
<div className="flex flex-col">
|
||||
<span>This policy has no open requests</span>
|
||||
<p className="text-xs text-mineshaft-200">This policy is safe to remove.</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</DeleteActionModal>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user