From fec47ef81c9f2f237e7436d9de439d5c7e1eb7ca Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 12 Jun 2025 01:59:47 -0400 Subject: [PATCH] Mass-update endpoint --- .../secret-scanning-v2-router.ts | 49 +++++++++++++++++++ .../hooks/api/secretScanningV2/mutations.tsx | 25 ++++++++++ .../SecretScanningUpdateFindingModal.tsx | 28 +++++++---- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts b/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts index 70cfd08dc..60704f69b 100644 --- a/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts +++ b/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts @@ -187,6 +187,55 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider) } }); + server.route({ + method: "PATCH", + url: "/findings", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretScanning], + description: "Update one or more Secret Scanning Findings in a batch.", + body: z + .object({ + findingId: z.string().trim().min(1, "Finding ID required").describe(SecretScanningFindings.UPDATE.findingId), + status: z.nativeEnum(SecretScanningFindingStatus).optional().describe(SecretScanningFindings.UPDATE.status), + remarks: z.string().nullish().describe(SecretScanningFindings.UPDATE.remarks) + }) + .array(), + response: { + 200: z.object({ findings: SecretScanningFindingSchema.array() }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { body, permission } = req; + + const updatedFindingPromises = body.map(async (findingUpdatePayload) => { + const { finding, projectId } = await server.services.secretScanningV2.updateSecretScanningFindingById( + findingUpdatePayload, + permission + ); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.SECRET_SCANNING_FINDING_UPDATE, + metadata: findingUpdatePayload + } + }); + + return finding; + }); + + const findings = await Promise.all(updatedFindingPromises); + + return { findings }; + } + }); + server.route({ method: "GET", url: "/configs", diff --git a/frontend/src/hooks/api/secretScanningV2/mutations.tsx b/frontend/src/hooks/api/secretScanningV2/mutations.tsx index 18bbbf236..2ed90911d 100644 --- a/frontend/src/hooks/api/secretScanningV2/mutations.tsx +++ b/frontend/src/hooks/api/secretScanningV2/mutations.tsx @@ -140,6 +140,31 @@ export const useUpdateSecretScanningFinding = () => { }); }; +export const useUpdateMultipleSecretScanningFinding = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (findings: TUpdateSecretScanningFinding[]) => { + const { data } = await apiRequest.patch( + `/api/v2/secret-scanning/findings`, + findings + ); + + return data.finding; + }, + onSuccess: (_, findings) => { + queryClient.invalidateQueries({ + queryKey: secretScanningV2Keys.listFindings(findings[0].projectId) + }); + queryClient.invalidateQueries({ + queryKey: secretScanningV2Keys.findingCount(findings[0].projectId) + }); + queryClient.invalidateQueries({ + queryKey: secretScanningV2Keys.dataSource() + }); + } + }); +}; + export const useUpdateSecretScanningConfig = () => { const queryClient = useQueryClient(); return useMutation({ diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx index 0a91d7355..d7a17572a 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx @@ -17,6 +17,7 @@ import { SECRET_SCANNING_FINDING_STATUS_ICON_MAP } from "@app/helpers/secretScan import { SecretScanningFindingStatus, TSecretScanningFinding, + useUpdateMultipleSecretScanningFinding, useUpdateSecretScanningFinding } from "@app/hooks/api/secretScanningV2"; @@ -40,6 +41,7 @@ type ContentProps = { const Content = ({ findings, onComplete }: ContentProps) => { const updateFinding = useUpdateSecretScanningFinding(); + const updateMultipleFindings = useUpdateMultipleSecretScanningFinding(); const single = findings.length === 1; @@ -52,17 +54,25 @@ const Content = ({ findings, onComplete }: ContentProps) => { }); const onSubmit = async (data: FormType) => { + if (!data.status) return; + try { - for await (const finding of findings) { - // If a status wasn't set, don't update - if (data.status) { - await updateFinding.mutateAsync({ + if (findings.length > 1) { + await updateMultipleFindings.mutateAsync( + findings.map((f) => ({ ...data, - status: data.status, - findingId: finding.id, - projectId: finding.projectId - }); - } + status: data.status!, + findingId: f.id, + projectId: f.projectId + })) + ); + } else { + await updateFinding.mutateAsync({ + ...data, + status: data.status, + findingId: findings[0].id, + projectId: findings[0].projectId + }); } createNotification({