diff --git a/apps/studio/components/interfaces/Support/SupportForm.state.ts b/apps/studio/components/interfaces/Support/SupportForm.state.ts index c9895322cbc..3caac18083d 100644 --- a/apps/studio/components/interfaces/Support/SupportForm.state.ts +++ b/apps/studio/components/interfaces/Support/SupportForm.state.ts @@ -32,6 +32,7 @@ export type SupportFormState = | { type: 'error' message: string + code?: number } export type SupportFormActions = @@ -45,7 +46,7 @@ export type SupportFormActions = submittedRequest: SubmittedSupportRequest debugSource?: string } - | { type: 'ERROR'; message: string; debugSource?: string } + | { type: 'ERROR'; message: string; code?: number; debugSource?: string } | { type: 'RETURN_TO_EDITING'; debugSource?: string } export function createInitialSupportFormState(): SupportFormState { @@ -89,6 +90,7 @@ export function supportFormReducer( return { type: 'error', message: action.message, + code: action.code, } } console.warn( diff --git a/apps/studio/components/interfaces/Support/SupportFormPage.tsx b/apps/studio/components/interfaces/Support/SupportFormPage.tsx index 0f675c3f165..8181283f5b7 100644 --- a/apps/studio/components/interfaces/Support/SupportFormPage.tsx +++ b/apps/studio/components/interfaces/Support/SupportFormPage.tsx @@ -82,7 +82,9 @@ function SupportFormPageContent() { useStateTransition(state, 'submitting', 'error', (_, curr) => { toast.error(`Failed to submit support ticket: ${curr.message}`) - Sentry.captureMessage(`Failed to submit Support Form: ${curr.message}`) + if (curr.code !== 429) { + Sentry.captureMessage(`Failed to submit Support Form: ${curr.message}`) + } dispatch({ type: 'RETURN_TO_EDITING' }) }) @@ -258,8 +260,7 @@ function SupportFormBody({ return (
diff --git a/apps/studio/components/interfaces/Support/SupportFormV2.tsx b/apps/studio/components/interfaces/Support/SupportFormV2.tsx index 5b60aa92a1d..17d6ad0e884 100644 --- a/apps/studio/components/interfaces/Support/SupportFormV2.tsx +++ b/apps/studio/components/interfaces/Support/SupportFormV2.tsx @@ -114,6 +114,7 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo dispatch({ type: 'ERROR', message: error.message, + code: error.code, }) }, }) diff --git a/apps/studio/components/interfaces/Support/SupportFormV3.tsx b/apps/studio/components/interfaces/Support/SupportFormV3.tsx index c86f463cc09..553dd433d88 100644 --- a/apps/studio/components/interfaces/Support/SupportFormV3.tsx +++ b/apps/studio/components/interfaces/Support/SupportFormV3.tsx @@ -122,6 +122,7 @@ export const SupportFormV3 = ({ dispatch({ type: 'ERROR', message: error.message, + code: error.code, }) }, }) diff --git a/apps/studio/components/interfaces/Support/SupportSidebarForm.tsx b/apps/studio/components/interfaces/Support/SupportSidebarForm.tsx index 40b0288d6ea..fa910e1db52 100644 --- a/apps/studio/components/interfaces/Support/SupportSidebarForm.tsx +++ b/apps/studio/components/interfaces/Support/SupportSidebarForm.tsx @@ -70,7 +70,9 @@ export function SupportForm({ initialParams }: SupportFormProps) { useStateTransition(state, 'submitting', 'error', (_, curr) => { toast.error(`Failed to submit support ticket: ${curr.message}`) - Sentry.captureMessage(`Failed to submit Support Form: ${curr.message}`) + if (curr.code !== 429) { + Sentry.captureMessage(`Failed to submit Support Form: ${curr.message}`) + } dispatch({ type: 'RETURN_TO_EDITING' }) }) diff --git a/apps/studio/data/feedback/support-ticket-send.ts b/apps/studio/data/feedback/support-ticket-send.ts index 8e14bf50b40..37957dce243 100644 --- a/apps/studio/data/feedback/support-ticket-send.ts +++ b/apps/studio/data/feedback/support-ticket-send.ts @@ -5,7 +5,8 @@ import { toast } from 'sonner' import type { ExtendedSupportCategories } from '@/components/interfaces/Support/Support.constants' import { handleError, post } from '@/data/fetchers' -import type { ResponseError, UseCustomMutationOptions } from '@/types' +import { ResponseError } from '@/types' +import type { UseCustomMutationOptions } from '@/types' export type sendSupportTicketVariables = { subject: string @@ -25,6 +26,8 @@ export type sendSupportTicketVariables = { dashboardStudioVersion?: string } +const RATE_LIMIT_FALLBACK_SECONDS = 60 + export async function sendSupportTicket({ subject, message, @@ -42,7 +45,7 @@ export async function sendSupportTicket({ dashboardLogs, dashboardStudioVersion, }: sendSupportTicketVariables) { - const { data, error } = await post('/platform/feedback/send', { + const { data, error, response } = await post('/platform/feedback/send', { body: { subject, message, @@ -65,6 +68,20 @@ export async function sendSupportTicket({ }) if (error) { + const httpResponse: unknown = response + if (httpResponse instanceof Response && httpResponse.status === 429) { + const resetHeader = + httpResponse.headers.get('Retry-After') ?? httpResponse.headers.get('X-RateLimit-Reset') + const parsedReset = resetHeader ? parseInt(resetHeader, 10) : NaN + const waitSeconds = Number.isFinite(parsedReset) ? parsedReset : RATE_LIMIT_FALLBACK_SECONDS + throw new ResponseError( + `You have submitted too many support requests. Please try again in ${waitSeconds} second${waitSeconds === 1 ? '' : 's'}.`, + 429, + undefined, + waitSeconds + ) + } + handleError(error, { alwaysCapture: true, sentryContext: { diff --git a/apps/studio/data/fetchers.ts b/apps/studio/data/fetchers.ts index 7e71e9dd686..1ce51f26e80 100644 --- a/apps/studio/data/fetchers.ts +++ b/apps/studio/data/fetchers.ts @@ -140,7 +140,8 @@ client.use( body.requestId = request.headers.get('X-Request-Id') - const retryAfterHeader = response.headers.get('Retry-After') + const retryAfterHeader = + response.headers.get('Retry-After') ?? response.headers.get('X-RateLimit-Reset') body.retryAfter = retryAfterHeader ? parseInt(retryAfterHeader) : undefined const requestUrl = new URL(request.url) @@ -296,10 +297,11 @@ async function handleFetchError(response: unknown): Promise { resJson.msg ?? resJson.error ?? `An error has occurred: ${status ?? 'Unknown error'}` - const retryAfter = - response instanceof Response && response.headers.get('Retry-After') - ? parseInt(response.headers.get('Retry-After')!) - : undefined + const retryAfterHeader = + response instanceof Response + ? (response.headers.get('Retry-After') ?? response.headers.get('X-RateLimit-Reset')) + : null + const retryAfter = retryAfterHeader ? parseInt(retryAfterHeader) : undefined let error = new ResponseError(message, status, undefined, retryAfter)