Files
supabase/apps/studio/components/interfaces/Integrations/Landing/useInstalledIntegrations.tsx
Ali WaseemandIvan Vasilov 3fcf980b0a 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>
2026-07-02 08:38:17 +00:00

133 lines
4.0 KiB
TypeScript

import { useMemo } from 'react'
import {
hasMatchingWrapper,
hasRequiredExtensions,
isOAuthInstalled,
isStripeSyncEngineInstalled,
useProjectOAuthIntegrationData,
} from './Landing.utils'
import { useAvailableIntegrations } from './useAvailableIntegrations'
import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query'
import { useSchemasQuery } from '@/data/database/schemas-query'
import { useFDWsQuery } from '@/data/fdw/fdws-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { EMPTY_ARR } from '@/lib/void'
export const useInstalledIntegrations = () => {
const { data: project } = useSelectedProjectQuery()
const {
data: allIntegrations = EMPTY_ARR,
error: availableIntegrationsError,
isPending: isAvailableIntegrationsLoading,
isSuccess: isSuccessAvailableIntegrations,
isError: isErrorAvailableIntegrations,
} = useAvailableIntegrations()
const hasOAuthIntegration = useMemo(() => {
return allIntegrations.some((integration) => integration.type === 'oauth')
}, [allIntegrations])
const {
data: oauthData,
error: oauthDataError,
isError: isErrorOAuthData,
isLoading: isOAuthDataLoading,
isSuccess: isSuccessOAuthData,
} = useProjectOAuthIntegrationData(project?.ref, { enabled: hasOAuthIntegration })
const {
data: wrappers = EMPTY_ARR,
error: fdwError,
isError: isErrorFDWs,
isPending: isFDWLoading,
isSuccess: isSuccessFDWs,
} = useFDWsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const {
data: extensions = EMPTY_ARR,
error: extensionsError,
isError: isErrorExtensions,
isPending: isExtensionsLoading,
isSuccess: isSuccessExtensions,
} = useDatabaseExtensionsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const {
data: schemas = EMPTY_ARR,
error: schemasError,
isError: isErrorSchemas,
isPending: isSchemasLoading,
isSuccess: isSuccessSchemas,
} = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const isHooksEnabled = schemas.some((schema) => schema.name === 'supabase_functions')
const installedIntegrations = useMemo(() => {
return allIntegrations
.filter((integration) => {
if (integration.id === 'webhooks') return isHooksEnabled
if (integration.id === 'data_api') return true
if (integration.id === 'stripe_sync_engine') {
return isStripeSyncEngineInstalled(schemas)
}
if (integration.type === 'wrapper') {
return hasMatchingWrapper({ meta: integration.meta, wrappers })
}
if (integration.type === 'postgres_extension') {
return hasRequiredExtensions({ integration, extensions })
}
if (integration.type === 'oauth') {
return isOAuthInstalled({
integration,
projectData: oauthData,
})
}
return false
})
.sort((a, b) => a.name.localeCompare(b.name))
}, [allIntegrations, wrappers, extensions, schemas, isHooksEnabled, oauthData])
const error =
fdwError ||
extensionsError ||
schemasError ||
availableIntegrationsError ||
(hasOAuthIntegration ? oauthDataError : null)
const isLoading =
isSchemasLoading ||
isFDWLoading ||
isExtensionsLoading ||
isAvailableIntegrationsLoading ||
(hasOAuthIntegration && isOAuthDataLoading)
const isError =
isErrorFDWs ||
isErrorExtensions ||
isErrorSchemas ||
isErrorAvailableIntegrations ||
(hasOAuthIntegration && isErrorOAuthData)
const isSuccess =
isSuccessFDWs &&
isSuccessExtensions &&
isSuccessSchemas &&
isSuccessAvailableIntegrations &&
(!hasOAuthIntegration || isSuccessOAuthData)
return {
// show all integrations at once instead of showing partial results
installedIntegrations: isLoading ? EMPTY_ARR : installedIntegrations,
error,
isError,
isLoading,
isSuccess,
}
}