IntegrationOverviewTab.tsx123 lines · main
| 1 | import { useParams } from 'common' |
| 2 | import { PropsWithChildren, ReactNode } from 'react' |
| 3 | import { Badge, Card, CardContent, cn, Separator } from 'ui' |
| 4 | |
| 5 | import { INTEGRATIONS } from '../Landing/Integrations.constants' |
| 6 | import { BuiltBySection } from './BuildBySection' |
| 7 | import { MarkdownContent } from './MarkdownContent' |
| 8 | import { MissingExtensionAlert } from './MissingExtensionAlert' |
| 9 | import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query' |
| 10 | import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' |
| 11 | |
| 12 | export interface IntegrationOverviewTabProps { |
| 13 | actions?: ReactNode |
| 14 | status?: string | ReactNode |
| 15 | alert?: ReactNode |
| 16 | hideRequiredExtensionsSection?: boolean |
| 17 | } |
| 18 | |
| 19 | /** [Joshen] This will eventually get replaced by IntegrationOverviewTabV2 */ |
| 20 | export const IntegrationOverviewTab = ({ |
| 21 | actions, |
| 22 | alert, |
| 23 | status, |
| 24 | children, |
| 25 | hideRequiredExtensionsSection = false, |
| 26 | }: PropsWithChildren<IntegrationOverviewTabProps>) => { |
| 27 | const { id } = useParams() |
| 28 | const { data: project } = useSelectedProjectQuery() |
| 29 | |
| 30 | const integration = INTEGRATIONS.find((i) => i.id === id) |
| 31 | |
| 32 | const { data: extensions } = useDatabaseExtensionsQuery({ |
| 33 | projectRef: project?.ref, |
| 34 | connectionString: project?.connectionString, |
| 35 | }) |
| 36 | |
| 37 | if (!integration) { |
| 38 | return <div>Unsupported integration type</div> |
| 39 | } |
| 40 | |
| 41 | const dependsOnExtension = (integration.requiredExtensions ?? []).length > 0 |
| 42 | |
| 43 | const installableExtensions = (extensions ?? []).filter((ext) => |
| 44 | (integration.requiredExtensions ?? []).includes(ext.name) |
| 45 | ) |
| 46 | const hasToInstallExtensions = installableExtensions.some((x) => !x.installed_version) |
| 47 | |
| 48 | // The integration requires extensions that are not available to install on the current database image |
| 49 | const hasMissingExtensions = |
| 50 | installableExtensions.length !== integration.requiredExtensions.length |
| 51 | |
| 52 | return ( |
| 53 | <div className="flex flex-col gap-8 py-10"> |
| 54 | <BuiltBySection integration={integration} status={status} /> |
| 55 | |
| 56 | {!!alert && <div className="px-10 max-w-4xl">{alert}</div>} |
| 57 | |
| 58 | <MarkdownContent key={integration.id} integrationId={integration.id} /> |
| 59 | |
| 60 | <Separator /> |
| 61 | |
| 62 | {dependsOnExtension && !hideRequiredExtensionsSection && ( |
| 63 | <div className="px-4 md:px-10 max-w-4xl flex flex-col gap-y-4"> |
| 64 | <h4>Required extensions</h4> |
| 65 | <Card> |
| 66 | <CardContent className="p-0"> |
| 67 | <ul className="text-foreground-light text-sm"> |
| 68 | {(integration.requiredExtensions ?? []).map((requiredExtension, idx) => { |
| 69 | const extension = (extensions ?? []).find((ext) => ext.name === requiredExtension) |
| 70 | const isInstalled = !!extension?.installed_version |
| 71 | const isLastRow = idx === (integration.requiredExtensions?.length ?? 0) - 1 |
| 72 | |
| 73 | return ( |
| 74 | <li |
| 75 | key={requiredExtension} |
| 76 | className={cn( |
| 77 | 'flex items-center justify-between gap-3 py-2 px-3', |
| 78 | !isLastRow ? 'border-b' : '' |
| 79 | )} |
| 80 | > |
| 81 | <code className="text-xs">{requiredExtension}</code> |
| 82 | |
| 83 | <div className="shrink-0"> |
| 84 | {extension ? ( |
| 85 | isInstalled ? ( |
| 86 | <Badge>Installed</Badge> |
| 87 | ) : ( |
| 88 | <MissingExtensionAlert extension={extension} /> |
| 89 | ) |
| 90 | ) : ( |
| 91 | <span className="text-foreground-muted">Unavailable</span> |
| 92 | )} |
| 93 | </div> |
| 94 | </li> |
| 95 | ) |
| 96 | })} |
| 97 | </ul> |
| 98 | |
| 99 | {hasMissingExtensions && ( |
| 100 | <div className="py-3 px-4 border-t">{integration.missingExtensionsAlert}</div> |
| 101 | )} |
| 102 | </CardContent> |
| 103 | </Card> |
| 104 | </div> |
| 105 | )} |
| 106 | |
| 107 | {!!actions && ( |
| 108 | <div |
| 109 | aria-disabled={hasToInstallExtensions && !hideRequiredExtensionsSection} |
| 110 | className={cn( |
| 111 | 'px-10 max-w-4xl', |
| 112 | hasToInstallExtensions && |
| 113 | !hideRequiredExtensionsSection && |
| 114 | 'opacity-25 [&_button]:pointer-events-none' |
| 115 | )} |
| 116 | > |
| 117 | {actions} |
| 118 | </div> |
| 119 | )} |
| 120 | {children} |
| 121 | </div> |
| 122 | ) |
| 123 | } |