improve change requests design

This commit is contained in:
Vladyslav Matsiiako
2025-05-25 15:53:30 -07:00
parent 8d147867ed
commit 8d5e7406c3
4 changed files with 225 additions and 256 deletions
@@ -44,8 +44,7 @@ export const SecretApprovalsPage = () => {
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 text-white"> <div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 text-white">
<PageHeader <PageHeader
title="Approval Workflows" title="Approval Workflows"
description="Create approval policies for any modifications to secrets in sensitive environments and folders. description="Create approval policies for any modifications to secrets in sensitive environments and folders."
"
> >
<a <a
href="https://infisical.com/docs/documentation/platform/pr-workflows" href="https://infisical.com/docs/documentation/platform/pr-workflows"
@@ -1,13 +1,16 @@
import { useState } from "react"; import { useState } from "react";
import { import {
faCheck, faCheck,
faCircleCheck,
faCircleXmark,
faClose, faClose,
faLandMineOn, faLandMineOn,
faLockOpen, faLockOpen,
faSquareCheck, faSquareCheck,
faSquareXmark, faSquareXmark,
faTriangleExclamation, faTriangleExclamation,
faUserLock faUserLock,
faXmark
} 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 { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -112,62 +115,67 @@ export const SecretApprovalRequestAction = ({
if (!hasMerged && status === "open") { if (!hasMerged && status === "open") {
return ( return (
<div className="flex w-full items-start justify-between transition-all"> <div className="flex flex-col w-full items-start justify-between transition-all py-4">
<div className="flex items-start space-x-4"> <div className="flex items-center space-x-4 px-4">
<FontAwesomeIcon <div className={`flex items-center justify-center rounded-full ${isMergable ? "bg-green w-10 h-10" : "bg-red-600 w-11 h-11"}`}>
icon={isMergable ? faSquareCheck : faSquareXmark} <FontAwesomeIcon
className={twMerge("pt-1 text-2xl", isMergable ? "text-primary" : "text-red-600")} icon={isMergable ? faCheck : faXmark}
/> className={isMergable ? "text-black text-lg" : "text-white text-2xl"}
/>
</div>
<span className="flex flex-col"> <span className="flex flex-col">
{isMergable ? "Good to merge" : "Review required"} <p className={`text-md font-medium ${isMergable && "text-lg"}`}>{isMergable ? "Good to merge" : "Merging is blocked"}</p>
<span className="inline-block text-xs text-bunker-200"> {!isMergable && <span className="inline-block text-xs text-bunker-200">
At least {approvals} approving review required At least {approvals} approving review{`${approvals > 1 ? "s" : ""}`} required by eligible reviewers.
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`} {Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
</span> </span>}
{isSoftEnforcement && !isMergable && canBypassApprovalPermission && (
<div className="mt-2 flex flex-col space-y-2">
<Checkbox
onCheckedChange={(checked) => setByPassApproval(checked === true)}
isChecked={byPassApproval}
id="byPassApproval"
checkIndicatorBg="text-white"
className={twMerge(
"mr-2",
byPassApproval ? "border-red bg-red hover:bg-red-600" : ""
)}
>
<span className="text-xs text-red">
Merge without waiting for approval (bypass secret change policy)
</span>
</Checkbox>
{byPassApproval && (
<FormControl
label="Reason for bypass"
className="mt-2"
isRequired
tooltipText="Enter a reason for bypassing the secret change policy"
>
<Input
value={bypassReason}
onChange={(e) => setBypassReason(e.target.value)}
placeholder="Enter reason for bypass (min 10 chars)"
leftIcon={<FontAwesomeIcon icon={faTriangleExclamation} />}
/>
</FormControl>
)}
</div>
)}
</span> </span>
</div> </div>
<div className="flex items-center space-x-2"> <div className={`w-full px-5 border-mineshaft-600 mt-4 ${isMergable ? "border-t pb-2" : "border-y pb-4"}`}>
{isSoftEnforcement && !isMergable && canBypassApprovalPermission && (
<div className="mt-2 pt-2 flex flex-col space-y-2">
<Checkbox
onCheckedChange={(checked) => setByPassApproval(checked === true)}
isChecked={byPassApproval}
id="byPassApproval"
checkIndicatorBg="text-white"
className={twMerge(
"mr-2",
byPassApproval ? "border-red bg-red hover:bg-red-600" : ""
)}
>
<span className="text-sm">
Merge without waiting for approval (bypass secret change policy)
</span>
</Checkbox>
{byPassApproval && (
<FormControl
label="Reason for bypass"
className="mt-2"
isRequired
tooltipText="Enter a reason for bypassing the secret change policy"
>
<Input
value={bypassReason}
onChange={(e) => setBypassReason(e.target.value)}
placeholder="Enter reason for bypass (min 10 chars)"
leftIcon={<FontAwesomeIcon icon={faTriangleExclamation} />}
/>
</FormControl>
)}
</div>
)}
</div>
<div className="flex items-center justify-end w-full space-x-2 px-4 mt-2">
{canApprove || isSoftEnforcement ? ( {canApprove || isSoftEnforcement ? (
<> <div className="flex items-center space-x-4">
<Button <Button
onClick={() => handleSecretApprovalStatusChange("close")} onClick={() => handleSecretApprovalStatusChange("close")}
isLoading={isStatusChanging} isLoading={isStatusChanging}
variant="outline_bg" variant="outline_bg"
colorSchema="secondary" colorSchema="primary"
leftIcon={<FontAwesomeIcon icon={faClose} />} leftIcon={<FontAwesomeIcon icon={faClose} />}
className="hover:bg-red/10 hover:border-red/60"
> >
Close request Close request
</Button> </Button>
@@ -186,7 +194,7 @@ export const SecretApprovalRequestAction = ({
> >
Merge Merge
</Button> </Button>
</> </div>
) : ( ) : (
<div>Only approvers can merge</div> <div>Only approvers can merge</div>
)} )}
@@ -197,13 +205,13 @@ export const SecretApprovalRequestAction = ({
if (hasMerged && status === "close") if (hasMerged && status === "close")
return ( return (
<div className="flex w-full items-center justify-between"> <div className="flex w-full items-center justify-between bg-primary/10 border border-primary/60 rounded-md">
<div className="flex items-start space-x-4"> <div className="flex items-start space-x-4 p-4">
<FontAwesomeIcon icon={faCheck} className="pt-1 text-2xl text-primary" /> <FontAwesomeIcon icon={faCheck} className="pt-1 text-2xl text-primary" />
<span className="flex flex-col"> <span className="flex flex-col">
Secret approval merged Change request merged
<span className="inline-block text-xs text-bunker-200"> <span className="inline-block text-xs text-bunker-200">
Merged by {statusChangeByEmail} Merged by {statusChangeByEmail}.
</span> </span>
</span> </span>
</div> </div>
@@ -1,19 +1,12 @@
import { faExclamationTriangle, faInfo, faKey } from "@fortawesome/free-solid-svg-icons"; import { faCircleXmark, faExclamationTriangle, faEye, faEyeSlash, faInfo, faKey } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { import {
SecretInput,
Table,
TableContainer,
Tag, Tag,
TBody, Tooltip
Td,
Th,
THead,
Tooltip,
Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { CommitType, SecretV3Raw, TSecretApprovalSecChange, WsTag } from "@app/hooks/api/types"; import { CommitType, SecretV3Raw, TSecretApprovalSecChange, WsTag } from "@app/hooks/api/types";
import { useState } from "react";
export type Props = { export type Props = {
op: CommitType; op: CommitType;
@@ -29,19 +22,19 @@ export type Props = {
const generateItemTitle = (op: CommitType) => { const generateItemTitle = (op: CommitType) => {
let text = { label: "", color: "" }; let text = { label: "", color: "" };
if (op === CommitType.CREATE) text = { label: "create", color: "#16a34a" }; if (op === CommitType.CREATE) text = { label: "create", color: "#60DD00" };
else if (op === CommitType.UPDATE) text = { label: "change", color: "#ea580c" }; else if (op === CommitType.UPDATE) text = { label: "change", color: "#F8EB30" };
else text = { label: "deletion", color: "#b91c1c" }; else text = { label: "deletion", color: "#F83030" };
return ( return (
<span> <div className="text-md font-medium pb-2">
Request for <span style={{ color: text.color }}>secret {text.label}</span> Request for <span style={{ color: text.color }}>secret {text.label}</span>
</span> </div>
); );
}; };
const generateConflictText = (op: CommitType) => { const generateConflictText = (op: CommitType) => {
if (op === CommitType.CREATE) return <div>Secret already exist</div>; if (op === CommitType.CREATE) return <div>Secret already exists</div>;
if (op === CommitType.UPDATE) return <div>Secret not found</div>; if (op === CommitType.UPDATE) return <div>Secret not found</div>;
return null; return null;
}; };
@@ -59,10 +52,12 @@ export const SecretApprovalRequestChangeItem = ({
const itemConflict = const itemConflict =
hasMerged && conflicts.find((el) => el.op === op && el.secretId === newVersion?.id); hasMerged && conflicts.find((el) => el.op === op && el.secretId === newVersion?.id);
const hasConflict = Boolean(itemConflict); const hasConflict = Boolean(itemConflict);
const [isOldSecretValueVisible, setIsOldSecretValueVisible] = useState(false);
const [isNewSecretValueVisible, setIsNewSecretValueVisible] = useState(false);
return ( return (
<div className="rounded-lg bg-bunker-500 px-4 pb-4 pt-2"> <div className="rounded-lg bg-mineshaft-900 px-4 pb-4 pt-2 border border-mineshaft-600">
<div className="flex items-center px-1 py-3"> <div className="flex items-center px-1 py-1">
<div className="flex-grow">{generateItemTitle(op)}</div> <div className="flex-grow">{generateItemTitle(op)}</div>
{!hasMerged && isStale && ( {!hasMerged && isStale && (
<div className="flex items-center"> <div className="flex items-center">
@@ -79,35 +74,43 @@ export const SecretApprovalRequestChangeItem = ({
</div> </div>
)} )}
</div> </div>
<TableContainer> <div>
<Table> <div className="flex flex-col xl:flex-row space-y-4 xl:space-y-0 space-x-0 xl:space-x-4">
<THead> {op === CommitType.UPDATE || op === CommitType.DELETE ? (
<Tr> <div className="flex flex-col border border-red-600/60 bg-red-600/10 p-4 w-full xl:w-1/2 rounded-md cursor-default">
{op === CommitType.UPDATE && <Th className="w-12 shrink-0" />} <div className="flex flex-row justify-between mb-4">
<Th className="w-48 shrink-0">Secret</Th> <span className="text-md font-medium">Legacy Secret</span>
<Th className="min-w-0 flex-1">Value</Th> <div className="pt-[0.2rem] pb-[0.14rem] px-2 bg-red text-xs rounded-full font-medium">
<Th className="w-24 shrink-0">Comment</Th> <FontAwesomeIcon icon={faCircleXmark} className="text-white pr-1" />
<Th className="w-24 shrink-0">Tags</Th> Deprecated
<Th className="w-40 shrink-0">Metadata</Th> </div>
</Tr> </div>
</THead> <div className="mb-2">
{op === CommitType.UPDATE ? ( <div className="text-sm text-mineshaft-300 font-medium">Key</div>
<TBody> <div className="text-sm">{secretVersion?.secretKey} </div>
<Tr> </div>
<Td className="text-red-600">OLD</Td> <div className="mb-2">
<Td>{secretVersion?.secretKey}</Td> <div className="text-sm text-mineshaft-300 font-medium">Value</div>
<Td> <div className="text-sm">{newVersion?.isRotatedSecret ? (
{newVersion?.isRotatedSecret ? ( <span className="text-mineshaft-400">
<span className="text-mineshaft-400"> Rotated Secret value will not be affected
Rotated Secret value will not be affected </span>
</span> ) : (
) : ( <div onClick={() => setIsOldSecretValueVisible(!isOldSecretValueVisible)} className="pl-2 border border-mineshaft-500 bg-mineshaft-900 rounded-md flex flex-row justify-between items-center">
<SecretInput isReadOnly value={secretVersion?.secretValue} /> <div className={`flex font-mono ${isOldSecretValueVisible || !secretVersion?.secretValue ? "text-md py-[0.55rem]" : "text-lg"}`}>{isOldSecretValueVisible ? (secretVersion?.secretValue || "EMPTY") : (secretVersion?.secretValue ? secretVersion?.secretValue?.split('').map((_, index) => "•") : "EMPTY")} </div>
)} {secretVersion?.secretValue && <div className="flex items-center w-10 h-10 justify-center"><FontAwesomeIcon icon={isOldSecretValueVisible ? faEyeSlash : faEye} className="text-mineshaft-300 p-1.5 border border-mineshaft-500 rounded-md bg-mineshaft-800 hover:bg-mineshaft-700 cursor-pointer" /></div>}
</Td> </div>
<Td>{secretVersion?.secretComment}</Td> )}
<Td className="flex flex-wrap gap-2"> </div>
{secretVersion?.tags?.map(({ slug, id: tagId, color }) => ( </div>
<div className="mb-2">
<div className="text-sm text-mineshaft-300 font-medium">Comment</div>
<div className="text-sm">{secretVersion?.secretComment || <span className="text-sm text-mineshaft-300">-</span>} </div>
</div>
<div className="mb-2">
<div className="text-sm text-mineshaft-300 font-medium">Tags</div>
<div className="flex flex-wrap gap-2">
{secretVersion?.tags?.length ?? 0 ? secretVersion?.tags?.map(({ slug, id: tagId, color }) => (
<Tag <Tag
className="flex w-min items-center space-x-2" className="flex w-min items-center space-x-2"
key={`${secretVersion.id}-${tagId}`} key={`${secretVersion.id}-${tagId}`}
@@ -118,9 +121,12 @@ export const SecretApprovalRequestChangeItem = ({
/> />
<div className="text-sm">{slug}</div> <div className="text-sm">{slug}</div>
</Tag> </Tag>
))} )) : <span className="text-sm text-mineshaft-300">-</span>}
</Td> </div>
<Td> </div>
<div className="mb-2">
<div className="text-sm text-mineshaft-300 font-medium">Metadata</div>
<div>
{secretVersion?.secretMetadata?.length ? ( {secretVersion?.secretMetadata?.length ? (
<div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300"> <div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300">
{secretVersion.secretMetadata?.map((el) => ( {secretVersion.secretMetadata?.map((el) => (
@@ -146,23 +152,45 @@ export const SecretApprovalRequestChangeItem = ({
) : ( ) : (
<p className="text-sm text-mineshaft-300">-</p> <p className="text-sm text-mineshaft-300">-</p>
)} )}
</Td> </div>
</Tr> </div>
<Tr> </div>)
<Td className="text-green-600">NEW</Td> : <div className="w-full xl:w-1/2 bg-mineshaft-800 border border-mineshaft-600 rounded-md flex items-center justify-center text-md text-mineshaft-300"> Secret not existent in the previous version.</div>}
<Td>{newVersion?.secretKey}</Td> {op === CommitType.UPDATE || op === CommitType.CREATE ? (
<Td> <div className="flex flex-col border border-green-600/60 bg-green-600/10 p-4 w-full xl:w-1/2 rounded-md cursor-default">
{newVersion?.isRotatedSecret ? ( <div className="flex flex-row justify-between mb-4">
<span className="text-mineshaft-400"> <span className="text-md font-medium">New Secret</span>
Rotated Secret value will not be affected <div className="pt-[0.2rem] pb-[0.14rem] px-2 bg-green-600 text-xs rounded-full font-medium">
</span> <FontAwesomeIcon icon={faCircleXmark} className="text-white pr-1" />
) : ( Current
<SecretInput isReadOnly value={newVersion?.secretValue} /> </div>
)} </div>
</Td> <div className="mb-2">
<Td>{newVersion?.secretComment}</Td> <div className="text-sm text-mineshaft-300 font-medium">Key</div>
<Td className="flex flex-wrap gap-2"> <div className="text-sm">{newVersion?.secretKey} </div>
{newVersion?.tags?.map(({ slug, id: tagId, color }) => ( </div>
<div className="mb-2">
<div className="text-sm text-mineshaft-300 font-medium">Value</div>
<div className="text-sm">{newVersion?.isRotatedSecret ? (
<span className="text-mineshaft-400">
Rotated Secret value will not be affected
</span>
) : (
<div onClick={() => setIsNewSecretValueVisible(!isNewSecretValueVisible)} className="pl-2 border border-mineshaft-500 bg-mineshaft-900 rounded-md flex flex-row justify-between items-center">
<div className={`flex font-mono ${isNewSecretValueVisible || !newVersion?.secretValue ? "text-md py-[0.55rem]" : "text-lg"}`}>{isNewSecretValueVisible ? (newVersion?.secretValue || "EMPTY") : (newVersion?.secretValue ? newVersion?.secretValue?.split('').map((_, index) => "•") : "EMPTY")} </div>
{newVersion?.secretValue && <div className="flex items-center w-10 h-10 justify-center"><FontAwesomeIcon icon={isNewSecretValueVisible ? faEyeSlash : faEye} className="text-mineshaft-300 p-1.5 border border-mineshaft-500 rounded-md bg-mineshaft-800 hover:bg-mineshaft-700 cursor-pointer" /></div>}
</div>
)}
</div>
</div>
<div className="mb-2">
<div className="text-sm text-mineshaft-300 font-medium">Comment</div>
<div className="text-sm">{newVersion?.secretComment || <span className="text-sm text-mineshaft-300">-</span>} </div>
</div>
<div className="mb-2">
<div className="text-sm text-mineshaft-300 font-medium">Tags</div>
<div className="flex flex-wrap gap-2">
{newVersion?.tags?.length ?? 0 ? newVersion?.tags?.map(({ slug, id: tagId, color }) => (
<Tag <Tag
className="flex w-min items-center space-x-2" className="flex w-min items-center space-x-2"
key={`${newVersion.id}-${tagId}`} key={`${newVersion.id}-${tagId}`}
@@ -173,108 +201,41 @@ export const SecretApprovalRequestChangeItem = ({
/> />
<div className="text-sm">{slug}</div> <div className="text-sm">{slug}</div>
</Tag> </Tag>
))} )) : <span className="text-sm text-mineshaft-300">-</span>}
</Td> </div>
<Td> </div>
{newVersion?.secretMetadata?.length ? ( <div className="mb-2">
<div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300"> <div className="text-sm text-mineshaft-300 font-medium">Metadata</div>
{newVersion.secretMetadata?.map((el) => ( {newVersion?.secretMetadata?.length ? (
<div key={el.key} className="flex items-center"> <div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300">
<Tag {newVersion.secretMetadata?.map((el) => (
size="xs" <div key={el.key} className="flex items-center">
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500" <Tag
> size="xs"
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" /> className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
<div>{el.key}</div> >
</Tag> <FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
<Tag <div>{el.key}</div>
size="xs" </Tag>
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1" <Tag
> size="xs"
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap"> className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
{el.value} >
</div> <div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
</Tag> {el.value}
</div> </div>
))} </Tag>
</div> </div>
) : ( ))}
<p className="text-sm text-mineshaft-300">-</p> </div>
)} ) : (
</Td> <p className="text-sm text-mineshaft-300">-</p>
</Tr> )}
</TBody> </div>
) : ( </div>)
<TBody> : <div className="w-full xl:w-1/2 bg-mineshaft-800 border border-mineshaft-600 rounded-md flex items-center justify-center text-md text-mineshaft-300"> Secret not existent in the new version.</div>}
<Tr> </div>
<Td> </div>
{op === CommitType.CREATE ? newVersion?.secretKey : secretVersion?.secretKey}
</Td>
<Td>
<SecretInput
isReadOnly
value={
op === CommitType.CREATE
? newVersion?.secretValue
: secretVersion?.secretValue
}
/>
</Td>
<Td>
{op === CommitType.CREATE
? newVersion?.secretComment
: secretVersion?.secretComment}
</Td>
<Td>
{(op === CommitType.CREATE ? newVersion?.tags : secretVersion?.tags)?.map(
({ slug, id: tagId, color }) => (
<Tag
className="flex w-min items-center space-x-2"
key={`${
op === CommitType.CREATE ? newVersion?.id : secretVersion?.id
}-${tagId}`}
>
<div
className="h-3 w-3 rounded-full"
style={{ backgroundColor: color || "#bec2c8" }}
/>
<div className="text-sm">{slug}</div>
</Tag>
)
)}
</Td>
<Td>
{newVersion?.secretMetadata?.length ? (
<div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300">
{newVersion.secretMetadata?.map((el) => (
<div key={el.key} className="flex items-center">
<Tag
size="xs"
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
<div>{el.key}</div>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
</Tag>
</div>
))}
</div>
) : (
<p className="text-sm text-mineshaft-300">-</p>
)}
</Td>
</Tr>
</TBody>
)}
</Table>
</TableContainer>
</div> </div>
); );
}; };
@@ -40,6 +40,7 @@ import { formatReservedPaths } from "@app/lib/fn/string";
import { SecretApprovalRequestAction } from "./SecretApprovalRequestAction"; import { SecretApprovalRequestAction } from "./SecretApprovalRequestAction";
import { SecretApprovalRequestChangeItem } from "./SecretApprovalRequestChangeItem"; import { SecretApprovalRequestChangeItem } from "./SecretApprovalRequestChangeItem";
import { format } from "date-fns";
export const generateCommitText = (commits: { op: CommitType }[] = []) => { export const generateCommitText = (commits: { op: CommitType }[] = []) => {
const score: Record<string, number> = {}; const score: Record<string, number> = {};
@@ -51,7 +52,7 @@ export const generateCommitText = (commits: { op: CommitType }[] = []) => {
text.push( text.push(
<span key="created-commit"> <span key="created-commit">
{score[CommitType.CREATE]} secret{score[CommitType.CREATE] !== 1 && "s"} {score[CommitType.CREATE]} secret{score[CommitType.CREATE] !== 1 && "s"}
<span style={{ color: "#16a34a" }}> created</span> <span style={{ color: "#60DD00" }}> created</span>
</span> </span>
); );
if (score[CommitType.UPDATE]) if (score[CommitType.UPDATE])
@@ -59,7 +60,7 @@ export const generateCommitText = (commits: { op: CommitType }[] = []) => {
<span key="updated-commit"> <span key="updated-commit">
{Boolean(text.length) && ","} {Boolean(text.length) && ","}
{score[CommitType.UPDATE]} secret{score[CommitType.UPDATE] !== 1 && "s"} {score[CommitType.UPDATE]} secret{score[CommitType.UPDATE] !== 1 && "s"}
<span style={{ color: "#ea580c" }} className="text-orange-600"> <span style={{ color: "#F8EB30" }} className="text-orange-600">
{" "} {" "}
updated updated
</span> </span>
@@ -70,7 +71,7 @@ export const generateCommitText = (commits: { op: CommitType }[] = []) => {
<span className="deleted-commit"> <span className="deleted-commit">
{Boolean(text.length) && "and"} {Boolean(text.length) && "and"}
{score[CommitType.DELETE]} secret{score[CommitType.UPDATE] !== 1 && "s"} {score[CommitType.DELETE]} secret{score[CommitType.UPDATE] !== 1 && "s"}
<span style={{ color: "#b91c1c" }}> deleted</span> <span style={{ color: "#F83030" }}> deleted</span>
</span> </span>
); );
@@ -221,29 +222,29 @@ export const SecretApprovalRequestChanges = ({
</span> </span>
</div> </div>
<div className="flex flex-grow flex-col"> <div className="flex flex-grow flex-col">
<div className="mb-1 text-lg"> <div className="text-lg">
{generateCommitText(secretApprovalRequestDetails.commits)} {generateCommitText(secretApprovalRequestDetails.commits)}
{secretApprovalRequestDetails.isReplicated && ( {secretApprovalRequestDetails.isReplicated && (
<span className="text-sm text-bunker-300"> (replication)</span> <span className="text-sm text-bunker-300"> (replication)</span>
)} )}
</div> </div>
<div className="flex items-center text-sm text-bunker-300"> <div className="text-sm text-bunker-300">
{secretApprovalRequestDetails?.committerUser?.firstName || ""} <p className="inline">
{secretApprovalRequestDetails?.committerUser?.lastName || ""} ( {secretApprovalRequestDetails?.committerUser?.firstName || ""}
{secretApprovalRequestDetails?.committerUser?.email}) wants to change{" "} {secretApprovalRequestDetails?.committerUser?.lastName || ""} (
{secretApprovalRequestDetails.commits.length} secret values in {secretApprovalRequestDetails?.committerUser?.email}) wants to change{" "}
<span className="mx-1 rounded bg-primary-600/60 px-1 text-primary-300"> {secretApprovalRequestDetails.commits.length} secret values in
</p>
<p className="inline rounded bg-primary-600/40 mx-1 px-1 py-1 text-primary-300">
{secretApprovalRequestDetails.environment} {secretApprovalRequestDetails.environment}
</span> </p>
<div className="flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2"> <div className="inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
<div className="border-r border-mineshaft-500 pr-1"> <p className="border-r border-mineshaft-500 pr-1 cursor-default">
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" /> <FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
</div> </p>
<Tooltip content={formatReservedPaths(secretApprovalRequestDetails.secretPath)}> <p className="truncate pb-0.5 pl-2 text-sm cursor-default" style={{ maxWidth: "10rem" }}>
<div className="truncate pb-0.5 pl-2 text-sm" style={{ maxWidth: "10rem" }}> {formatReservedPaths(secretApprovalRequestDetails.secretPath)}
{formatReservedPaths(secretApprovalRequestDetails.secretPath)} </p>
</div>
</Tooltip>
</div> </div>
</div> </div>
</div> </div>
@@ -256,7 +257,7 @@ export const SecretApprovalRequestChanges = ({
> >
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
variant="outline_bg" variant="primary"
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />} rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
> >
Review Review
@@ -264,8 +265,8 @@ export const SecretApprovalRequestChanges = ({
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" asChild className="mt-3"> <DropdownMenuContent align="end" asChild className="mt-3">
<form onSubmit={handleSubmit(handleSubmitReview)}> <form onSubmit={handleSubmit(handleSubmitReview)}>
<div className="flex w-[400px] flex-col space-y-2 p-5"> <div className="flex w-[500px] flex-col space-y-2 p-5">
<div className="text-lg font-medium">Finish your review</div> <div className="text-md font-medium">Finish your review</div>
<Controller <Controller
control={control} control={control}
name="comment" name="comment"
@@ -275,7 +276,7 @@ export const SecretApprovalRequestChanges = ({
{...field} {...field}
placeholder="Leave a comment..." placeholder="Leave a comment..."
reSize="none" reSize="none"
className="text-md mt-2 h-48 border border-mineshaft-600 bg-bunker-800" className="text-md mt-2 h-40 border border-mineshaft-600 bg-bunker-800"
/> />
</FormControl> </FormControl>
)} )}
@@ -320,7 +321,7 @@ export const SecretApprovalRequestChanges = ({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<RadioGroupItem <RadioGroupItem
id="reject" id="reject"
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500" className="h-4 w-4 rounded-full border border-gray-300 text-red focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.REJECTED} value={ApprovalStatus.REJECTED}
aria-labelledby="reject-label" aria-labelledby="reject-label"
> >
@@ -376,14 +377,14 @@ export const SecretApprovalRequestChanges = ({
) )
)} )}
</div> </div>
<div className="mt-4 flex flex-col items-center rounded-lg"> <div className="my-4 flex flex-col items-center rounded-lg border border-mineshaft-600">
{secretApprovalRequestDetails?.policy?.approvers {secretApprovalRequestDetails?.policy?.approvers
.filter((requiredApprover) => reviewedUsers?.[requiredApprover.userId]) .filter((requiredApprover) => reviewedUsers?.[requiredApprover.userId])
.map((requiredApprover) => { .map((requiredApprover) => {
const reviewer = reviewedUsers?.[requiredApprover.userId]; const reviewer = reviewedUsers?.[requiredApprover.userId];
return ( return (
<div <div
className="mb-4 flex w-full flex-col rounded-md bg-mineshaft-800 p-6" className="flex w-full flex-col rounded-md bg-mineshaft-800 p-4"
key={`required-approver-${requiredApprover.userId}`} key={`required-approver-${requiredApprover.userId}`}
> >
<div> <div>
@@ -396,10 +397,10 @@ export const SecretApprovalRequestChanges = ({
> >
{reviewer?.status === ApprovalStatus.APPROVED ? "approved" : "rejected"} {reviewer?.status === ApprovalStatus.APPROVED ? "approved" : "rejected"}
</span>{" "} </span>{" "}
the request. the request on {format(new Date(secretApprovalRequestDetails.createdAt), "PPpp zzz")}.
</div> </div>
{reviewer?.comment && ( {reviewer?.comment && (
<FormControl label="Comment" className="mb-0 mt-2"> <FormControl label="Comment" className="mb-0 mt-4">
<TextArea value={reviewer.comment} isDisabled reSize="none"> <TextArea value={reviewer.comment} isDisabled reSize="none">
{reviewer?.comment && reviewer.comment} {reviewer?.comment && reviewer.comment}
</TextArea> </TextArea>
@@ -409,7 +410,7 @@ export const SecretApprovalRequestChanges = ({
); );
})} })}
</div> </div>
<div className="flex items-center space-x-6 rounded-lg bg-mineshaft-800 px-5 py-6"> <div className="flex items-center space-x-6 mt-2 rounded-lg bg-mineshaft-800 border border-mineshaft-600">
<SecretApprovalRequestAction <SecretApprovalRequestAction
canApprove={canApprove} canApprove={canApprove}
approvalRequestId={secretApprovalRequestDetails.id} approvalRequestId={secretApprovalRequestDetails.id}
@@ -423,7 +424,7 @@ export const SecretApprovalRequestChanges = ({
/> />
</div> </div>
</div> </div>
<div className="sticky top-0 w-1/5 pt-4" style={{ minWidth: "240px" }}> <div className="sticky top-0 w-1/5 pt-4 cursor-default" 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 {secretApprovalRequestDetails?.policy?.approvers
@@ -435,10 +436,10 @@ export const SecretApprovalRequestChanges = ({
const reviewer = reviewedUsers?.[requiredApprover.userId]; const reviewer = reviewedUsers?.[requiredApprover.userId];
return ( return (
<div <div
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1" className="flex justify-between flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 border border-mineshaft-600 px-2 py-1"
key={`required-approver-${requiredApprover.userId}`} key={`required-approver-${requiredApprover.userId}`}
> >
<div className="flex-grow text-sm"> <div className="flex text-sm">
<Tooltip <Tooltip
content={`${requiredApprover.firstName || ""} ${ content={`${requiredApprover.firstName || ""} ${
requiredApprover.lastName || "" requiredApprover.lastName || ""
@@ -458,7 +459,7 @@ export const SecretApprovalRequestChanges = ({
/> />
</Tooltip> </Tooltip>
)} )}
<Tooltip content={reviewer?.status || ApprovalStatus.PENDING}> <Tooltip content={`Status: ${reviewer?.status || ApprovalStatus.PENDING}`}>
{getReviewedStatusSymbol(reviewer?.status)} {getReviewedStatusSymbol(reviewer?.status)}
</Tooltip> </Tooltip>
</div> </div>