Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Book } from 'lucide-react'
import Link from 'next/link'
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react'
import { ComponentPropsWithoutRef, ElementRef, forwardRef, ReactNode } from 'react'

import { cn } from 'ui'
import { IntegrationDefinition } from '../Landing/Integrations.constants'

interface BuiltBySectionProps extends ComponentPropsWithoutRef<'div'> {
integration: IntegrationDefinition
status?: string | ReactNode
}

export const BuiltBySection = forwardRef<ElementRef<'div'>, BuiltBySectionProps>(
({ integration, className, ...props }, ref) => {
({ integration, status, className, ...props }, ref) => {
const { docsUrl } = integration
const { name, websiteUrl } = integration?.author ?? {}

Expand All @@ -22,6 +23,12 @@ export const BuiltBySection = forwardRef<ElementRef<'div'>, BuiltBySectionProps>
className={cn('flex flex-wrap items-center gap-8 md:gap-10 px-4 md:px-10', className)}
{...props}
>
{status && (
<div>
<div className="text-foreground-lighter font-mono text-xs mb-1">STATUS</div>
<div>{status}</div>
</div>
)}
{name && (
<div>
<div className="text-foreground-lighter font-mono text-xs mb-1">BUILT BY</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import { useRouter } from 'next/router'
import { PropsWithChildren, ReactNode } from 'react'

import { useParams } from 'common'
import { Markdown } from 'components/interfaces/Markdown'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Alert_Shadcn_, AlertDescription_Shadcn_, Badge, Separator } from 'ui'
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'

interface IntegrationOverviewTabProps {
actions?: ReactNode
status?: string | ReactNode
alert?: ReactNode
}

export const IntegrationOverviewTab = ({
actions,
alert,
status,
children,
}: PropsWithChildren<IntegrationOverviewTabProps>) => {
const { id } = useParams()
const router = useRouter()
const { data: project } = useSelectedProjectQuery()

const integration = INTEGRATIONS.find((i) => i.id === id)
Expand All @@ -47,43 +48,69 @@ export const IntegrationOverviewTab = ({

return (
<div className="flex flex-col gap-8 py-10">
<BuiltBySection integration={integration} />
<BuiltBySection integration={integration} status={status} />
{alert && <div className="px-10 max-w-4xl">{alert}</div>}
<MarkdownContent key={integration.id} integrationId={integration.id} />
<Separator />
{dependsOnExtension && (
<div className="px-4 md:px-10 max-w-4xl">
<Alert_Shadcn_ variant="default" className="bg-surface-200/25 border border-default">
<AlertDescription_Shadcn_ className="flex flex-col gap-y-2">
<Badge className="bg-surface-300 bg-opacity-100 flex items-center gap-x-2 w-max">
<img
alt="Supabase"
src={`${router.basePath}/img/supabase-logo.svg`}
className=" h-2.5 cursor-pointer rounded"
/>
<span>Postgres Module</span>
</Badge>
<Markdown
className="max-w-full"
content={`This integration uses the ${integration.requiredExtensions.map((x) => `\`${x}\``).join(', ')}
extension${integration.requiredExtensions.length > 1 ? 's' : ''} directly in your Postgres database.
${hasToInstallExtensions && !hasMissingExtensions ? `Install ${integration.requiredExtensions.length > 1 ? 'these' : 'this'} database extension${integration.requiredExtensions.length > 1 ? 's' : ''} to use ${integration.name} in your project.` : ''}
`}
/>
<h3 className="heading-default mb-4">Required extensions</h3>
<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={[
'flex items-center justify-between gap-3 py-2 px-3',
!isLastRow ? 'border-b' : '',
].join(' ')}
>
<div className="flex items-center gap-2 min-w-0">
<span className="truncate">
<code>{requiredExtension}</code>
</span>
</div>

<div className="shrink-0">
{extension ? (
isInstalled ? (
<Badge>Installed</Badge>
) : (
<MissingExtensionAlert extension={extension} />
)
) : (
<span className="text-foreground-muted">Unavailable</span>
)}
</div>
</li>
)
})}
</ul>

{hasMissingExtensions ? (
integration.missingExtensionsAlert
) : (
<div className="flex flex-row gap-x-2">
{installableExtensions.map((extension) => (
<MissingExtensionAlert key={extension.name} extension={extension} />
))}
</div>
{hasMissingExtensions && (
<div className="py-3 px-4 border-t">{integration.missingExtensionsAlert}</div>
)}
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
</CardContent>
</Card>
</div>
)}
{!!actions && (
<div
aria-disabled={hasToInstallExtensions}
className={cn(
'px-10 max-w-4xl',
hasToInstallExtensions && 'opacity-25 [&_button]:pointer-events-none'
)}
>
{actions}
</div>
)}
{!!actions && !hasToInstallExtensions && <div className="px-10 max-w-4xl">{actions}</div>}
<MarkdownContent key={integration.id} integrationId={integration.id} />
<Separator />
{children}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ import { cn } from 'ui'

const CHAR_LIMIT = 500 // Adjust this number as needed

export const MarkdownContent = ({ integrationId }: { integrationId: string }) => {
export const MarkdownContent = ({
integrationId,
initiallyExpanded,
}: {
integrationId: string
initiallyExpanded?: boolean
}) => {
const [content, setContent] = useState<string>('')
const [isExpanded, setIsExpanded] = useState(false)
const [isExpanded, setIsExpanded] = useState(initiallyExpanded ?? false)

useEffect(() => {
import(`static-data/integrations/${integrationId}/overview.md`)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Clock5, Layers, Timer, Vault, Webhook } from 'lucide-react'
import { Clock5, Layers, Timer, Vault, Webhook, Receipt } from 'lucide-react'
import dynamic from 'next/dynamic'
import Image from 'next/image'
import { ComponentType, ReactNode } from 'react'
Expand Down Expand Up @@ -51,7 +51,7 @@ const authorSupabase = {
websiteUrl: 'https://supabase.com',
}

const supabaseIntegrations: IntegrationDefinition[] = [
const SUPABASE_INTEGRATIONS: IntegrationDefinition[] = [
{
id: 'queues',
type: 'postgres_extension' as const,
Expand Down Expand Up @@ -315,7 +315,7 @@ const supabaseIntegrations: IntegrationDefinition[] = [
},
] as const

const wrapperIntegrations: IntegrationDefinition[] = WRAPPERS.map((w) => {
const WRAPPER_INTEGRATIONS: IntegrationDefinition[] = WRAPPERS.map((w) => {
return {
id: w.name,
type: 'wrapper' as const,
Expand Down Expand Up @@ -366,7 +366,66 @@ const wrapperIntegrations: IntegrationDefinition[] = WRAPPERS.map((w) => {
}
})

const TEMPLATE_INTEGRATIONS: IntegrationDefinition[] = [
{
id: 'stripe_sync_engine',
type: 'custom' as const,
requiredExtensions: ['pgmq', 'supabase_vault', 'pg_cron', 'pg_net'],
missingExtensionsAlert: <UpgradeDatabaseAlert minimumVersion="15.6.1.143" />,
name: `Stripe Sync Engine`,
status: 'alpha',
icon: ({ className, ...props } = {}) => (
<Image
fill
src={`${BASE_PATH}/img/icons/stripe-icon.svg`}
alt={'Stripe Logo'}
className={cn('p-2', className)}
{...props}
/>
),
description:
'Continuously sync your payments, customer, and other data from Stripe to your Postgres database',
docsUrl: 'https://github.com/stripe-experiments/sync-engine/',
author: {
name: 'Stripe',
websiteUrl: 'https://www.stripe.com',
},
navigation: [
{
route: 'overview',
label: 'Overview',
},
{
route: 'settings',
label: 'Settings',
},
],
navigate: (_id: string, pageId: string = 'overview', _childId: string | undefined) => {
switch (pageId) {
case 'overview':
return dynamic(
() =>
import(
'components/interfaces/Integrations/templates/StripeSyncEngine/InstallationOverview'
).then((mod) => mod.StripeSyncInstallationPage),
{ loading: Loading }
)
case 'settings':
return dynamic(
() =>
import(
'components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncSettingsPage'
).then((mod) => mod.StripeSyncSettingsPage),
{ loading: Loading }
)
}
return null
},
},
]

export const INTEGRATIONS: IntegrationDefinition[] = [
...wrapperIntegrations,
...supabaseIntegrations,
...WRAPPER_INTEGRATIONS,
...SUPABASE_INTEGRATIONS,
...TEMPLATE_INTEGRATIONS,
]
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,36 @@ import { useMemo } from 'react'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useFDWsQuery } from 'data/fdw/fdws-query'
import { useFlag } from 'common'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { EMPTY_ARR } from 'lib/void'
import {
INSTALLATION_INSTALLED_SUFFIX,
STRIPE_SCHEMA_COMMENT_PREFIX,
} from 'stripe-experiment-sync/supabase'
import { wrapperMetaComparator } from '../Wrappers/Wrappers.utils'
import { INTEGRATIONS } from './Integrations.constants'

export const useInstalledIntegrations = () => {
const { data: project } = useSelectedProjectQuery()
const { integrationsWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
const stripeSyncEnabled = useFlag('enableStripeSyncEngineIntegration')

const allIntegrations = useMemo(() => {
if (integrationsWrappers) {
return INTEGRATIONS
} else {
return INTEGRATIONS.filter((integration) => !integration.id.endsWith('_wrapper'))
}
}, [integrationsWrappers])
return INTEGRATIONS.filter((integration) => {
if (
!integrationsWrappers &&
(integration.type === 'wrapper' || integration.id.endsWith('_wrapper'))
) {
return false
}
if (!stripeSyncEnabled && integration.id === 'stripe_sync_engine') {
return false
}
return true
})
}, [integrationsWrappers, stripeSyncEnabled])

const {
data,
Expand Down Expand Up @@ -58,30 +71,37 @@ export const useInstalledIntegrations = () => {

const installedIntegrations = useMemo(() => {
return allIntegrations
.filter((i) => {
.filter((integration) => {
// special handling for supabase webhooks
if (i.id === 'webhooks') {
if (integration.id === 'webhooks') {
return isHooksEnabled
}
if (i.type === 'wrapper') {
return wrappers.find((w) => wrapperMetaComparator(i.meta, w))
if (integration.id === 'stripe_sync_engine') {
const stripeSchema = schemas?.find(({ name }) => name === 'stripe')
return (
!!stripeSchema?.comment?.startsWith(STRIPE_SCHEMA_COMMENT_PREFIX) &&
!!stripeSchema.comment?.includes(INSTALLATION_INSTALLED_SUFFIX)
)
}
if (integration.type === 'wrapper') {
return wrappers.find((w) => wrapperMetaComparator(integration.meta, w))
}
if (i.type === 'postgres_extension') {
return i.requiredExtensions.every((extName) => {
if (integration.type === 'postgres_extension') {
return integration.requiredExtensions.every((extName) => {
const foundExtension = (extensions ?? []).find((ext) => ext.name === extName)
return !!foundExtension?.installed_version
})
}
return false
})
.sort((a, b) => a.name.localeCompare(b.name))
}, [wrappers, extensions, isHooksEnabled])
}, [allIntegrations, wrappers, extensions, schemas, isHooksEnabled])

// 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 availableIntegrations = useMemo(
() => allIntegrations.sort((a, b) => a.name.localeCompare(b.name)),
[]
[allIntegrations]
)

const error = fdwError || extensionsError || schemasError
Expand Down
Loading
Loading