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,