mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 03:45:06 +03:00
Adds a new method of installation detection for partners like Doppler. Doppler creates edge function secrets with specific names (`DOPPLER_CONFIG`, `DOPPLER_ENVIRONMENT`, and `DOPPLER_PROJECT`). This method allows the dashboard to check for the presence of such a secret to show the installation status. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Support for edge-function-secret-based OAuth identification and verification. * Installation checks adapt to the selected installation method and only fetch required credentials when needed. * Integration detection can validate installations via API key prefixes or matching edge-function secret names. * **Chores** * Shared helper utilities and types exported to streamline installation checks and mappings. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45826) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
164 lines
5.5 KiB
TypeScript
164 lines
5.5 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
import { FeatureFlagContext, IS_PLATFORM, useFlag } from 'common'
|
|
import { fullImageUrl } from 'common/marketplace-client'
|
|
import { Boxes } from 'lucide-react'
|
|
import dynamic from 'next/dynamic'
|
|
import Image from 'next/image'
|
|
import { useContext, useMemo } from 'react'
|
|
import { cn } from 'ui'
|
|
|
|
import { INTEGRATIONS, Loading, type IntegrationDefinition } from './Integrations.constants'
|
|
import { marketplaceIntegrationsQueryOptions } from '@/data/marketplace/integrations-query'
|
|
import { useCLIReleaseVersionQuery } from '@/data/misc/cli-release-version-query'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
|
|
/**
|
|
* [Joshen] Returns a combination of
|
|
* - Marketplace integrations retrieved remotely (Only if feature flag enabled)
|
|
* - Existing integrations that are defined within studio
|
|
*/
|
|
export const useAvailableIntegrations = () => {
|
|
const { hasLoaded } = useContext(FeatureFlagContext)
|
|
const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
|
|
const { integrationsWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
|
|
|
|
const { data: cliData } = useCLIReleaseVersionQuery()
|
|
const isCLI = !!cliData?.current
|
|
|
|
const { data, error } = useQuery({
|
|
...marketplaceIntegrationsQueryOptions(),
|
|
enabled: isMarketplaceEnabled,
|
|
})
|
|
const isPending = IS_PLATFORM && (!hasLoaded || (isMarketplaceEnabled && !data && !error))
|
|
const isSuccess = !IS_PLATFORM || (hasLoaded && (!isMarketplaceEnabled || (!!data && !error)))
|
|
const isError = IS_PLATFORM && isMarketplaceEnabled && !!error
|
|
|
|
// [Joshen] Format marketplace integrations into existing ones for now
|
|
// Likely that we might need to change, but can look into separately
|
|
const marketplaceIntegrations: IntegrationDefinition[] = useMemo(
|
|
() =>
|
|
(data ?? [])?.map((integration) => {
|
|
const {
|
|
id: listingId,
|
|
slug,
|
|
categories,
|
|
featured,
|
|
title,
|
|
description,
|
|
documentation_url: docsUrl,
|
|
website_url: siteUrl,
|
|
installation_url: installUrl,
|
|
installation_url_type: installUrlType,
|
|
installation_identification_method: installMethod,
|
|
secret_key_prefix: secretKeyPrefix,
|
|
edge_function_secret_name: edgeFunctionSecretName,
|
|
images,
|
|
content,
|
|
partner_name: authorName,
|
|
listing_logo: listingLogo,
|
|
} = integration
|
|
|
|
const status = undefined
|
|
const author = { name: authorName ?? '', websiteUrl: '' }
|
|
|
|
return {
|
|
id: slug ?? '',
|
|
name: title ?? '',
|
|
status,
|
|
featured: !!featured,
|
|
type: 'oauth' as const, // Currently marketplace only supports oauth apps
|
|
categories: Array.isArray(categories)
|
|
? (categories as Array<{ slug: string }>).map((x) => x.slug)
|
|
: [],
|
|
content,
|
|
files: images?.map((image) => fullImageUrl(image)),
|
|
description,
|
|
docsUrl,
|
|
siteUrl,
|
|
installUrl,
|
|
installUrlType: installUrlType ?? undefined,
|
|
installIdentificationMethod: installMethod ?? undefined,
|
|
secretKeyPrefix: secretKeyPrefix ?? undefined,
|
|
edgeFunctionSecretName: edgeFunctionSecretName ?? undefined,
|
|
listingId: listingId ?? undefined,
|
|
author,
|
|
requiredExtensions: [],
|
|
icon: ({ className, ...props } = {}) => (
|
|
<div className="relative w-full h-full">
|
|
{listingLogo ? (
|
|
<Image
|
|
fill
|
|
src={fullImageUrl(listingLogo)}
|
|
alt=""
|
|
className={cn('p-2', className)}
|
|
{...props}
|
|
/>
|
|
) : (
|
|
<Boxes
|
|
className={cn('inset-0 p-2 text-black w-full h-full', className)}
|
|
{...props}
|
|
/>
|
|
)}
|
|
</div>
|
|
),
|
|
navigation: [
|
|
{
|
|
route: 'overview',
|
|
label: 'Overview',
|
|
},
|
|
],
|
|
navigate: ({ pageId = 'overview' }) => {
|
|
switch (pageId) {
|
|
case 'overview':
|
|
return dynamic(
|
|
() =>
|
|
import('@/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/index').then(
|
|
(mod) => mod.IntegrationOverviewTabV2
|
|
),
|
|
{
|
|
loading: Loading,
|
|
}
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
}
|
|
}),
|
|
[data]
|
|
)
|
|
|
|
// [Joshen] Existing integrations that are defined within studio
|
|
// Available integrations are all integrations that can be installed. If an integration can't be installed (needed
|
|
// extensions are not available on this DB image), the UI will provide a tooltip explaining why.
|
|
const allIntegrations = useMemo(() => {
|
|
return INTEGRATIONS.filter((integration) => {
|
|
if (
|
|
!integrationsWrappers &&
|
|
(integration.type === 'wrapper' || integration.id.endsWith('_wrapper'))
|
|
) {
|
|
return false
|
|
}
|
|
|
|
if (integration.id === 'stripe_sync_engine' && isCLI) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
}, [integrationsWrappers, isCLI])
|
|
|
|
const dataWithMarketplace = useMemo(() => {
|
|
return [...marketplaceIntegrations, ...allIntegrations].sort((a, b) =>
|
|
a.name.localeCompare(b.name)
|
|
)
|
|
}, [marketplaceIntegrations, allIntegrations])
|
|
|
|
return {
|
|
data: dataWithMarketplace,
|
|
error,
|
|
isPending,
|
|
isSuccess,
|
|
isError,
|
|
}
|
|
}
|