mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 21:29:08 +03:00
## Problem The enable-extension dialog in #51440 needs clearer installation copy and a consistent disclosure for custom-schema guidance. On narrow screens, footer buttons touch and dialog content can scroll sideways. ## Solution Polish the dialog on top of #51440: - Explain fixed-schema requirements and hide custom-schema guidance when the extension requires a specific schema. - Share the disclosure interaction with the session-expired and API-hardening dialogs through `ui-patterns/DialogDisclosure`, documented with a live design-system example. - Fix narrow-screen overflow and add vertical spacing to shared dialog footers. - Refine inline-code styling and update the PostGIS instructions and E2E button locator. | After | | --- | | <img width="852" height="710" alt="CleanShot 2026-10-09 at 12 17 47@2x" src="https://github.com/user-attachments/assets/59f6f1e7-9884-4cdb-b678-62b7f88a8ec5" /> | | _Standard extension_ | | <img width="836" height="1092" alt="52254" src="https://github.com/user-attachments/assets/dbf53d3e-6583-4c3b-8f43-577e0723d9a7" /> | | _Standard extension expanded accordion_ | | <img width="836" height="604" alt="CleanShot 2026-10-09 at 12 20 04@2x" src="https://github.com/user-attachments/assets/c8e3961f-2683-440c-a2a2-48ccd53c4a17" /> | | _Extension with fixed schema_ | ## Review instructions 1. Open `/project/<ref>/database/extensions`. Turn on the toggle for a disabled `postgres_fdw` extension. Expand **Install in a different schema** and check its hover, keyboard focus and full-width click target. Repeat with `pg_cron` or `pgmq`: the intro must say **must**, and custom-schema guidance must be absent. Cancel without enabling. 2. At 320 px and 375 px, check the extension dialog's alignment, inline-code rendering, wrapped SQL and stacked **Enable** / **Cancel** spacing. Confirm the page does not scroll sideways. 3. On a platform project with the Data API enabled, open `/project/<ref>/integrations/data_api/settings`. Under **Harden Data API**, click **Harden Data API**. Expand both sections and check the same trigger behaviour. Confirm completed sections still show their check icons. Close without applying either action. 4. The session-expired dialog has no direct route. It is rendered by `apps/studio/hooks/misc/withAuth.tsx` after authentication loading exceeds 10 seconds. For repeatable local review, temporarily pass `visible={true}` to its `SessionTimeoutModal` callsite, then open `/projects`. Expand **Having trouble?** by clicking the text or chevron and using Enter / Space. Check the help text and controls. Close the dialog without signing out or clearing site data, then undo the temporary prop change. 5. In the design system, open [`/docs/components/dialog#optional-details`](https://design-system-git-dnywh-choreextension-dialog-polish-supabase.vercel.app/design-system/docs/components/dialog#optional-details). Click **Export results**, then expand **What’s included** with the mouse and keyboard. The demo imports the same `ui-patterns/DialogDisclosure` used by Studio. 6. Open another dialog and an alert dialog at a narrow width. Check the shared 8 px gap between stacked footer buttons. Review inline-code examples on `/docs/components/typography` for the shared styling change.
172 lines
5.9 KiB
TypeScript
172 lines
5.9 KiB
TypeScript
import { ident, safeSql } from '@supabase/pg-meta'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Button,
|
|
Card,
|
|
CardContent,
|
|
cn,
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogSection,
|
|
DialogSectionSeparator,
|
|
DialogTitle,
|
|
} from 'ui'
|
|
import { Admonition } from 'ui-patterns/Admonition'
|
|
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
|
import {
|
|
DialogDisclosure,
|
|
DialogDisclosureContent,
|
|
DialogDisclosureTrigger,
|
|
} from 'ui-patterns/DialogDisclosure'
|
|
|
|
import { DocsButton } from '@/components/ui/DocsButton'
|
|
import { useDatabaseExtensionEnableMutation } from '@/data/database-extensions/database-extension-enable-mutation'
|
|
import { type DatabaseExtension } from '@/data/database-extensions/database-extensions-query'
|
|
import { useIsOrioleDb, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { DOCS_URL } from '@/lib/constants'
|
|
|
|
const orioleExtCallOuts = ['vector', 'postgis']
|
|
|
|
interface EnableExtensionModalProps {
|
|
visible: boolean
|
|
extension: DatabaseExtension
|
|
onCancel: () => void
|
|
}
|
|
|
|
export const EnableExtensionModal = ({
|
|
visible,
|
|
extension,
|
|
onCancel,
|
|
}: EnableExtensionModalProps) => {
|
|
const isOrioleDb = useIsOrioleDb()
|
|
|
|
const { data: project } = useSelectedProjectQuery()
|
|
|
|
// [Joshen] Hard-coding pg_cron here as this is enforced on our end (Not via pg_available_extension_versions)
|
|
const defaultSchema =
|
|
extension.name === 'pg_cron' ? 'pg_catalog' : extension.default_version_schema
|
|
const hasFixedSchema = defaultSchema != null
|
|
|
|
const { mutate: enableExtension, isPending: isEnabling } = useDatabaseExtensionEnableMutation({
|
|
onSuccess: () => {
|
|
toast.success(`Extension "${extension.name}" is now enabled`)
|
|
onCancel()
|
|
},
|
|
onError: (error) => {
|
|
toast.error(`Failed to enable ${extension.name}: ${error.message}`)
|
|
},
|
|
})
|
|
|
|
const onConfirmEnable = async () => {
|
|
if (project === undefined) return console.error('Project is required')
|
|
|
|
enableExtension({
|
|
projectRef: project.ref,
|
|
connectionString: project?.connectionString,
|
|
schema: defaultSchema ?? 'extensions',
|
|
name: extension.name,
|
|
version: extension.default_version,
|
|
cascade: true,
|
|
})
|
|
}
|
|
|
|
return (
|
|
<Dialog
|
|
open={visible}
|
|
onOpenChange={(open: boolean) => {
|
|
if (!open) onCancel()
|
|
}}
|
|
>
|
|
<DialogContent size="small" className="min-w-0 overflow-hidden" aria-describedby={undefined}>
|
|
<DialogHeader>
|
|
<DialogTitle>Enable {extension.name}</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<DialogSectionSeparator />
|
|
|
|
{isOrioleDb && orioleExtCallOuts.includes(extension.name) && (
|
|
<Admonition
|
|
type="default"
|
|
title="Extension is limited by OrioleDB"
|
|
className="border-x-0 border-t-0 rounded-none"
|
|
>
|
|
<span className="block">
|
|
{extension.name} cannot be accelerated by indexes on tables that are using the
|
|
OrioleDB access method
|
|
</span>
|
|
<DocsButton abbrev={false} className="mt-2" href={`${DOCS_URL}`} />
|
|
</Admonition>
|
|
)}
|
|
|
|
<DialogSection className="flex flex-col gap-y-4">
|
|
<p className="text-sm text-foreground-light">
|
|
{hasFixedSchema
|
|
? 'This extension must be installed in the schema below.'
|
|
: 'This extension will be installed in the schema below.'}
|
|
</p>
|
|
|
|
<Card>
|
|
<CardContent className="divide-y text-sm p-0">
|
|
<div className="flex items-center justify-between px-3 py-2.5">
|
|
<p className="text-foreground-lighter">Extension</p>
|
|
<code className="text-code-inline">{extension.name}</code>
|
|
</div>
|
|
<div className="flex items-center justify-between px-3 py-2.5">
|
|
<p className="text-foreground-lighter">Schema</p>
|
|
<code data-testid="enable-extension-schema" className="text-code-inline">
|
|
{defaultSchema ?? 'extensions'}
|
|
</code>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</DialogSection>
|
|
|
|
{!hasFixedSchema && (
|
|
<>
|
|
<DialogSectionSeparator />
|
|
<DialogDisclosure>
|
|
<DialogDisclosureTrigger className="py-4 px-4 md:px-5">
|
|
<span>Install in a different schema</span>
|
|
</DialogDisclosureTrigger>
|
|
<DialogDisclosureContent>
|
|
<DialogSection className="pt-1 min-w-0 flex flex-col gap-y-3">
|
|
<div className="text-sm text-foreground-light flex flex-col gap-y-3">
|
|
<p>
|
|
We recommend using the <code className="text-code-inline">extensions</code>{' '}
|
|
schema to keep extension objects separate from your own tables.
|
|
</p>
|
|
<p>
|
|
To use another schema, run the following in the SQL Editor. Replace{' '}
|
|
<code className="text-code-inline">target_schema</code> with an existing
|
|
schema name.
|
|
</p>
|
|
</div>
|
|
<CodeBlock
|
|
language="pgsql"
|
|
hideLineNumbers
|
|
wrapLongLines
|
|
wrapperClassName={cn('min-w-0 max-w-full [&_pre]:px-3 [&_pre]:py-3')}
|
|
className="[&_code]:text-xs"
|
|
value={safeSql`create extension if not exists ${ident(extension.name)} schema target_schema;`}
|
|
/>
|
|
</DialogSection>
|
|
</DialogDisclosureContent>
|
|
</DialogDisclosure>
|
|
</>
|
|
)}
|
|
|
|
<DialogFooter>
|
|
<Button disabled={isEnabling} onClick={() => onCancel()}>
|
|
Cancel
|
|
</Button>
|
|
<Button variant="primary" loading={isEnabling} onClick={() => onConfirmEnable()}>
|
|
Enable
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|