feat(frontend): accept soft approvals on access requests

This commit is contained in:
Alfonso Hernandez
2024-07-18 02:25:53 +02:00
parent f464d7a096
commit 454826fbb6
3 changed files with 55 additions and 10 deletions

View File

@@ -53,6 +53,7 @@ export type TAccessApprovalRequest = {
approvers: string[]; approvers: string[];
secretPath?: string | null; secretPath?: string | null;
envId: string; envId: string;
enforcementLevel: EnforcementLevel;
}; };
reviewers: { reviewers: {

View File

@@ -40,6 +40,7 @@ import {
useGetAccessRequestsCount useGetAccessRequestsCount
} from "@app/hooks/api/accessApproval/queries"; } from "@app/hooks/api/accessApproval/queries";
import { TAccessApprovalRequest } from "@app/hooks/api/accessApproval/types"; import { TAccessApprovalRequest } from "@app/hooks/api/accessApproval/types";
import { EnforcementLevel } from "@app/hooks/api/policies/enums";
import { ApprovalStatus, TWorkspaceUser } from "@app/hooks/api/types"; import { ApprovalStatus, TWorkspaceUser } from "@app/hooks/api/types";
import { queryClient } from "@app/reactQuery"; import { queryClient } from "@app/reactQuery";
@@ -80,7 +81,12 @@ export const AccessApprovalRequest = ({
projectId: string; projectId: string;
}) => { }) => {
const [selectedRequest, setSelectedRequest] = useState< const [selectedRequest, setSelectedRequest] = useState<
(TAccessApprovalRequest & { user: TWorkspaceUser["user"] | null }) | null (TAccessApprovalRequest & {
user: TWorkspaceUser["user"] | null;
isRequestedByCurrentUser: boolean;
isApprover: boolean;
})
| null
>(null); >(null);
const { handlePopUpOpen, popUp, handlePopUpClose } = usePopUp([ const { handlePopUpOpen, popUp, handlePopUpClose } = usePopUp([
@@ -141,6 +147,8 @@ export const AccessApprovalRequest = ({
); );
const isApprover = request.policy.approvers.indexOf(membership.id || "") !== -1; const isApprover = request.policy.approvers.indexOf(membership.id || "") !== -1;
const isAccepted = request.isApproved; const isAccepted = request.isApproved;
const isSoftEnforcement = request.policy.enforcementLevel === EnforcementLevel.Soft;
const isRequestedByCurrentUser = request.requestedBy === membership.id;
const userReviewStatus = request.reviewers.find( const userReviewStatus = request.reviewers.find(
({ member }) => member === membership.id ({ member }) => member === membership.id
@@ -178,7 +186,9 @@ export const AccessApprovalRequest = ({
isRejectedByAnyone, isRejectedByAnyone,
isApprover, isApprover,
userReviewStatus, userReviewStatus,
isAccepted isAccepted,
isSoftEnforcement,
isRequestedByCurrentUser
}; };
}; };
@@ -331,16 +341,24 @@ export const AccessApprovalRequest = ({
tabIndex={0} tabIndex={0}
onClick={() => { onClick={() => {
if ( if (
!details.isApprover || (
details.isReviewedByUser || !details.isApprover
details.isRejectedByAnyone || || details.isReviewedByUser
details.isAccepted || details.isRejectedByAnyone
|| details.isAccepted
) && !(
details.isSoftEnforcement
&& details.isRequestedByCurrentUser
&& !details.isAccepted
)
) )
return; return;
setSelectedRequest({ setSelectedRequest({
...request, ...request,
user: membersGroupById?.[request.requestedBy].user! user: membersGroupById?.[request.requestedBy].user!,
isRequestedByCurrentUser: details.isRequestedByCurrentUser,
isApprover: details.isApprover
}); });
handlePopUpOpen("reviewRequest"); handlePopUpOpen("reviewRequest");
}} }}

View File

@@ -2,11 +2,12 @@ import { useCallback, useMemo, useState } from "react";
import ms from "ms"; import ms from "ms";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, Modal, ModalContent } from "@app/components/v2"; import { Button, Checkbox, Modal, ModalContent } from "@app/components/v2";
import { Badge } from "@app/components/v2/Badge"; import { Badge } from "@app/components/v2/Badge";
import { ProjectPermissionActions } from "@app/context"; import { ProjectPermissionActions } from "@app/context";
import { useReviewAccessRequest } from "@app/hooks/api"; import { useReviewAccessRequest } from "@app/hooks/api";
import { TAccessApprovalRequest } from "@app/hooks/api/accessApproval/types"; import { TAccessApprovalRequest } from "@app/hooks/api/accessApproval/types";
import { EnforcementLevel } from "@app/hooks/api/policies/enums";
import { TWorkspaceUser } from "@app/hooks/api/types"; import { TWorkspaceUser } from "@app/hooks/api/types";
export const ReviewAccessRequestModal = ({ export const ReviewAccessRequestModal = ({
@@ -19,12 +20,18 @@ export const ReviewAccessRequestModal = ({
}: { }: {
isOpen: boolean; isOpen: boolean;
onOpenChange: (isOpen: boolean) => void; onOpenChange: (isOpen: boolean) => void;
request: TAccessApprovalRequest & { user: TWorkspaceUser["user"] | null }; request: TAccessApprovalRequest & {
user: TWorkspaceUser["user"] | null;
isRequestedByCurrentUser: boolean;
isApprover: boolean;
};
projectSlug: string; projectSlug: string;
selectedRequester: string | undefined; selectedRequester: string | undefined;
selectedEnvSlug: string | undefined; selectedEnvSlug: string | undefined;
}) => { }) => {
const [isLoading, setIsLoading] = useState<"approved" | "rejected" | null>(null); const [isLoading, setIsLoading] = useState<"approved" | "rejected" | null>(null);
const [byPassApproval, setByPassApproval] = useState(false);
const isSoftEnforcement = request.policy.enforcementLevel === EnforcementLevel.Soft;
const accessDetails = { const accessDetails = {
env: request.environmentName, env: request.environmentName,
@@ -134,10 +141,14 @@ export const ReviewAccessRequestModal = ({
<div className="space-x-2"> <div className="space-x-2">
<Button <Button
isLoading={isLoading === "approved"} isLoading={isLoading === "approved"}
isDisabled={!!isLoading} isDisabled={
!!isLoading ||
(!request.isApprover && !byPassApproval && isSoftEnforcement)
}
onClick={() => handleReview("approved")} onClick={() => handleReview("approved")}
className="mt-4" className="mt-4"
size="sm" size="sm"
colorSchema={!request.isApprover && isSoftEnforcement ? "danger" : "primary"}
> >
Approve Request Approve Request
</Button> </Button>
@@ -151,6 +162,21 @@ export const ReviewAccessRequestModal = ({
Reject Request Reject Request
</Button> </Button>
</div> </div>
{isSoftEnforcement && request.isRequestedByCurrentUser && !request.isApprover && (
<div className="mt-4">
<Checkbox
onCheckedChange={(checked) => setByPassApproval(checked === true)}
isChecked={byPassApproval}
id="byPassApproval"
checkIndicatorBg="text-white"
className={byPassApproval ? "bg-red hover:bg-red-600 border-red" : ""}
>
<span className="text-red text-sm">
Approve without waiting for requirements to be met (by pass secrets protection)
</span>
</Checkbox>
</div>
)}
</div> </div>
</ModalContent> </ModalContent>
</Modal> </Modal>