feat: ui changes for secret approval group

This commit is contained in:
=
2024-07-03 20:13:16 +05:30
parent 612cf4f968
commit ef3cdd11ac
9 changed files with 124 additions and 102 deletions

View File

@@ -9,12 +9,12 @@ export const useCreateSecretApprovalPolicy = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<{}, {}, TCreateSecretPolicyDTO>({ return useMutation<{}, {}, TCreateSecretPolicyDTO>({
mutationFn: async ({ environment, workspaceId, approvals, approvers, secretPath, name }) => { mutationFn: async ({ environment, workspaceId, approvals, approverUserIds, secretPath, name }) => {
const { data } = await apiRequest.post("/api/v1/secret-approvals", { const { data } = await apiRequest.post("/api/v1/secret-approvals", {
environment, environment,
workspaceId, workspaceId,
approvals, approvals,
approvers, approverUserIds,
secretPath, secretPath,
name name
}); });
@@ -30,10 +30,10 @@ export const useUpdateSecretApprovalPolicy = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<{}, {}, TUpdateSecretPolicyDTO>({ return useMutation<{}, {}, TUpdateSecretPolicyDTO>({
mutationFn: async ({ id, approvers, approvals, secretPath, name }) => { mutationFn: async ({ id, approverUserIds, approvals, secretPath, name }) => {
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, { const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
approvals, approvals,
approvers, approverUserIds,
secretPath, secretPath,
name name
}); });

View File

@@ -7,8 +7,8 @@ export type TSecretApprovalPolicy = {
envId: string; envId: string;
environment: WorkspaceEnv; environment: WorkspaceEnv;
secretPath?: string; secretPath?: string;
approvers: string[];
approvals: number; approvals: number;
userApprovers: { userId: string }[];
}; };
export type TGetSecretApprovalPoliciesDTO = { export type TGetSecretApprovalPoliciesDTO = {
@@ -26,14 +26,14 @@ export type TCreateSecretPolicyDTO = {
name?: string; name?: string;
environment: string; environment: string;
secretPath?: string | null; secretPath?: string | null;
approvers?: string[]; approverUserIds?: string[];
approvals?: number; approvals?: number;
}; };
export type TUpdateSecretPolicyDTO = { export type TUpdateSecretPolicyDTO = {
id: string; id: string;
name?: string; name?: string;
approvers?: string[]; approverUserIds?: string[];
secretPath?: string | null; secretPath?: string | null;
approvals?: number; approvals?: number;
// for invalidating list // for invalidating list

View File

@@ -47,10 +47,14 @@ export type TSecretApprovalRequest<J extends unknown = EncryptedSecret> = {
isReplicated?: boolean; isReplicated?: boolean;
slug: string; slug: string;
createdAt: string; createdAt: string;
committerId: string; committerUserId: string;
reviewers: { reviewers: {
member: string; userId: string;
status: ApprovalStatus; status: ApprovalStatus;
email: string;
firstName: string;
lastName: string;
username: string;
}[]; }[];
workspace: string; workspace: string;
environment: string; environment: string;
@@ -58,8 +62,30 @@ export type TSecretApprovalRequest<J extends unknown = EncryptedSecret> = {
secretPath: string; secretPath: string;
hasMerged: boolean; hasMerged: boolean;
status: "open" | "close"; status: "open" | "close";
policy: TSecretApprovalPolicy; policy: Omit<TSecretApprovalPolicy, "approvers"> & {
statusChangeBy: string; approvers: {
userId: string;
email: string;
firstName: string;
lastName: string;
username: string;
}[];
};
statusChangedByUserId: string;
statusChangedByUser?: {
userId: string;
email: string;
firstName: string;
lastName: string;
username: string;
};
committerUser: {
userId: string;
email: string;
firstName: string;
lastName: string;
username: string;
};
conflicts: Array<{ secretId: string; op: CommitType.UPDATE }>; conflicts: Array<{ secretId: string; op: CommitType.UPDATE }>;
commits: ({ commits: ({
// if there is no secret means it was creation // if there is no secret means it was creation

View File

@@ -7,6 +7,8 @@ import {
Button, Button,
DeleteActionModal, DeleteActionModal,
EmptyState, EmptyState,
Modal,
ModalContent,
Table, Table,
TableContainer, TableContainer,
TableSkeleton, TableSkeleton,
@@ -145,13 +147,20 @@ export const SecretApprovalPolicyList = ({ workspaceId }: Props) => {
</TBody> </TBody>
</Table> </Table>
</TableContainer> </TableContainer>
<SecretPolicyForm <Modal
workspaceId={workspaceId}
isOpen={popUp.secretPolicyForm.isOpen} isOpen={popUp.secretPolicyForm.isOpen}
onToggle={(isOpen) => handlePopUpToggle("secretPolicyForm", isOpen)} onOpenChange={(isOpen) => handlePopUpToggle("secretPolicyForm", isOpen)}
members={members} >
editValues={popUp.secretPolicyForm.data as TSecretApprovalPolicy} <ModalContent title={popUp.secretPolicyForm.data ? "Edit policy" : "Create policy"}>
/> <SecretPolicyForm
workspaceId={workspaceId}
isOpen={popUp.secretPolicyForm.isOpen}
onToggle={(isOpen) => handlePopUpToggle("secretPolicyForm", isOpen)}
members={members}
editValues={popUp.secretPolicyForm.data as TSecretApprovalPolicy}
/>
</ModalContent>
</Modal>
<DeleteActionModal <DeleteActionModal
isOpen={popUp.deletePolicy.isOpen} isOpen={popUp.deletePolicy.isOpen}
deleteKey="remove" deleteKey="remove"

View File

@@ -51,7 +51,7 @@ export const SecretApprovalPolicyRow = ({
{ {
workspaceId, workspaceId,
id: policy.id, id: policy.id,
approvers: selectedApprovers approverUserIds: selectedApprovers
}, },
{ {
onSettled: () => { onSettled: () => {
@@ -60,7 +60,7 @@ export const SecretApprovalPolicyRow = ({
} }
); );
} else { } else {
setSelectedApprovers(policy.approvers); setSelectedApprovers(policy.userApprovers.map(({ userId }) => userId));
} }
}} }}
> >
@@ -73,7 +73,9 @@ export const SecretApprovalPolicyRow = ({
> >
<Input <Input
isReadOnly isReadOnly
value={policy.approvers?.length ? `${policy.approvers.length} selected` : "None"} value={
policy?.userApprovers.length ? `${policy.userApprovers.length} selected` : "None"
}
className="text-left" className="text-left"
/> />
</DropdownMenuTrigger> </DropdownMenuTrigger>
@@ -84,17 +86,17 @@ export const SecretApprovalPolicyRow = ({
<DropdownMenuLabel> <DropdownMenuLabel>
Select members that are allowed to approve changes Select members that are allowed to approve changes
</DropdownMenuLabel> </DropdownMenuLabel>
{members?.map(({ id, user }) => { {members?.map(({ user }) => {
const isChecked = selectedApprovers.includes(id); const isChecked = selectedApprovers.includes(user.id);
return ( return (
<DropdownMenuItem <DropdownMenuItem
onClick={(evt) => { onClick={(evt) => {
evt.preventDefault(); evt.preventDefault();
setSelectedApprovers((state) => setSelectedApprovers((state) =>
isChecked ? state.filter((el) => el !== id) : [...state, id] isChecked ? state.filter((el) => el !== user.id) : [...state, user.id]
); );
}} }}
key={`create-policy-members-${id}`} key={`create-policy-members-${user.id}`}
iconPos="right" iconPos="right"
icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />} icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />}
> >

View File

@@ -1,4 +1,3 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons"; import { faCheckCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -15,8 +14,6 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
FormControl, FormControl,
Input, Input,
Modal,
ModalContent,
Select, Select,
SelectItem SelectItem
} from "@app/components/v2"; } from "@app/components/v2";
@@ -40,9 +37,9 @@ const formSchema = z
name: z.string().optional(), name: z.string().optional(),
secretPath: z.string().optional().nullable(), secretPath: z.string().optional().nullable(),
approvals: z.number().min(1), approvals: z.number().min(1),
approvers: z.string().array().min(1) approverUserIds: z.string().array().min(1)
}) })
.refine((data) => data.approvals <= data.approvers.length, { .refine((data) => data.approvals <= data.approverUserIds.length, {
path: ["approvals"], path: ["approvals"],
message: "The number of approvals should be lower than the number of approvers." message: "The number of approvals should be lower than the number of approvers."
}); });
@@ -50,7 +47,6 @@ const formSchema = z
type TFormSchema = z.infer<typeof formSchema>; type TFormSchema = z.infer<typeof formSchema>;
export const SecretPolicyForm = ({ export const SecretPolicyForm = ({
isOpen,
onToggle, onToggle,
members = [], members = [],
workspaceId, workspaceId,
@@ -59,20 +55,22 @@ export const SecretPolicyForm = ({
const { const {
control, control,
handleSubmit, handleSubmit,
reset,
watch, watch,
formState: { isSubmitting } formState: { isSubmitting }
} = useForm<TFormSchema>({ } = useForm<TFormSchema>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
values: editValues ? { ...editValues, environment: editValues.environment.slug } : undefined values: editValues
? {
...editValues,
approverUserIds: editValues.userApprovers.map(({ userId }) => userId),
environment: editValues.environment.slug
}
: undefined
}); });
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const selectedEnvironment = watch("environment"); const selectedEnvironment = watch("environment");
const environments = currentWorkspace?.environments || []; const environments = currentWorkspace?.environments || [];
useEffect(() => {
if (!isOpen) reset({});
}, [isOpen]);
const isEditMode = Boolean(editValues); const isEditMode = Boolean(editValues);
@@ -131,8 +129,6 @@ export const SecretPolicyForm = ({
}; };
return ( return (
<Modal isOpen={isOpen} onOpenChange={onToggle}>
<ModalContent title={isEditMode ? "Edit policy" : "Create policy"}>
<form onSubmit={handleSubmit(handleFormSubmit)}> <form onSubmit={handleSubmit(handleFormSubmit)}>
<Controller <Controller
control={control} control={control}
@@ -155,6 +151,7 @@ export const SecretPolicyForm = ({
errorText={error?.message} errorText={error?.message}
> >
<Select <Select
isDisabled={isEditMode}
value={value} value={value}
onValueChange={(val) => onChange(val)} onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500" className="w-full border border-mineshaft-500"
@@ -186,7 +183,7 @@ export const SecretPolicyForm = ({
/> />
<Controller <Controller
control={control} control={control}
name="approvers" name="approverUserIds"
render={({ field: { value, onChange }, fieldState: { error } }) => ( render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl <FormControl
label="Approvers Required" label="Approvers Required"
@@ -208,17 +205,19 @@ export const SecretPolicyForm = ({
<DropdownMenuLabel> <DropdownMenuLabel>
Select members that are allowed to approve changes Select members that are allowed to approve changes
</DropdownMenuLabel> </DropdownMenuLabel>
{members.map(({ id, user }) => { {members.map(({ user }) => {
const isChecked = value?.includes(id); const isChecked = value?.includes(user.id);
return ( return (
<DropdownMenuItem <DropdownMenuItem
onClick={(evt) => { onClick={(evt) => {
evt.preventDefault(); evt.preventDefault();
onChange( onChange(
isChecked ? value?.filter((el) => el !== id) : [...(value || []), id] isChecked
? value?.filter((el) => el !== user.id)
: [...(value || []), user.id]
); );
}} }}
key={`create-policy-members-${id}`} key={`create-policy-members-${user.id}`}
iconPos="right" iconPos="right"
icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />} icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />}
> >
@@ -258,7 +257,6 @@ export const SecretPolicyForm = ({
</Button> </Button>
</div> </div>
</form> </form>
</ModalContent>
</Modal>
); );
}; };

View File

@@ -31,7 +31,7 @@ import {
useGetSecretApprovalRequests, useGetSecretApprovalRequests,
useGetWorkspaceUsers useGetWorkspaceUsers
} from "@app/hooks/api"; } from "@app/hooks/api";
import { ApprovalStatus, TSecretApprovalRequest, TWorkspaceUser } from "@app/hooks/api/types"; import { ApprovalStatus, TSecretApprovalRequest } from "@app/hooks/api/types";
import { import {
generateCommitText, generateCommitText,
@@ -63,14 +63,9 @@ export const SecretApprovalRequest = () => {
}); });
const { data: secretApprovalRequestCount, isSuccess: isSecretApprovalReqCountSuccess } = const { data: secretApprovalRequestCount, isSuccess: isSecretApprovalReqCountSuccess } =
useGetSecretApprovalRequestCount({ workspaceId }); useGetSecretApprovalRequestCount({ workspaceId });
const { user: presentUser } = useUser(); const { user: userSession } = useUser();
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
const { data: members } = useGetWorkspaceUsers(workspaceId); const { data: members } = useGetWorkspaceUsers(workspaceId);
const membersGroupById = members?.reduce<Record<string, TWorkspaceUser>>(
(prev, curr) => ({ ...prev, [curr.id]: curr }),
{}
);
const myMembershipId = members?.find(({ user }) => user.id === presentUser?.id)?.id;
const isSecretApprovalScreen = Boolean(selectedApproval); const isSecretApprovalScreen = Boolean(selectedApproval);
const handleGoBackSecretRequestDetail = () => { const handleGoBackSecretRequestDetail = () => {
@@ -93,10 +88,8 @@ export const SecretApprovalRequest = () => {
> >
<SecretApprovalRequestChanges <SecretApprovalRequestChanges
workspaceId={workspaceId} workspaceId={workspaceId}
members={membersGroupById}
approvalRequestId={selectedApproval?.id || ""} approvalRequestId={selectedApproval?.id || ""}
onGoBack={handleGoBackSecretRequestDetail} onGoBack={handleGoBackSecretRequestDetail}
committer={membersGroupById?.[selectedApproval?.committerId || ""]}
/> />
</motion.div> </motion.div>
) : ( ) : (
@@ -182,10 +175,12 @@ export const SecretApprovalRequest = () => {
{members?.map(({ user, id }) => ( {members?.map(({ user, id }) => (
<DropdownMenuItem <DropdownMenuItem
onClick={() => onClick={() =>
setCommitterFilter((state) => (state === id ? undefined : id)) setCommitterFilter((state) => (state === user.id ? undefined : user.id))
} }
key={`request-filter-member-${id}`} key={`request-filter-member-${id}`}
icon={committerFilter === id && <FontAwesomeIcon icon={faCheckCircle} />} icon={
committerFilter === user.id && <FontAwesomeIcon icon={faCheckCircle} />
}
iconPos="right" iconPos="right"
> >
{user.username} {user.username}
@@ -208,19 +203,16 @@ export const SecretApprovalRequest = () => {
const { const {
id: reqId, id: reqId,
commits, commits,
committerId,
createdAt, createdAt,
policy,
reviewers, reviewers,
status, status,
committerUser,
isReplicated: isReplication isReplicated: isReplication
} = secretApproval; } = secretApproval;
const isApprover = policy?.approvers?.indexOf(myMembershipId || "") !== -1; const isReviewed = reviewers.some(
const isReviewed = ({ status: reviewStatus, userId }) =>
reviewers.findIndex( userId === userSession.id && reviewStatus === ApprovalStatus.APPROVED
({ member, status: reviewStatus }) => );
member === myMembershipId && reviewStatus === ApprovalStatus.APPROVED
) !== -1;
return ( return (
<div <div
key={reqId} key={reqId}
@@ -239,11 +231,9 @@ export const SecretApprovalRequest = () => {
</div> </div>
<span className="text-xs text-gray-500"> <span className="text-xs text-gray-500">
Opened {formatDistance(new Date(createdAt), new Date())} ago by{" "} Opened {formatDistance(new Date(createdAt), new Date())} ago by{" "}
{membersGroupById?.[committerId]?.user?.firstName}{" "} {committerUser?.firstName || ""} {committerUser?.lastName || ""} (
{membersGroupById?.[committerId]?.user?.lastName} ( {committerUser?.email}){isReplication && " via replication"}
{membersGroupById?.[committerId]?.user?.email}) {!isReviewed && status === "open" && " - Review required"}
{isReplication && " via replication"}
{isApprover && !isReviewed && status === "open" && " - Review required"}
</span> </span>
</div> </div>
); );

View File

@@ -23,7 +23,7 @@ type Props = {
status: "close" | "open"; status: "close" | "open";
approvals: number; approvals: number;
canApprove?: boolean; canApprove?: boolean;
statusChangeByEmail: string; statusChangeByEmail?: string;
workspaceId: string; workspaceId: string;
}; };

View File

@@ -19,7 +19,7 @@ import {
useGetUserWsKey, useGetUserWsKey,
useUpdateSecretApprovalReviewStatus useUpdateSecretApprovalReviewStatus
} from "@app/hooks/api"; } from "@app/hooks/api";
import { ApprovalStatus, CommitType, TWorkspaceUser } from "@app/hooks/api/types"; import { ApprovalStatus, CommitType } from "@app/hooks/api/types";
import { formatReservedPaths } from "@app/lib/fn/string"; import { formatReservedPaths } from "@app/lib/fn/string";
import { SecretApprovalRequestAction } from "./SecretApprovalRequestAction"; import { SecretApprovalRequestAction } from "./SecretApprovalRequestAction";
@@ -73,18 +73,14 @@ type Props = {
workspaceId: string; workspaceId: string;
approvalRequestId: string; approvalRequestId: string;
onGoBack: () => void; onGoBack: () => void;
committer?: TWorkspaceUser;
members?: Record<string, TWorkspaceUser>;
}; };
export const SecretApprovalRequestChanges = ({ export const SecretApprovalRequestChanges = ({
approvalRequestId, approvalRequestId,
onGoBack, onGoBack,
committer, workspaceId
workspaceId,
members = {}
}: Props) => { }: Props) => {
const { user } = useUser(); const { user: userSession } = useUser();
const { data: decryptFileKey } = useGetUserWsKey(workspaceId); const { data: decryptFileKey } = useGetUserWsKey(workspaceId);
const { const {
data: secretApprovalRequestDetails, data: secretApprovalRequestDetails,
@@ -105,22 +101,20 @@ export const SecretApprovalRequestChanges = ({
const isRejecting = variables?.status === ApprovalStatus.REJECTED && isUpdatingRequestStatus; const isRejecting = variables?.status === ApprovalStatus.REJECTED && isUpdatingRequestStatus;
// membership of present user // membership of present user
const myMembership = Object.values(members).find( const canApprove = secretApprovalRequestDetails?.policy?.approvers?.some(
({ user: membershipUser }) => membershipUser.email === user.email ({ userId }) => userId === userSession.id
); );
const myMembershipId = myMembership?.id || ""; const reviewedUsers = secretApprovalRequestDetails?.reviewers?.reduce<
const canApprove = secretApprovalRequestDetails?.policy?.approvers?.includes(myMembershipId);
const reviewedMembers = secretApprovalRequestDetails?.reviewers?.reduce<
Record<string, ApprovalStatus> Record<string, ApprovalStatus>
>( >(
(prev, curr) => ({ (prev, curr) => ({
...prev, ...prev,
[curr.member]: curr.status [curr.userId]: curr.status
}), }),
{} {}
); );
const hasApproved = reviewedMembers?.[myMembershipId] === ApprovalStatus.APPROVED; const hasApproved = reviewedUsers?.[userSession.id] === ApprovalStatus.APPROVED;
const hasRejected = reviewedMembers?.[myMembershipId] === ApprovalStatus.REJECTED; const hasRejected = reviewedUsers?.[userSession.id] === ApprovalStatus.REJECTED;
const handleSecretApprovalStatusUpdate = async (status: ApprovalStatus) => { const handleSecretApprovalStatusUpdate = async (status: ApprovalStatus) => {
try { try {
@@ -159,7 +153,7 @@ export const SecretApprovalRequestChanges = ({
const isMergable = const isMergable =
secretApprovalRequestDetails?.policy?.approvals <= secretApprovalRequestDetails?.policy?.approvals <=
secretApprovalRequestDetails?.policy?.approvers?.filter( secretApprovalRequestDetails?.policy?.approvers?.filter(
(approverId) => reviewedMembers?.[approverId] === ApprovalStatus.APPROVED ({ userId }) => reviewedUsers?.[userId] === ApprovalStatus.APPROVED
).length; ).length;
const hasMerged = secretApprovalRequestDetails?.hasMerged; const hasMerged = secretApprovalRequestDetails?.hasMerged;
@@ -191,8 +185,9 @@ export const SecretApprovalRequestChanges = ({
)} )}
</div> </div>
<div className="flex items-center text-sm text-bunker-300"> <div className="flex items-center text-sm text-bunker-300">
{committer?.user?.firstName} {secretApprovalRequestDetails?.committerUser?.firstName || ""}
{committer?.user?.lastName} ({committer?.user?.email}) wants to change{" "} {secretApprovalRequestDetails?.committerUser?.lastName || ""} (
{secretApprovalRequestDetails?.committerUser?.email}) wants to change{" "}
{secretApprovalRequestDetails.commits.length} secret values in {secretApprovalRequestDetails.commits.length} secret values in
<span className="mx-1 rounded bg-primary-600/60 px-1 text-primary-300"> <span className="mx-1 rounded bg-primary-600/60 px-1 text-primary-300">
{secretApprovalRequestDetails.environment} {secretApprovalRequestDetails.environment}
@@ -256,9 +251,7 @@ export const SecretApprovalRequestChanges = ({
approvals={secretApprovalRequestDetails.policy.approvals || 0} approvals={secretApprovalRequestDetails.policy.approvals || 0}
status={secretApprovalRequestDetails.status} status={secretApprovalRequestDetails.status}
isMergable={isMergable} isMergable={isMergable}
statusChangeByEmail={ statusChangeByEmail={secretApprovalRequestDetails.statusChangedByUser?.email}
members[secretApprovalRequestDetails?.statusChangeBy || ""]?.user?.email || ""
}
workspaceId={workspaceId} workspaceId={workspaceId}
/> />
</div> </div>
@@ -266,17 +259,19 @@ export const SecretApprovalRequestChanges = ({
<div className="sticky top-0 w-1/5 pt-4" style={{ minWidth: "240px" }}> <div className="sticky top-0 w-1/5 pt-4" style={{ minWidth: "240px" }}>
<div className="text-sm text-bunker-300">Reviewers</div> <div className="text-sm text-bunker-300">Reviewers</div>
<div className="mt-2 flex flex-col space-y-2 text-sm"> <div className="mt-2 flex flex-col space-y-2 text-sm">
{secretApprovalRequestDetails?.policy?.approvers.map((requiredApproverId) => { {secretApprovalRequestDetails?.policy?.approvers.map((requiredApprover) => {
const userDetails = members?.[requiredApproverId]?.user; const status = reviewedUsers?.[requiredApprover.userId];
const status = reviewedMembers?.[requiredApproverId];
return ( return (
<div <div
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1" className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
key={`required-approver-${requiredApproverId}`} key={`required-approver-${requiredApprover.userId}`}
> >
<div className="flex-grow text-sm"> <div className="flex-grow text-sm">
<Tooltip content={`${userDetails.firstName} ${userDetails.lastName}`}> <Tooltip
<span>{userDetails?.email} </span> content={`${requiredApprover.firstName || ""} ${requiredApprover.lastName || ""
}`}
>
<span>{requiredApprover?.email} </span>
</Tooltip> </Tooltip>
<span className="text-red">*</span> <span className="text-red">*</span>
</div> </div>
@@ -290,19 +285,21 @@ export const SecretApprovalRequestChanges = ({
})} })}
{secretApprovalRequestDetails?.reviewers {secretApprovalRequestDetails?.reviewers
.filter( .filter(
({ member }) => !secretApprovalRequestDetails?.policy?.approvers?.includes(member) (reviewer) =>
!secretApprovalRequestDetails?.policy?.approvers?.some(
({ userId }) => userId === reviewer.userId
)
) )
.map((reviewer) => { .map((reviewer) => {
const userDetails = members?.[reviewer.member]?.user; const status = reviewedUsers?.[reviewer.userId];
const status = reviewedMembers?.[reviewer.status];
return ( return (
<div <div
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1" className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
key={`required-approver-${reviewer.member}`} key={`required-approver-${reviewer.userId}`}
> >
<div className="flex-grow text-sm"> <div className="flex-grow text-sm">
<Tooltip content={`${userDetails.firstName} ${userDetails.lastName}`}> <Tooltip content={`${reviewer.firstName || ""} ${reviewer.lastName || ""}`}>
<span>{userDetails?.email} </span> <span>{reviewer?.email} </span>
</Tooltip> </Tooltip>
<span className="text-red">*</span> <span className="text-red">*</span>
</div> </div>