mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 06:26:16 +00:00
feat(secret-approval): resolved infinite query bug and added support for closing, re-opening request, stale req ui
This commit is contained in:
@@ -35,7 +35,7 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) =>
|
|||||||
.skip(offset)
|
.skip(offset)
|
||||||
.populate("policy")
|
.populate("policy")
|
||||||
.lean();
|
.lean();
|
||||||
if (!approvalRequests.length) return res.send({ requests: [] });
|
if (!approvalRequests.length) return res.send({ approvals: [] });
|
||||||
|
|
||||||
const unqiueEnvs = environment ?? {
|
const unqiueEnvs = environment ?? {
|
||||||
$in: [...new Set(approvalRequests.map(({ environment }) => environment))]
|
$in: [...new Set(approvalRequests.map(({ environment }) => environment))]
|
||||||
@@ -64,7 +64,7 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
|
|||||||
params: { id }
|
params: { id }
|
||||||
} = await validateRequest(reqValidator.getSecretApprovalRequestDetails, req);
|
} = await validateRequest(reqValidator.getSecretApprovalRequestDetails, req);
|
||||||
const secretApprovalRequest = await SecretApprovalRequest.findById(id)
|
const secretApprovalRequest = await SecretApprovalRequest.findById(id)
|
||||||
.populate("policy")
|
.populate<{ policy: ISecretApprovalPolicy }>("policy")
|
||||||
.populate({
|
.populate({
|
||||||
path: "commits.secretVersion",
|
path: "commits.secretVersion",
|
||||||
populate: {
|
populate: {
|
||||||
@@ -84,7 +84,9 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
|
|||||||
if (
|
if (
|
||||||
membership.role !== "admin" &&
|
membership.role !== "admin" &&
|
||||||
secretApprovalRequest.committer !== membership.id &&
|
secretApprovalRequest.committer !== membership.id &&
|
||||||
secretApprovalRequest.reviewers.find(({ member }) => member === membership.id)
|
!secretApprovalRequest.policy.approvers.find(
|
||||||
|
(approverId) => approverId.toString() === membership._id.toString()
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
throw UnauthorizedRequestError({ message: "User has no access" });
|
throw UnauthorizedRequestError({ message: "User has no access" });
|
||||||
}
|
}
|
||||||
@@ -94,11 +96,11 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateSecretApprovalRequestStatus = async (req: Request, res: Response) => {
|
export const updateSecretApprovalReviewStatus = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
body: { status },
|
body: { status },
|
||||||
params: { id }
|
params: { id }
|
||||||
} = await validateRequest(reqValidator.updateSecretApprovalRequestStatus, req);
|
} = await validateRequest(reqValidator.updateSecretApprovalReviewStatus, req);
|
||||||
const secretApprovalRequest = await SecretApprovalRequest.findById(id).populate<{
|
const secretApprovalRequest = await SecretApprovalRequest.findById(id).populate<{
|
||||||
policy: ISecretApprovalPolicy;
|
policy: ISecretApprovalPolicy;
|
||||||
}>("policy");
|
}>("policy");
|
||||||
@@ -132,7 +134,7 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo
|
|||||||
|
|
||||||
export const mergeSecretApprovalRequest = async (req: Request, res: Response) => {
|
export const mergeSecretApprovalRequest = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
body: { id }
|
params: { id }
|
||||||
} = await validateRequest(reqValidator.mergeSecretApprovalRequest, req);
|
} = await validateRequest(reqValidator.mergeSecretApprovalRequest, req);
|
||||||
|
|
||||||
const secretApprovalRequest = await SecretApprovalRequest.findById(id).populate<{
|
const secretApprovalRequest = await SecretApprovalRequest.findById(id).populate<{
|
||||||
@@ -149,7 +151,7 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) =>
|
|||||||
if (
|
if (
|
||||||
membership.role !== "admin" &&
|
membership.role !== "admin" &&
|
||||||
secretApprovalRequest.committer !== membership.id &&
|
secretApprovalRequest.committer !== membership.id &&
|
||||||
!secretApprovalRequest.policy.approvers.find((approverId) => approverId === membership.id)
|
!secretApprovalRequest.policy.approvers.find((approverId) => approverId.equals(membership.id))
|
||||||
) {
|
) {
|
||||||
throw UnauthorizedRequestError({ message: "User has no access" });
|
throw UnauthorizedRequestError({ message: "User has no access" });
|
||||||
}
|
}
|
||||||
@@ -166,6 +168,51 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) =>
|
|||||||
|
|
||||||
if (!hasMinApproval) throw BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
if (!hasMinApproval) throw BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||||
|
|
||||||
const approval = await performSecretApprovalRequestMerge(id, req.authData);
|
const approval = await performSecretApprovalRequestMerge(
|
||||||
|
id,
|
||||||
|
req.authData,
|
||||||
|
membership._id.toString()
|
||||||
|
);
|
||||||
return res.send({ approval });
|
return res.send({ approval });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateSecretApprovalRequestStatus = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
body: { status },
|
||||||
|
params: { id }
|
||||||
|
} = await validateRequest(reqValidator.updateSecretApprovalRequestStatus, req);
|
||||||
|
|
||||||
|
const secretApprovalRequest = await SecretApprovalRequest.findById(id).populate<{
|
||||||
|
policy: ISecretApprovalPolicy;
|
||||||
|
}>("policy");
|
||||||
|
|
||||||
|
if (!secretApprovalRequest)
|
||||||
|
throw BadRequestError({ message: "Secret approval request not found" });
|
||||||
|
|
||||||
|
const { membership } = await getUserProjectPermissions(
|
||||||
|
req.user._id,
|
||||||
|
secretApprovalRequest.workspace.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
membership.role !== "admin" &&
|
||||||
|
secretApprovalRequest.committer !== membership.id &&
|
||||||
|
!secretApprovalRequest.policy.approvers.find((approverId) => approverId.equals(membership._id))
|
||||||
|
) {
|
||||||
|
throw UnauthorizedRequestError({ message: "User has no access" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secretApprovalRequest.hasMerged)
|
||||||
|
throw BadRequestError({ message: "Approval request has been merged" });
|
||||||
|
if (secretApprovalRequest.status === "close" && status === "close")
|
||||||
|
throw BadRequestError({ message: "Approval request is already closed" });
|
||||||
|
if (secretApprovalRequest.status === "open" && status === "open")
|
||||||
|
throw BadRequestError({ message: "Approval request is already open" });
|
||||||
|
|
||||||
|
const updatedRequest = await SecretApprovalRequest.findByIdAndUpdate(
|
||||||
|
id,
|
||||||
|
{ status, statusChangeBy: membership._id },
|
||||||
|
{ new: true }
|
||||||
|
);
|
||||||
|
return res.send({ approval: updatedRequest });
|
||||||
|
};
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export type ISecretCommits<T = Types.ObjectId, J = Types.ObjectId> = Array<
|
|||||||
export interface ISecretApprovalRequest {
|
export interface ISecretApprovalRequest {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
committer: Types.ObjectId;
|
committer: Types.ObjectId;
|
||||||
|
statusChangeBy: Types.ObjectId;
|
||||||
reviewers: {
|
reviewers: {
|
||||||
member: Types.ObjectId;
|
member: Types.ObjectId;
|
||||||
status: ApprovalStatus;
|
status: ApprovalStatus;
|
||||||
@@ -159,6 +160,7 @@ const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
|
|||||||
hasMerged: { type: Boolean, default: false },
|
hasMerged: { type: Boolean, default: false },
|
||||||
status: { type: String, enum: ["close", "open"], default: "open" },
|
status: { type: String, enum: ["close", "open"], default: "open" },
|
||||||
committer: { type: Schema.Types.ObjectId, ref: "Membership" },
|
committer: { type: Schema.Types.ObjectId, ref: "Membership" },
|
||||||
|
statusChangeBy: { type: Schema.Types.ObjectId, ref: "Membership" },
|
||||||
commits: [
|
commits: [
|
||||||
{
|
{
|
||||||
secret: { type: Types.ObjectId, ref: "Secret" },
|
secret: { type: Types.ObjectId, ref: "Secret" },
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ router.get(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/merge",
|
"/:id/merge",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
@@ -29,7 +29,15 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:id",
|
"/:id/review",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
secretApprovalRequestController.updateSecretApprovalReviewStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/:id/status",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -317,7 +317,11 @@ export const generateSecretApprovalRequest = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// validation for a merge conditions happen in another function in controller
|
// validation for a merge conditions happen in another function in controller
|
||||||
export const performSecretApprovalRequestMerge = async (id: string, authData: AuthData) => {
|
export const performSecretApprovalRequestMerge = async (
|
||||||
|
id: string,
|
||||||
|
authData: AuthData,
|
||||||
|
userMembershipId: string
|
||||||
|
) => {
|
||||||
const secretApprovalRequest = await SecretApprovalRequest.findById(id)
|
const secretApprovalRequest = await SecretApprovalRequest.findById(id)
|
||||||
.populate<{ commits: ISecretCommits<ISecret> }>({
|
.populate<{ commits: ISecretCommits<ISecret> }>({
|
||||||
path: "commits.secret",
|
path: "commits.secret",
|
||||||
@@ -636,7 +640,8 @@ export const performSecretApprovalRequestMerge = async (id: string, authData: Au
|
|||||||
{
|
{
|
||||||
conflicts,
|
conflicts,
|
||||||
hasMerged: true,
|
hasMerged: true,
|
||||||
status: "close"
|
status: "close",
|
||||||
|
statusChangeBy: userMembershipId
|
||||||
},
|
},
|
||||||
{ new: true }
|
{ new: true }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const getSecretApprovalRequestDetails = z.object({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateSecretApprovalRequestStatus = z.object({
|
export const updateSecretApprovalReviewStatus = z.object({
|
||||||
body: z.object({
|
body: z.object({
|
||||||
status: z.enum([ApprovalStatus.APPROVED, ApprovalStatus.REJECTED])
|
status: z.enum([ApprovalStatus.APPROVED, ApprovalStatus.REJECTED])
|
||||||
}),
|
}),
|
||||||
@@ -28,7 +28,16 @@ export const updateSecretApprovalRequestStatus = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const mergeSecretApprovalRequest = z.object({
|
export const mergeSecretApprovalRequest = z.object({
|
||||||
body: z.object({
|
params: z.object({
|
||||||
id: z.string().trim()
|
id: z.string().trim()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const updateSecretApprovalRequestStatus = z.object({
|
||||||
|
params: z.object({
|
||||||
|
id: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
status: z.enum(["open", "close"])
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ type Props = {
|
|||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// refactor(akhilmhdh): both color and size variants are together need to split it
|
||||||
|
// colorSchema should handle all color class names
|
||||||
|
// variant should handle how the button padding and other types should be set
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
[
|
[
|
||||||
"button",
|
"button",
|
||||||
@@ -106,6 +109,12 @@ const buttonVariants = cva(
|
|||||||
variant: "outline",
|
variant: "outline",
|
||||||
className: "text-red hover:bg-red hover:text-black"
|
className: "text-red hover:bg-red hover:text-black"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
colorSchema: "danger",
|
||||||
|
variant: "outline_bg",
|
||||||
|
className:
|
||||||
|
"bg-mineshaft-600 border border-red-500 hover:bg-red/[0.1] hover:border-red/40 text-red-500"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
colorSchema: "primary",
|
colorSchema: "primary",
|
||||||
variant: "plain",
|
variant: "plain",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export {
|
export {
|
||||||
usePerformSecretApprovalRequestMerge,
|
usePerformSecretApprovalRequestMerge,
|
||||||
useUpdateSecretApprovalRequestStatus
|
useUpdateSecretApprovalRequestStatus,
|
||||||
|
useUpdateSecretApprovalReviewStatus
|
||||||
} from "./mutation";
|
} from "./mutation";
|
||||||
export { useGetSecretApprovalRequestDetails, useGetSecretApprovalRequests } from "./queries";
|
export { useGetSecretApprovalRequestDetails, useGetSecretApprovalRequests } from "./queries";
|
||||||
|
|||||||
@@ -3,14 +3,36 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { secretApprovalRequestKeys } from "./queries";
|
import { secretApprovalRequestKeys } from "./queries";
|
||||||
import { TPerformSecretApprovalRequestMerge, TUpdateSecretApprovalRequestStatusDTO } from "./types";
|
import {
|
||||||
|
TPerformSecretApprovalRequestMerge,
|
||||||
|
TUpdateSecretApprovalRequestStatusDTO,
|
||||||
|
TUpdateSecretApprovalReviewStatusDTO
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export const useUpdateSecretApprovalReviewStatus = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, TUpdateSecretApprovalReviewStatusDTO>({
|
||||||
|
mutationFn: async ({ id, status }) => {
|
||||||
|
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}/review`, {
|
||||||
|
status
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { id }) => {
|
||||||
|
queryClient.invalidateQueries(secretApprovalRequestKeys.detail({ id }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useUpdateSecretApprovalRequestStatus = () => {
|
export const useUpdateSecretApprovalRequestStatus = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, TUpdateSecretApprovalRequestStatusDTO>({
|
return useMutation<{}, {}, TUpdateSecretApprovalRequestStatusDTO>({
|
||||||
mutationFn: async ({ id, status }) => {
|
mutationFn: async ({ id, status }) => {
|
||||||
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}`, { status });
|
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}/status`, {
|
||||||
|
status
|
||||||
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { id }) => {
|
onSuccess: (_, { id }) => {
|
||||||
@@ -24,11 +46,12 @@ export const usePerformSecretApprovalRequestMerge = () => {
|
|||||||
|
|
||||||
return useMutation<{}, {}, TPerformSecretApprovalRequestMerge>({
|
return useMutation<{}, {}, TPerformSecretApprovalRequestMerge>({
|
||||||
mutationFn: async ({ id }) => {
|
mutationFn: async ({ id }) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/secret-approval-requests/merge", { id });
|
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}/merge`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { id }) => {
|
onSuccess: (_, { id, workspaceId }) => {
|
||||||
queryClient.invalidateQueries(secretApprovalRequestKeys.detail({ id }));
|
queryClient.invalidateQueries(secretApprovalRequestKeys.detail({ id }));
|
||||||
|
queryClient.invalidateQueries(secretApprovalRequestKeys.list({ workspaceId }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export type TSecretApprovalRequest<
|
|||||||
hasMerged: boolean;
|
hasMerged: boolean;
|
||||||
status: "open" | "close";
|
status: "open" | "close";
|
||||||
policy: TSecretApprovalPolicy;
|
policy: TSecretApprovalPolicy;
|
||||||
|
statusChangeBy: string;
|
||||||
commits: {
|
commits: {
|
||||||
// if there is no secret means it was creation
|
// if there is no secret means it was creation
|
||||||
secret?: { version: number };
|
secret?: { version: number };
|
||||||
@@ -82,11 +83,17 @@ export type TGetSecretApprovalRequestDetails = {
|
|||||||
decryptKey: UserWsKeyPair;
|
decryptKey: UserWsKeyPair;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateSecretApprovalRequestStatusDTO = {
|
export type TUpdateSecretApprovalReviewStatusDTO = {
|
||||||
status: ApprovalStatus;
|
status: ApprovalStatus;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TUpdateSecretApprovalRequestStatusDTO = {
|
||||||
|
status: "open" | "close";
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TPerformSecretApprovalRequestMerge = {
|
export type TPerformSecretApprovalRequestMerge = {
|
||||||
id: string;
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -482,11 +482,11 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
<a>
|
<a>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
isSelected={
|
isSelected={
|
||||||
router.asPath === `/project/${currentWorkspace?._id}/allowlist`
|
router.asPath === `/project/${currentWorkspace?._id}/approval`
|
||||||
}
|
}
|
||||||
icon="system-outline-126-verified"
|
icon="system-outline-126-verified"
|
||||||
>
|
>
|
||||||
Admin Panel
|
Secret Approval
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
+20
-13
@@ -39,7 +39,8 @@ export const SecretApprovalRequest = () => {
|
|||||||
data: secretApprovalRequests,
|
data: secretApprovalRequests,
|
||||||
isFetchingNextPage: isFetchingNextApprovalRequest,
|
isFetchingNextPage: isFetchingNextApprovalRequest,
|
||||||
fetchNextPage: fetchNextApprovalRequest,
|
fetchNextPage: fetchNextApprovalRequest,
|
||||||
hasNextPage: hasNextApprovalPage
|
hasNextPage: hasNextApprovalPage,
|
||||||
|
refetch
|
||||||
} = useGetSecretApprovalRequests({
|
} = useGetSecretApprovalRequests({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
status: statusFilter,
|
status: statusFilter,
|
||||||
@@ -51,9 +52,13 @@ export const SecretApprovalRequest = () => {
|
|||||||
(prev, curr) => ({ ...prev, [curr._id]: curr }),
|
(prev, curr) => ({ ...prev, [curr._id]: curr }),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSecretApprovalScreen = Boolean(selectedApproval);
|
const isSecretApprovalScreen = Boolean(selectedApproval);
|
||||||
|
|
||||||
|
const handleGoBackSecretRequestDetail = () => {
|
||||||
|
setSelectedApproval(null);
|
||||||
|
refetch({ refetchPage: (_page, index) => index === 0 });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence exitBeforeEnter>
|
<AnimatePresence exitBeforeEnter>
|
||||||
{isSecretApprovalScreen ? (
|
{isSecretApprovalScreen ? (
|
||||||
@@ -68,7 +73,7 @@ export const SecretApprovalRequest = () => {
|
|||||||
workspaceId={workspaceId}
|
workspaceId={workspaceId}
|
||||||
members={membersGroupById}
|
members={membersGroupById}
|
||||||
approvalRequestId={selectedApproval?._id || ""}
|
approvalRequestId={selectedApproval?._id || ""}
|
||||||
onGoBack={() => setSelectedApproval(null)}
|
onGoBack={handleGoBackSecretRequestDetail}
|
||||||
committer={membersGroupById?.[selectedApproval?.committer || ""]}
|
committer={membersGroupById?.[selectedApproval?.committer || ""]}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -192,16 +197,18 @@ export const SecretApprovalRequest = () => {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{hasNextApprovalPage && (
|
||||||
className="mt-4 text-sm"
|
<Button
|
||||||
isFullWidth
|
className="mt-4 text-sm"
|
||||||
variant="star"
|
isFullWidth
|
||||||
isLoading={isFetchingNextApprovalRequest}
|
variant="star"
|
||||||
isDisabled={isFetchingNextApprovalRequest || !hasNextApprovalPage}
|
isLoading={isFetchingNextApprovalRequest}
|
||||||
onClick={() => fetchNextApprovalRequest()}
|
isDisabled={isFetchingNextApprovalRequest || !hasNextApprovalPage}
|
||||||
>
|
onClick={() => fetchNextApprovalRequest()}
|
||||||
{hasNextApprovalPage ? "Load More" : "End of history"}
|
>
|
||||||
</Button>
|
{hasNextApprovalPage ? "Load More" : "End of history"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
+140
-27
@@ -1,47 +1,160 @@
|
|||||||
import { faCheck, faClose } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faCheck,
|
||||||
|
faClose,
|
||||||
|
faLockOpen,
|
||||||
|
faSquareCheck,
|
||||||
|
faSquareXmark,
|
||||||
|
faUserLock
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { Button } from "@app/components/v2";
|
import { Button } from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
usePerformSecretApprovalRequestMerge,
|
||||||
|
useUpdateSecretApprovalRequestStatus
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
approvalRequestId: string;
|
||||||
hasMerged?: boolean;
|
hasMerged?: boolean;
|
||||||
status: "close" | "open";
|
|
||||||
isMergable?: boolean;
|
isMergable?: boolean;
|
||||||
isMerging?: boolean;
|
status: "close" | "open";
|
||||||
onMerge: () => void;
|
approvals: number;
|
||||||
onClose?: () => void;
|
statusChangeByEmail: string;
|
||||||
|
workspaceId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretApprovalRequestAction = ({
|
export const SecretApprovalRequestAction = ({
|
||||||
|
approvalRequestId,
|
||||||
hasMerged,
|
hasMerged,
|
||||||
status,
|
status,
|
||||||
isMergable,
|
isMergable,
|
||||||
onMerge,
|
approvals,
|
||||||
isMerging,
|
statusChangeByEmail,
|
||||||
onClose
|
workspaceId
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
const { mutateAsync: performSecretApprovalMerge, isLoading: isMerging } =
|
||||||
|
usePerformSecretApprovalRequestMerge();
|
||||||
|
|
||||||
|
const { mutateAsync: updateSecretStatusChange, isLoading: isStatusChanging } =
|
||||||
|
useUpdateSecretApprovalRequestStatus();
|
||||||
|
|
||||||
|
const handleSecretApprovalRequestMerge = async () => {
|
||||||
|
try {
|
||||||
|
await performSecretApprovalMerge({
|
||||||
|
id: approvalRequestId,
|
||||||
|
workspaceId
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully merged the request"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update the request status"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSecretApprovalStatusChange = async (reqState: "open" | "close") => {
|
||||||
|
try {
|
||||||
|
await updateSecretStatusChange({
|
||||||
|
id: approvalRequestId,
|
||||||
|
status: reqState
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully updated the request"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update the request status"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!hasMerged && status === "open") {
|
if (!hasMerged && status === "open") {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex justify-between items-center w-full">
|
||||||
<Button
|
<div className="flex space-x-4 items-start">
|
||||||
leftIcon={<FontAwesomeIcon icon={faCheck} />}
|
<FontAwesomeIcon
|
||||||
isDisabled={!isMergable}
|
icon={isMergable ? faSquareCheck : faSquareXmark}
|
||||||
isLoading={isMerging}
|
className={twMerge("text-2xl pt-1", isMergable ? "text-primary" : "text-red-600")}
|
||||||
onClick={onMerge}
|
/>
|
||||||
>
|
<span className="flex flex-col">
|
||||||
Merge
|
{isMergable ? "Good to merge" : "Review required"}
|
||||||
</Button>
|
<span className="inline-block text-xs text-bunker-200">
|
||||||
<Button
|
At least {approvals} approving review required
|
||||||
onClick={onClose}
|
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
|
||||||
variant="outline_bg"
|
</span>
|
||||||
leftIcon={<FontAwesomeIcon icon={faClose} />}
|
</span>
|
||||||
>
|
</div>
|
||||||
Close request
|
<div className="flex items-center space-x-6">
|
||||||
</Button>
|
<Button
|
||||||
</>
|
leftIcon={<FontAwesomeIcon icon={faCheck} />}
|
||||||
|
isDisabled={!isMergable}
|
||||||
|
isLoading={isMerging}
|
||||||
|
onClick={handleSecretApprovalRequestMerge}
|
||||||
|
>
|
||||||
|
Merge
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => handleSecretApprovalStatusChange("close")}
|
||||||
|
isLoading={isStatusChanging}
|
||||||
|
variant="outline_bg"
|
||||||
|
colorSchema="danger"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faClose} />}
|
||||||
|
>
|
||||||
|
Close request
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasMerged && status === "close") return <span>This approval request has been merged</span>;
|
if (hasMerged && status === "close")
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between items-center w-full">
|
||||||
|
<div className="flex space-x-4 items-start">
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-2xl text-primary pt-1" />
|
||||||
|
<span className="flex flex-col">
|
||||||
|
Change request merged
|
||||||
|
<span className="inline-block text-xs text-bunker-200">
|
||||||
|
Merged by {statusChangeByEmail}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return <span>This approval request has been closed</span>;
|
return (
|
||||||
|
<div className="flex justify-between items-center w-full">
|
||||||
|
<div className="flex space-x-4 items-start">
|
||||||
|
<FontAwesomeIcon icon={faUserLock} className="text-2xl text-primary pt-1" />
|
||||||
|
<span className="flex flex-col">
|
||||||
|
Change request has been closed
|
||||||
|
<span className="inline-block text-xs text-bunker-200">
|
||||||
|
Closed by {statusChangeByEmail}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-6">
|
||||||
|
<Button
|
||||||
|
onClick={() => handleSecretApprovalStatusChange("open")}
|
||||||
|
isLoading={isStatusChanging}
|
||||||
|
variant="outline_bg"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faLockOpen} />}
|
||||||
|
>
|
||||||
|
Reopen request
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
import { faFilePen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import {
|
||||||
|
SecretInput,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
Tag,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { CommitType, DecryptedSecret, TSecretApprovalSecChange, WsTag } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
op: CommitType;
|
||||||
|
secretVersion?: DecryptedSecret;
|
||||||
|
newVersion?: Omit<TSecretApprovalSecChange, "tags"> & { tags?: WsTag[] };
|
||||||
|
presentSecretVersionNumber: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateItemTitle = (op: CommitType) => {
|
||||||
|
let text = { label: "", color: "" };
|
||||||
|
if (op === CommitType.CREATE) text = { label: "create", color: "#16a34a" };
|
||||||
|
else if (op === CommitType.UPDATE) text = { label: "change", color: "#ea580c" };
|
||||||
|
else text = { label: "deletion", color: "#b91c1c" };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
Request for <span style={{ color: text.color }}>secret {text.label}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SecretApprovalRequestChangeItem = ({
|
||||||
|
op,
|
||||||
|
secretVersion,
|
||||||
|
newVersion,
|
||||||
|
presentSecretVersionNumber
|
||||||
|
}: Props) => {
|
||||||
|
// meaning request has changed
|
||||||
|
const isStale = (secretVersion?.version || 1) < presentSecretVersionNumber;
|
||||||
|
return (
|
||||||
|
<div className="bg-bunker-500 rounded-lg pt-2 pb-4 px-4">
|
||||||
|
<div className="py-3 px-1 flex items-center">
|
||||||
|
<div className="flex-grow">{generateItemTitle(op)}</div>
|
||||||
|
{isStale && (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<FontAwesomeIcon icon={faFilePen} className="text-primary-600 text-sm" />
|
||||||
|
<span className="text-xs ml-2">Secret has been changed(stale)</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
{op === CommitType.UPDATE && <Th className="w-12" />}
|
||||||
|
<Th className="min-table-row">Secret</Th>
|
||||||
|
<Th>Value</Th>
|
||||||
|
<Th className="min-table-row">Comment</Th>
|
||||||
|
<Th className="min-table-row">Tags</Th>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
{op === CommitType.UPDATE ? (
|
||||||
|
<TBody>
|
||||||
|
<Tr>
|
||||||
|
<Td className="text-red-600">OLD</Td>
|
||||||
|
<Td>{secretVersion?.key}</Td>
|
||||||
|
<Td>
|
||||||
|
<SecretInput isReadOnly value={secretVersion?.value} />
|
||||||
|
</Td>
|
||||||
|
<Td>{secretVersion?.comment}</Td>
|
||||||
|
<Td>
|
||||||
|
{secretVersion?.tags?.map(({ name, _id: tagId, tagColor }) => (
|
||||||
|
<Tag
|
||||||
|
className="flex items-center space-x-2 w-min"
|
||||||
|
key={`${secretVersion._id}-${tagId}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
||||||
|
/>
|
||||||
|
<div className="text-sm">{name}</div>
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Td className="text-green-600">NEW</Td>
|
||||||
|
<Td>{newVersion?.secretKey}</Td>
|
||||||
|
<Td>
|
||||||
|
<SecretInput isReadOnly value={newVersion?.secretValue} />
|
||||||
|
</Td>
|
||||||
|
<Td>{newVersion?.secretComment}</Td>
|
||||||
|
<Td>
|
||||||
|
{newVersion?.tags?.map(({ name, _id: tagId, tagColor }) => (
|
||||||
|
<Tag
|
||||||
|
className="flex items-center space-x-2 w-min"
|
||||||
|
key={`${newVersion._id}-${tagId}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
||||||
|
/>
|
||||||
|
<div className="text-sm">{name}</div>
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
</TBody>
|
||||||
|
) : (
|
||||||
|
<TBody>
|
||||||
|
<Tr>
|
||||||
|
<Td>{op === CommitType.CREATE ? newVersion?.secretKey : secretVersion?.key}</Td>
|
||||||
|
<Td>
|
||||||
|
<SecretInput
|
||||||
|
isReadOnly
|
||||||
|
value={
|
||||||
|
op === CommitType.CREATE ? newVersion?.secretValue : secretVersion?.value
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{op === CommitType.CREATE ? newVersion?.secretComment : secretVersion?.comment}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{(op === CommitType.CREATE ? newVersion?.tags : secretVersion?.tags)?.map(
|
||||||
|
({ name, _id: tagId, tagColor }) => (
|
||||||
|
<Tag
|
||||||
|
className="flex items-center space-x-2 w-min"
|
||||||
|
key={`${
|
||||||
|
op === CommitType.CREATE ? newVersion?._id : secretVersion?._id
|
||||||
|
}-${tagId}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
||||||
|
/>
|
||||||
|
<div className="text-sm">{name}</div>
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
</TBody>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+52
-169
@@ -9,33 +9,18 @@ import {
|
|||||||
} 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 {
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
Button,
|
import { Button, ContentLoader, EmptyState, IconButton, Tooltip } from "@app/components/v2";
|
||||||
ContentLoader,
|
|
||||||
IconButton,
|
|
||||||
SecretInput,
|
|
||||||
Table,
|
|
||||||
TableContainer,
|
|
||||||
Tag,
|
|
||||||
TBody,
|
|
||||||
Td,
|
|
||||||
Th,
|
|
||||||
THead,
|
|
||||||
Tooltip,
|
|
||||||
Tr
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useUser } from "@app/context";
|
import { useUser } from "@app/context";
|
||||||
import {
|
import {
|
||||||
useGetSecretApprovalRequestDetails,
|
useGetSecretApprovalRequestDetails,
|
||||||
useGetUserWsKey,
|
useGetUserWsKey,
|
||||||
usePerformSecretApprovalRequestMerge,
|
useUpdateSecretApprovalReviewStatus
|
||||||
useUpdateSecretApprovalRequestStatus
|
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { ApprovalStatus, CommitType, TWorkspaceUser } from "@app/hooks/api/types";
|
import { ApprovalStatus, CommitType, TWorkspaceUser } from "@app/hooks/api/types";
|
||||||
|
|
||||||
import { useNotificationContext } from "~/components/context/Notifications/NotificationProvider";
|
|
||||||
|
|
||||||
import { SecretApprovalRequestAction } from "./SecretApprovalRequestAction";
|
import { SecretApprovalRequestAction } from "./SecretApprovalRequestAction";
|
||||||
|
import { SecretApprovalRequestChangeItem } from "./SecretApprovalRequestChangeItem";
|
||||||
|
|
||||||
export const generateCommitText = (commits: { op: CommitType }[] = []) => {
|
export const generateCommitText = (commits: { op: CommitType }[] = []) => {
|
||||||
const score: Record<string, number> = {};
|
const score: Record<string, number> = {};
|
||||||
@@ -112,9 +97,7 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
mutateAsync: updateSecretApprovalRequestStatus,
|
mutateAsync: updateSecretApprovalRequestStatus,
|
||||||
isLoading: isUpdatingRequestStatus,
|
isLoading: isUpdatingRequestStatus,
|
||||||
variables
|
variables
|
||||||
} = useUpdateSecretApprovalRequestStatus();
|
} = useUpdateSecretApprovalReviewStatus();
|
||||||
const { mutateAsync: performSecretApprovalMerge, isLoading: isMerging } =
|
|
||||||
usePerformSecretApprovalRequestMerge();
|
|
||||||
|
|
||||||
const isApproving = variables?.status === ApprovalStatus.APPROVED && isUpdatingRequestStatus;
|
const isApproving = variables?.status === ApprovalStatus.APPROVED && isUpdatingRequestStatus;
|
||||||
const isRejecting = variables?.status === ApprovalStatus.REJECTED && isUpdatingRequestStatus;
|
const isRejecting = variables?.status === ApprovalStatus.REJECTED && isUpdatingRequestStatus;
|
||||||
@@ -155,31 +138,18 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSecretApprovalRequestMerge = async () => {
|
|
||||||
try {
|
|
||||||
await performSecretApprovalMerge({
|
|
||||||
id: approvalRequestId
|
|
||||||
});
|
|
||||||
createNotification({
|
|
||||||
type: "success",
|
|
||||||
text: "Successfully merged the request"
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Failed to update the request status"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isSecretApprovalRequestLoading) {
|
if (isSecretApprovalRequestLoading) {
|
||||||
<div>
|
<div>
|
||||||
<ContentLoader />
|
<ContentLoader />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSecretApprovalRequestSuccess) return <div>Failed</div>;
|
if (!isSecretApprovalRequestSuccess)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<EmptyState title="Failed to load approvals" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const isMergable =
|
const isMergable =
|
||||||
secretApprovalRequestDetails?.policy?.approvals <=
|
secretApprovalRequestDetails?.policy?.approvals <=
|
||||||
@@ -212,141 +182,54 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{!hasMerged && secretApprovalRequestDetails.status === "open" && (
|
||||||
size="xs"
|
<>
|
||||||
leftIcon={hasApproved && <FontAwesomeIcon icon={faCheck} />}
|
<Button
|
||||||
onClick={() => handleSecretApprovalStatusUpdate(ApprovalStatus.APPROVED)}
|
size="xs"
|
||||||
isLoading={isApproving}
|
leftIcon={hasApproved && <FontAwesomeIcon icon={faCheck} />}
|
||||||
isDisabled={isApproving || hasApproved}
|
onClick={() => handleSecretApprovalStatusUpdate(ApprovalStatus.APPROVED)}
|
||||||
>
|
isLoading={isApproving}
|
||||||
{hasApproved ? "Approved" : "Approve"}
|
isDisabled={isApproving || hasApproved}
|
||||||
</Button>
|
>
|
||||||
<Button
|
{hasApproved ? "Approved" : "Approve"}
|
||||||
size="xs"
|
</Button>
|
||||||
colorSchema="danger"
|
<Button
|
||||||
leftIcon={hasRejected && <FontAwesomeIcon icon={faCheck} />}
|
size="xs"
|
||||||
onClick={() => handleSecretApprovalStatusUpdate(ApprovalStatus.REJECTED)}
|
colorSchema="danger"
|
||||||
isLoading={isRejecting}
|
leftIcon={hasRejected && <FontAwesomeIcon icon={faCheck} />}
|
||||||
isDisabled={isRejecting || hasRejected}
|
onClick={() => handleSecretApprovalStatusUpdate(ApprovalStatus.REJECTED)}
|
||||||
>
|
isLoading={isRejecting}
|
||||||
{hasRejected ? "Rejected" : "Reject"}
|
isDisabled={isRejecting || hasRejected}
|
||||||
</Button>
|
>
|
||||||
|
{hasRejected ? "Rejected" : "Reject"}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
{secretApprovalRequestDetails.commits.map(({ op, secretVersion, newVersion }, index) => (
|
{secretApprovalRequestDetails.commits.map(
|
||||||
<div key={`commit-change-secret-${index + 1}`}>
|
({ op, secretVersion, secret, newVersion }, index) => (
|
||||||
<TableContainer>
|
<SecretApprovalRequestChangeItem
|
||||||
<Table>
|
op={op}
|
||||||
<THead>
|
secretVersion={secretVersion}
|
||||||
<Tr>
|
presentSecretVersionNumber={secret?.version || 0}
|
||||||
{op === CommitType.UPDATE && <Th className="w-12" />}
|
newVersion={newVersion}
|
||||||
<Th className="min-table-row">Secret</Th>
|
key={`${op}-${index + 1}-${secretVersion?._id}`}
|
||||||
<Th>Value</Th>
|
/>
|
||||||
<Th className="min-table-row">Comment</Th>
|
)
|
||||||
<Th className="min-table-row">Tags</Th>
|
)}
|
||||||
</Tr>
|
|
||||||
</THead>
|
|
||||||
{op === CommitType.UPDATE ? (
|
|
||||||
<TBody>
|
|
||||||
<Tr>
|
|
||||||
<Td className="text-red-600">OLD</Td>
|
|
||||||
<Td>{secretVersion?.key}</Td>
|
|
||||||
<Td>
|
|
||||||
<SecretInput isReadOnly value={secretVersion?.value} />
|
|
||||||
</Td>
|
|
||||||
<Td>{secretVersion?.comment}</Td>
|
|
||||||
<Td>
|
|
||||||
{secretVersion?.tags?.map(({ name, _id: tagId, tagColor }) => (
|
|
||||||
<Tag
|
|
||||||
className="flex items-center space-x-2 w-min"
|
|
||||||
key={`${secretVersion._id}-${tagId}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="w-3 h-3 rounded-full"
|
|
||||||
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
|
||||||
/>
|
|
||||||
<div className="text-sm">{name}</div>
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Td className="text-green-600">NEW</Td>
|
|
||||||
<Td>{newVersion?.secretKey}</Td>
|
|
||||||
<Td>
|
|
||||||
<SecretInput isReadOnly value={newVersion?.secretValue} />
|
|
||||||
</Td>
|
|
||||||
<Td>{newVersion?.secretComment}</Td>
|
|
||||||
<Td>
|
|
||||||
{newVersion?.tags?.map(({ name, _id: tagId, tagColor }) => (
|
|
||||||
<Tag
|
|
||||||
className="flex items-center space-x-2 w-min"
|
|
||||||
key={`${newVersion._id}-${tagId}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="w-3 h-3 rounded-full"
|
|
||||||
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
|
||||||
/>
|
|
||||||
<div className="text-sm">{name}</div>
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
</TBody>
|
|
||||||
) : (
|
|
||||||
<TBody>
|
|
||||||
<Tr>
|
|
||||||
<Td>
|
|
||||||
{op === CommitType.CREATE ? newVersion?.secretKey : secretVersion?.key}
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<SecretInput
|
|
||||||
isReadOnly
|
|
||||||
value={
|
|
||||||
op === CommitType.CREATE
|
|
||||||
? newVersion?.secretValue
|
|
||||||
: secretVersion?.value
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
{op === CommitType.CREATE
|
|
||||||
? newVersion?.secretComment
|
|
||||||
: secretVersion?.comment}
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
{(op === CommitType.CREATE ? newVersion?.tags : secretVersion?.tags)?.map(
|
|
||||||
({ name, _id: tagId, tagColor }) => (
|
|
||||||
<Tag
|
|
||||||
className="flex items-center space-x-2 w-min"
|
|
||||||
key={`${
|
|
||||||
op === CommitType.CREATE ? newVersion?._id : secretVersion?._id
|
|
||||||
}-${tagId}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="w-3 h-3 rounded-full"
|
|
||||||
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
|
||||||
/>
|
|
||||||
<div className="text-sm">{name}</div>
|
|
||||||
</Tag>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
</TBody>
|
|
||||||
)}
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center px-4 py-6 rounded-lg space-x-6 bg-mineshaft-800 mt-8">
|
<div className="flex items-center px-5 py-6 rounded-lg space-x-6 bg-mineshaft-800 mt-8">
|
||||||
<SecretApprovalRequestAction
|
<SecretApprovalRequestAction
|
||||||
|
approvalRequestId={secretApprovalRequestDetails._id}
|
||||||
hasMerged={hasMerged}
|
hasMerged={hasMerged}
|
||||||
|
approvals={secretApprovalRequestDetails.policy.approvals || 0}
|
||||||
status={secretApprovalRequestDetails.status}
|
status={secretApprovalRequestDetails.status}
|
||||||
isMerging={isMerging}
|
|
||||||
isMergable={isMergable}
|
isMergable={isMergable}
|
||||||
onMerge={handleSecretApprovalRequestMerge}
|
statusChangeByEmail={
|
||||||
|
members[secretApprovalRequestDetails?.statusChangeBy || ""]?.user?.email || ""
|
||||||
|
}
|
||||||
|
workspaceId={workspaceId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -183,7 +183,6 @@ export const SecretListView = ({
|
|||||||
modSecret: Omit<DecryptedSecret, "tags"> & { tags: { _id: string }[] },
|
modSecret: Omit<DecryptedSecret, "tags"> & { tags: { _id: string }[] },
|
||||||
cb?: () => void
|
cb?: () => void
|
||||||
) => {
|
) => {
|
||||||
console.log(orgSecret, modSecret);
|
|
||||||
const { key: oldKey } = orgSecret;
|
const { key: oldKey } = orgSecret;
|
||||||
const { key, value, overrideAction, idOverride, valueOverride, tags, comment } = modSecret;
|
const { key, value, overrideAction, idOverride, valueOverride, tags, comment } = modSecret;
|
||||||
const hasKeyChanged = oldKey !== key;
|
const hasKeyChanged = oldKey !== key;
|
||||||
@@ -219,7 +218,7 @@ export const SecretListView = ({
|
|||||||
newKey: hasKeyChanged ? key : undefined,
|
newKey: hasKeyChanged ? key : undefined,
|
||||||
skipMultilineEncoding: modSecret.skipMultilineEncoding
|
skipMultilineEncoding: modSecret.skipMultilineEncoding
|
||||||
});
|
});
|
||||||
if (isProtectedBranch) cb?.();
|
if (isProtectedBranch) cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
queryClient.invalidateQueries(
|
queryClient.invalidateQueries(
|
||||||
|
|||||||
Reference in New Issue
Block a user