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)