Files
supabase/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx
T
Joshen Lim 514b097021 Migrate Database Webhook integrations to new integration sUI (#44277)
## Context

Related to marketplace integrations

Shifts Database Webhooks integration to the new Integrations UI. This
one's a bit different from the previous PRs as this involves a full SQL
installation query instead of only just a database extension. So am
tweaking `Integrations.constants` a little.

For context eventually all the integrations will be pulled remotely from
a database, so am still trying to figure out an optimal data structure,
but requirements will be clearer as we build out the UI

RE installing integrations:
- For now, if the integration has a provided SQL installation command,
that'll take precedence
- Else, if the integration has a provided SQL installation query, we'll
use that on the /query endpoint
- Otherwise, if the integration only requires database extensions,
dashboard will generate the queries to install the extensions
- In the case of the former tho, we won't allow users to choose which
schema to install the extension in too

Just ping me if any clarification's required!

## To test
- [ ] Verify that you can install the database webhooks with the new
integration UI
- [ ] Verify that behaviour is status quo without the new integration UI
2026-03-30 19:39:49 +08:00

121 lines
4.1 KiB
TypeScript

import { useParams } from 'common'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { PropsWithChildren, ReactNode } from 'react'
import { Badge, Card, CardContent, cn, Separator } from 'ui'
import { INTEGRATIONS } from '../Landing/Integrations.constants'
import { BuiltBySection } from './BuildBySection'
import { MarkdownContent } from './MarkdownContent'
import { MissingExtensionAlert } from './MissingExtensionAlert'
export interface IntegrationOverviewTabProps {
actions?: ReactNode
status?: string | ReactNode
alert?: ReactNode
hideRequiredExtensionsSection?: boolean
}
export const IntegrationOverviewTab = ({
actions,
alert,
status,
children,
hideRequiredExtensionsSection = false,
}: PropsWithChildren<IntegrationOverviewTabProps>) => {
const { id } = useParams()
const { data: project } = useSelectedProjectQuery()
const integration = INTEGRATIONS.find((i) => i.id === id)
const { data: extensions } = useDatabaseExtensionsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
if (!integration) {
return <div>Unsupported integration type</div>
}
const dependsOnExtension = (integration.requiredExtensions ?? []).length > 0
const installableExtensions = (extensions ?? []).filter((ext) =>
(integration.requiredExtensions ?? []).includes(ext.name)
)
const hasToInstallExtensions = installableExtensions.some((x) => !x.installed_version)
// The integration requires extensions that are not available to install on the current database image
const hasMissingExtensions =
installableExtensions.length !== integration.requiredExtensions.length
return (
<div className="flex flex-col gap-8 py-10">
<BuiltBySection integration={integration} status={status} />
{!!alert && <div className="px-10 max-w-4xl">{alert}</div>}
<MarkdownContent key={integration.id} integrationId={integration.id} />
<Separator />
{dependsOnExtension && !hideRequiredExtensionsSection && (
<div className="px-4 md:px-10 max-w-4xl flex flex-col gap-y-4">
<h4>Required extensions</h4>
<Card>
<CardContent className="p-0">
<ul className="text-foreground-light text-sm">
{(integration.requiredExtensions ?? []).map((requiredExtension, idx) => {
const extension = (extensions ?? []).find((ext) => ext.name === requiredExtension)
const isInstalled = !!extension?.installed_version
const isLastRow = idx === (integration.requiredExtensions?.length ?? 0) - 1
return (
<li
key={requiredExtension}
className={cn(
'flex items-center justify-between gap-3 py-2 px-3',
!isLastRow ? 'border-b' : ''
)}
>
<code className="text-xs">{requiredExtension}</code>
<div className="shrink-0">
{extension ? (
isInstalled ? (
<Badge>Installed</Badge>
) : (
<MissingExtensionAlert extension={extension} />
)
) : (
<span className="text-foreground-muted">Unavailable</span>
)}
</div>
</li>
)
})}
</ul>
{hasMissingExtensions && (
<div className="py-3 px-4 border-t">{integration.missingExtensionsAlert}</div>
)}
</CardContent>
</Card>
</div>
)}
{!!actions && (
<div
aria-disabled={hasToInstallExtensions}
className={cn(
'px-10 max-w-4xl',
hasToInstallExtensions && 'opacity-25 [&_button]:pointer-events-none'
)}
>
{actions}
</div>
)}
{children}
</div>
)
}