Mass-update endpoint

This commit is contained in:
x032205
2025-06-12 01:59:47 -04:00
parent 348f4b9787
commit fec47ef81c
3 changed files with 93 additions and 9 deletions

View File

@@ -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",

View File

@@ -140,6 +140,31 @@ export const useUpdateSecretScanningFinding = () => {
});
};
export const useUpdateMultipleSecretScanningFinding = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (findings: TUpdateSecretScanningFinding[]) => {
const { data } = await apiRequest.patch<TSecretScanningFindingResponse>(
`/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({

View File

@@ -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({