From 5c6b7ed95c875d2b25d0e5d11604b5b841ce1337 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 20 Oct 2025 21:28:05 -0300 Subject: [PATCH] Use commit batch logic on SecretDropzone --- .../SecretDashboardPage.tsx | 1 - .../SecretDropzone/SecretDropzone.tsx | 170 ++++++++++-------- 2 files changed, 96 insertions(+), 75 deletions(-) diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index caef19326..f1a7ffba8 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -1109,7 +1109,6 @@ const Page = () => { secretPath={secretPath} isSmaller={isNotEmpty} environments={currentProject?.environments} - isProtectedBranch={isProtectedBranch} /> ; -type TSecOverwriteOpt = { update: TParsedEnv; create: TParsedEnv }; +type TSecOverwriteOpt = { + update: TParsedEnv; + create: TParsedEnv; + existingSecrets: SecretV3RawSanitized[]; +}; type Props = { isSmaller: boolean; @@ -56,7 +61,6 @@ type Props = { projectId: string; environment: string; secretPath: string; - isProtectedBranch?: boolean; }; type SecretMatrixMap = { @@ -142,8 +146,7 @@ export const SecretDropzone = ({ environments = [], projectId, environment, - secretPath, - isProtectedBranch = false + secretPath }: Props): JSX.Element => { const { t } = useTranslation(); const [isDragActive, setDragActive] = useToggle(); @@ -157,18 +160,12 @@ export const SecretDropzone = ({ }); const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp(popupKeys); - const queryClient = useQueryClient(); const { openPopUp } = usePopUpAction(); + const { addPendingChange } = useBatchModeActions(); - const { mutateAsync: updateSecretBatch, isPending: isUpdatingSecrets } = useUpdateSecretBatch({ - options: { onSuccess: undefined } - }); - const { mutateAsync: createSecretBatch, isPending: isCreatingSecrets } = useCreateSecretBatch({ - options: { onSuccess: undefined } - }); // hide copy secrets from board due to import folders feature const shouldRenderCopySecrets = false; - const isSubmitting = isCreatingSecrets || isUpdatingSecrets; + const [isSubmitting, setIsSubmitting] = useToggle(); const handleDrag = (e: DragEvent) => { e.preventDefault(); @@ -193,29 +190,38 @@ export const SecretDropzone = ({ try { setIsLoading.on(); - const { secrets: existingSecrets } = await fetchDashboardProjectSecretsByKeys({ - secretPath, - environment, + const { secrets: rawExistingSecrets } = await fetchProjectSecrets({ projectId, - keys: envSecretKeys + environment, + secretPath, + viewSecretValue: true }); - const secretsGroupedByKey = existingSecrets.reduce>( - (prev, curr) => ({ ...prev, [curr.secretKey]: true }), + const allExistingSecrets = mergePersonalSecrets(rawExistingSecrets); + + const existingSecretsMap = allExistingSecrets.reduce>( + (prev, curr) => ({ ...prev, [curr.key]: curr }), {} ); - const updateSecrets = Object.keys(env) - .filter((secKey) => secretsGroupedByKey[secKey]) - .reduce((prev, curr) => ({ ...prev, [curr]: env[curr] }), {}); + const updateSecrets: TParsedEnv = {}; + const createSecrets: TParsedEnv = {}; + const relevantExistingSecrets: SecretV3RawSanitized[] = []; - const createSecrets = Object.keys(env) - .filter((secKey) => !secretsGroupedByKey[secKey]) - .reduce((prev, curr) => ({ ...prev, [curr]: env[curr] }), {}); + Object.entries(env).forEach(([secretKey, secretData]) => { + const existingSecret = existingSecretsMap[secretKey]; + if (existingSecret) { + updateSecrets[secretKey] = secretData; + relevantExistingSecrets.push(existingSecret); + } else { + createSecrets[secretKey] = secretData; + } + }); handlePopUpOpen("confirmUpload", { update: updateSecrets, - create: createSecrets + create: createSecrets, + existingSecrets: relevantExistingSecrets }); } catch (e) { console.error(e); @@ -329,56 +335,72 @@ export const SecretDropzone = ({ }; const handleSaveSecrets = async () => { - const { update, create } = popUp?.confirmUpload?.data as TSecOverwriteOpt; + const { update, create, existingSecrets } = popUp?.confirmUpload?.data as TSecOverwriteOpt; + try { + setIsSubmitting.on(); + + const context: BatchContext = { + projectId, + environment, + secretPath + }; + + const existingSecretsMap = existingSecrets.reduce>( + (prev, curr) => ({ ...prev, [curr.key]: curr }), + {} + ); + if (Object.keys(create || {}).length) { - await createSecretBatch({ - secretPath, - projectId, - environment, - secrets: Object.entries(create).map(([secretKey, secData]) => ({ - type: SecretType.Shared, - secretComment: secData.comments.join("\n"), + Object.entries(create).forEach(([secretKey, secData]) => { + const createChange: PendingSecretCreate = { + id: secretKey, + timestamp: Date.now(), + resourceType: "secret", + type: PendingAction.Create, + secretKey, secretValue: secData.value, - secretKey - })) + secretComment: secData.comments.join("\n") || undefined, + tags: [], + secretMetadata: [] + }; + addPendingChange(createChange, context); }); } + if (Object.keys(update || {}).length) { - await updateSecretBatch({ - secretPath, - projectId, - environment, - secrets: Object.entries(update).map(([secretKey, secData]) => ({ - type: SecretType.Shared, - secretComment: secData.comments.join("\n"), + Object.entries(update).forEach(([secretKey, secData]) => { + const existingSecret = existingSecretsMap[secretKey]; + + if (!existingSecret) { + console.warn(`Existing secret not found for key: ${secretKey}`); + return; + } + + const updateChange: PendingSecretUpdate = { + id: existingSecret.id, + timestamp: Date.now(), + resourceType: "secret", + type: PendingAction.Update, + secretKey, secretValue: secData.value, - secretKey - })) + secretComment: secData.comments.join("\n") || undefined, + existingSecret, + originalValue: existingSecret.value || "", + originalComment: existingSecret.comment || "", + originalSkipMultilineEncoding: existingSecret.skipMultilineEncoding || false, + originalTags: existingSecret.tags || [], + originalSecretMetadata: existingSecret.secretMetadata || [] + }; + addPendingChange(updateChange, context); }); } - queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretApprovalRequestKeys.count({ projectId }) - }); + handlePopUpClose("confirmUpload"); - createNotification({ - type: "success", - text: isProtectedBranch - ? "Uploaded changes have been sent for review" - : "Successfully uploaded secrets" - }); } catch (err) { console.log(err); - createNotification({ - type: "error", - text: "Failed to upload secrets" - }); + } finally { + setIsSubmitting.off(); } }; @@ -541,7 +563,7 @@ export const SecretDropzone = ({ ? ` and import ${createSecretCount} new one${createSecretCount > 1 ? "s" : ""}` : ""} - ? + ? These will be applied when you commit your changes. )}