diff --git a/ui/components/findings/send-to-jira-modal.test.tsx b/ui/components/findings/send-to-jira-modal.test.tsx index 56fe82261c..f26353ea79 100644 --- a/ui/components/findings/send-to-jira-modal.test.tsx +++ b/ui/components/findings/send-to-jira-modal.test.tsx @@ -300,6 +300,59 @@ describe("SendToJiraModal", () => { }); }); + it("keeps polling until a slow grouped Finding Group dispatch succeeds", async () => { + // Given + const user = userEvent.setup(); + const onOpenChange = vi.fn(); + sendJiraDispatchMock.mockResolvedValueOnce({ + success: true, + taskId: "group-task", + message: "Group started", + }); + pollJiraDispatchTaskMock + .mockResolvedValueOnce({ + success: false, + error: "Task timeout", + }) + .mockResolvedValueOnce({ + success: true, + message: "Finding successfully sent to Jira!", + }); + render( + , + ); + await waitFor(() => expect(getJiraIntegrationsMock).toHaveBeenCalled()); + await user.click( + screen.getByRole("button", { name: "Select a Jira project" }), + ); + await user.click( + screen.getByRole("button", { name: "Select an issue type" }), + ); + + // When + await user.click(screen.getByRole("button", { name: "Send to Jira" })); + + // Then + await waitFor(() => + expect(pollJiraDispatchTaskMock).toHaveBeenCalledTimes(2), + ); + await waitFor(() => + expect(toastMock).toHaveBeenCalledWith({ + title: "Success!", + description: "Finding successfully sent to Jira!", + }), + ); + }); + it("shows one success toast after mixed Group and Finding batches all succeed", async () => { // Given const user = userEvent.setup(); diff --git a/ui/components/findings/send-to-jira-modal.tsx b/ui/components/findings/send-to-jira-modal.tsx index 5b637f9735..7cab8770ea 100644 --- a/ui/components/findings/send-to-jira-modal.tsx +++ b/ui/components/findings/send-to-jira-modal.tsx @@ -72,6 +72,23 @@ const sendToJiraSchema = z.object({ type SendToJiraFormData = z.infer; +const JIRA_TASK_TIMEOUT_ERROR = "Task timeout"; +const JIRA_TASK_POLL_ROUNDS = 15; + +const pollJiraDispatchTaskUntilDone = async (taskId: string) => { + for (let round = 0; round < JIRA_TASK_POLL_ROUNDS; round++) { + const result = await pollJiraDispatchTask(taskId); + if (result.success || result.error !== JIRA_TASK_TIMEOUT_ERROR) { + return result; + } + } + + return { + success: false, + error: "The Jira dispatch task is taking too long. Try again later.", + } as const; +}; + export const SendToJiraModal = ({ isOpen, onOpenChange, @@ -232,7 +249,7 @@ export const SendToJiraModal = ({ // Poll for task completion and notify once const taskResults = await Promise.all( - taskIds.map((taskId) => pollJiraDispatchTask(taskId)), + taskIds.map((taskId) => pollJiraDispatchTaskUntilDone(taskId)), ); const failedTask = taskResults.find( (taskResult) => !taskResult.success,