mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix(studio): batch of production Sentry crash fixes (array/null guards) (#47460)
Fixes a batch of production Studio crashes from Sentry (all caught by the global error boundary). Most are missing array/null guards where an endpoint typed as an array — or with a nested array field — returned a non-array body in production; a few are one-off render crashes. Resolves FE-3748. ## Issues fixed | Sentry | Error | Fix | | --- | --- | --- | | [J7R](https://supabase.sentry.io/issues/7492997940/) | Maximum update depth exceeded | Disable RadialBar animation in disk-cooldown countdown | | [JR5](https://supabase.sentry.io/issues/7548484681/) | resourceWarnings.find is not a function | Guard in ResourceExhaustionWarningBanner | | [JCJ](https://supabase.sentry.io/issues/7506024989/) | resourceWarnings.find is not a function | Guard in ProjectLayout + normalize query | | [K1Y](https://supabase.sentry.io/issues/7584792331/) | snippet.name on undefined | Optional-chain SQL editor download filename | | [B3K](https://supabase.sentry.io/issues/7141649636/) | pagination.count on undefined | Guard pagination in projects infinite query | | [JVP](https://supabase.sentry.io/issues/7560437621/) | schemas.some / extensions.find | Coerce pg-meta lists to arrays in useInstalledIntegrations | | [JR2](https://supabase.sentry.io/issues/7548339272/) | extensions.find is not a function | (same fix as JVP) | | [JQR](https://supabase.sentry.io/issues/7547163939/) | lints.filter is not a function | Normalize project lints query | | [JR3](https://supabase.sentry.io/issues/7548433501/) | entitlements.find is not a function | Guard call sites + normalize entitlements query | | [JQS](https://supabase.sentry.io/issues/7547557098/) | selected_addons.find is not a function | Normalize addons query arrays | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved stability across several Studio screens by handling missing or unexpected data more safely. * Downloads now use a fallback name when a snippet name isn’t available. * Project, entitlement, schema, addon, warning, and extension views are less likely to break when data is missing or not in the expected format. * Pagination and countdown visuals now behave more consistently, with reduced chance of runtime errors or animation-related glitches. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
1 parent
5c27710c2b
commit
3fcf980b0a
13 files changed
+30
-16
No files matched your search
@@ -69,7 +69,7 @@ export const useInstalledIntegrations = () => {
|
||||
connectionString: project?.connectionString,
|
||||
})
|
||||
|
||||
const isHooksEnabled = schemas?.some((schema) => schema.name === 'supabase_functions')
|
||||
const isHooksEnabled = schemas.some((schema) => schema.name === 'supabase_functions')
|
||||
|
||||
const installedIntegrations = useMemo(() => {
|
||||
return allIntegrations
|
||||
|
||||
@@ -153,7 +153,7 @@ export const UtilityPanel = ({
|
||||
<DownloadResultsButton
|
||||
variant="text"
|
||||
results={result.rows as any[]}
|
||||
fileName={`Supabase Snippet ${snippet.name}`}
|
||||
fileName={`Supabase Snippet ${snippet?.name ?? 'Results'}`}
|
||||
onDownloadAsCSV={() => track('sql_editor_result_download_csv_clicked')}
|
||||
onCopyAsMarkdown={() => track('sql_editor_result_copy_markdown_clicked')}
|
||||
onCopyAsJSON={() => track('sql_editor_result_copy_json_clicked')}
|
||||
|
||||
@@ -142,7 +142,7 @@ export const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<Projec
|
||||
const { data: resourceWarnings } = useResourceWarningsQuery({
|
||||
slug: selectedOrganization?.slug,
|
||||
})
|
||||
const projectResourceWarnings = resourceWarnings?.find(
|
||||
const projectResourceWarnings = (Array.isArray(resourceWarnings) ? resourceWarnings : []).find(
|
||||
(w) => w.project === selectedProject?.ref
|
||||
)
|
||||
const isComputeNearExhaustion =
|
||||
|
||||
@@ -35,7 +35,7 @@ const CountdownTimerRadial = ({ progress }: CountdownTimerRadialProps) => {
|
||||
className="first:fill-foreground-muted/50 last:fill-background-200"
|
||||
polarRadius={[16, 11]}
|
||||
/>
|
||||
<RadialBar dataKey="timeRemaining" cornerRadius={2} />
|
||||
<RadialBar dataKey="timeRemaining" cornerRadius={2} isAnimationActive={false} />
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
|
||||
@@ -48,7 +48,7 @@ const CountdownTimerRadial = ({ progress }: CountdownTimerRadialProps) => {
|
||||
innerRadius={14}
|
||||
outerRadius={5}
|
||||
>
|
||||
<RadialBar dataKey="hand" cornerRadius={2} isAnimationActive={true} />
|
||||
<RadialBar dataKey="hand" cornerRadius={2} isAnimationActive={false} />
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { databaseExtensionsKeys } from './keys'
|
||||
import { executeSql } from '@/data/sql/execute-sql-mutation'
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
import { PROJECT_STATUS } from '@/lib/constants'
|
||||
import { EMPTY_ARR } from '@/lib/void'
|
||||
import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
export type DatabaseExtension = components['schemas']['PostgresExtension'] & {
|
||||
@@ -26,7 +27,7 @@ export async function getDatabaseExtensions(
|
||||
{ projectRef, connectionString, sql, queryKey: ['database-extensions'] },
|
||||
signal
|
||||
)
|
||||
return result as DatabaseExtension[]
|
||||
return (Array.isArray(result) ? result : EMPTY_ARR) as DatabaseExtension[]
|
||||
}
|
||||
|
||||
export type DatabaseExtensionsData = Awaited<ReturnType<typeof getDatabaseExtensions>>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from 'zod'
|
||||
|
||||
import { databaseKeys } from './keys'
|
||||
import { executeSql } from '@/data/sql/execute-sql-mutation'
|
||||
import { EMPTY_ARR } from '@/lib/void'
|
||||
import { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
export type SchemasVariables = {
|
||||
@@ -32,7 +33,7 @@ export async function getSchemas(
|
||||
signal
|
||||
)
|
||||
|
||||
return result
|
||||
return Array.isArray(result) ? result : EMPTY_ARR
|
||||
}
|
||||
|
||||
export const useSchemasQuery = <TData = SchemasData>(
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { components } from 'api-types'
|
||||
|
||||
import { get, handleError } from '@/data/fetchers'
|
||||
import { organizationKeys } from '@/data/organizations/keys'
|
||||
import { EMPTY_ARR } from '@/lib/void'
|
||||
import { UseCustomQueryOptions } from '@/types'
|
||||
import { ResponseError } from '@/types/base'
|
||||
|
||||
@@ -32,7 +33,10 @@ export async function getEntitlements(
|
||||
})
|
||||
if (error) handleError(error)
|
||||
|
||||
return data
|
||||
return {
|
||||
...data,
|
||||
entitlements: Array.isArray(data?.entitlements) ? data.entitlements : EMPTY_ARR,
|
||||
}
|
||||
}
|
||||
|
||||
export type EntitlementsData = Awaited<ReturnType<typeof getEntitlements>>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { fdwKeys } from './keys'
|
||||
import { executeSql } from '@/data/sql/execute-sql-mutation'
|
||||
import { EMPTY_ARR } from '@/lib/void'
|
||||
import { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
export type FDWColumn = {
|
||||
@@ -43,7 +44,7 @@ export async function getFDWs(
|
||||
signal
|
||||
)
|
||||
|
||||
return result as FDW[]
|
||||
return (Array.isArray(result) ? result : EMPTY_ARR) as FDW[]
|
||||
}
|
||||
|
||||
export type FDWsData = Awaited<ReturnType<typeof getFDWs>>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { lintKeys } from './keys'
|
||||
import { get, handleError } from '@/data/fetchers'
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
import { PROJECT_STATUS } from '@/lib/constants'
|
||||
import { EMPTY_ARR } from '@/lib/void'
|
||||
import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
type ProjectLintsVariables = {
|
||||
@@ -24,7 +25,7 @@ export async function getProjectLints({ projectRef }: ProjectLintsVariables, sig
|
||||
|
||||
if (error) handleError(error)
|
||||
|
||||
return data
|
||||
return Array.isArray(data) ? data : EMPTY_ARR
|
||||
}
|
||||
|
||||
export type ProjectLintsData = Awaited<ReturnType<typeof getProjectLints>>
|
||||
|
||||
@@ -70,9 +70,9 @@ export const useProjectsInfiniteQuery = <TData = ProjectsInfiniteData>(
|
||||
const page = pages.length
|
||||
const currentTotalCount = page * limit
|
||||
// @ts-ignore [Joshen] API type issue for Version 2 endpoints
|
||||
const totalCount = lastPage.pagination.count
|
||||
const totalCount = lastPage?.pagination?.count
|
||||
|
||||
if (currentTotalCount >= totalCount) return undefined
|
||||
if (totalCount === undefined || currentTotalCount >= totalCount) return undefined
|
||||
return page
|
||||
},
|
||||
...options,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { subscriptionKeys } from './keys'
|
||||
import { get, handleError } from '@/data/fetchers'
|
||||
import { IS_PLATFORM } from '@/lib/constants'
|
||||
import { EMPTY_ARR } from '@/lib/void'
|
||||
import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
export type ProjectAddonsVariables = {
|
||||
@@ -24,7 +25,11 @@ export async function getProjectAddons(
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
return {
|
||||
...data,
|
||||
selected_addons: Array.isArray(data?.selected_addons) ? data.selected_addons : EMPTY_ARR,
|
||||
available_addons: Array.isArray(data?.available_addons) ? data.available_addons : EMPTY_ARR,
|
||||
}
|
||||
}
|
||||
|
||||
export type ProjectAddonsData = Awaited<ReturnType<typeof getProjectAddons>>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { IS_PLATFORM } from 'common'
|
||||
import { usageKeys } from './keys'
|
||||
import type { components } from '@/data/api'
|
||||
import { get, handleError } from '@/data/fetchers'
|
||||
import { EMPTY_ARR } from '@/lib/void'
|
||||
import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
export type ResourceWarningsVariables = {
|
||||
@@ -26,7 +27,7 @@ export async function getResourceWarnings(
|
||||
})
|
||||
if (error) handleError(error)
|
||||
|
||||
return data
|
||||
return Array.isArray(data) ? data : EMPTY_ARR
|
||||
}
|
||||
|
||||
export type ResourceWarning = components['schemas']['ProjectResourceWarningsResponse']
|
||||
|
||||
@@ -69,7 +69,7 @@ export function useHasEntitlementAccess(organizationSlug?: string) {
|
||||
return useCallback(
|
||||
(key: string) =>
|
||||
IS_PLATFORM
|
||||
? (entitlementsData?.entitlements.find((e) => e.feature.key === key)?.hasAccess ?? false)
|
||||
? (entitlementsData?.entitlements?.find((e) => e.feature.key === key)?.hasAccess ?? false)
|
||||
: true,
|
||||
[entitlementsData]
|
||||
)
|
||||
@@ -107,7 +107,7 @@ export function useCheckEntitlements(
|
||||
// If no organization slug, no access
|
||||
if (!finalOrgSlug) return { entitlement: null }
|
||||
|
||||
const entitlement = entitlementsData?.entitlements.find(
|
||||
const entitlement = entitlementsData?.entitlements?.find(
|
||||
(entitlement) => entitlement.feature.key === featureKey
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user