Files
Alaister YoungandAlaister Young ca2b50a0a7 chore(ui-patterns): collapse the admonition shim into ui-patterns/Admonition (#48377)
Follow-up to #48344: collapses the two resolution paths for the
Admonition module into one.

`src/admonition.tsx` was a back-compat shim re-exporting
`src/Admonition/`. Two ways to resolve one module is exactly what
produced the macOS self-import bug fixed in #48344, and the local
typecheck errors that #48374 worked around. This removes the shim and
standardizes on the PascalCase subpath, matching every other export in
the package.

**Changed:**

- Codemodded all 246 `ui-patterns/admonition` imports to
`ui-patterns/Admonition` (240 `.tsx`, 5 `.mdx`, 1 `.ts` across studio,
docs, www, design-system, and lite-studio)
- Pointed the 5 internal `'../admonition'` imports back at the
`'../Admonition'` directory

**Removed:**

- `packages/ui-patterns/src/admonition.tsx`, and its `./admonition`
entry in the exports map (regenerated with `pnpm gen:exports`)

## To test

- `grep -r "ui-patterns/admonition" --include='*.ts*'` → no hits
- `pnpm test:case-hazards` → passes
- `pnpm typecheck` → all 15 tasks green
- `pnpm --filter studio run lint:ratchet` → passes
- `pnpm --filter ui-patterns vitest run src/Admonition` → 11 tests pass

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Standardized Admonition component imports across the application and
documentation.
* Improved compatibility with case-sensitive environments by using the
canonical component path.
  * Removed the legacy Admonition import entry point.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-07-29 00:48:56 +08:00

242 lines
9.1 KiB
TypeScript

import { SupportCategories } from '@supabase/shared-types/out/constants'
import { Search } from 'lucide-react'
import { useRef, useState } from 'react'
import {
Button,
Card,
cn,
SidePanel,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { Input } from 'ui-patterns/DataInputs/Input'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { TimestampInfo } from 'ui-patterns/TimestampInfo'
import { MigrationsEmptyState } from './MigrationsEmptyState'
import { SupportLink } from '@/components/interfaces/Support/SupportLink'
import { CodeEditor } from '@/components/ui/CodeEditor/CodeEditor'
import { InlineLink } from '@/components/ui/InlineLink'
import { DatabaseMigration, useMigrationsQuery } from '@/data/database/migrations-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { DOCS_URL } from '@/lib/constants'
import { formatMigrationVersionLabel, parseMigrationVersion } from '@/lib/migration-utils'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
export const Migrations = () => {
const [search, setSearch] = useState('')
const [selectedMigration, setSelectedMigration] = useState<DatabaseMigration>()
const searchInputRef = useRef<HTMLInputElement>(null)
useShortcut(
SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH,
() => {
searchInputRef.current?.focus()
searchInputRef.current?.select()
},
{ label: 'Search migrations' }
)
useShortcut(SHORTCUT_IDS.LIST_PAGE_RESET_FILTERS, () => setSearch(''))
const { data: project } = useSelectedProjectQuery()
const {
data = [],
isPending: isLoading,
isSuccess,
isError,
error,
} = useMigrationsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const migrations =
search.length === 0
? data
: (data.filter(
(migration) => migration.version.includes(search) || migration.name?.includes(search)
) ?? [])
return (
<>
{isLoading && (
<div className="space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
)}
<div>
{isError && (
<Admonition
type="warning"
title="Failed to retrieve migration history for database"
description={
<>
<p className="mb-1">
Try refreshing your browser, but if the issue persists for more than a few
minutes, please reach out to us via support.
</p>
<p className="mb-4">Error: {error?.message ?? 'Unknown'}</p>
</>
}
>
<Button key="contact-support" asChild variant="default">
<SupportLink
queryParams={{
projectRef: project?.ref,
category: SupportCategories.DASHBOARD_BUG,
subject: 'Unable to view database migrations',
}}
>
Contact support
</SupportLink>
</Button>
</Admonition>
)}
{isSuccess && (
<div>
{data.length <= 0 && <MigrationsEmptyState />}
{data.length > 0 && (
<div className="flex flex-col gap-y-4">
<Input
ref={searchInputRef}
size="tiny"
placeholder="Search for a migration"
value={search}
className="w-full lg:w-52"
onChange={(e) => setSearch(e.target.value)}
icon={<Search />}
/>
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead key="version" style={{ width: '180px' }}>
Version
</TableHead>
<TableHead key="name">Name</TableHead>
<TableHead key="insertedAt">Inserted at (UTC)</TableHead>
<TableHead key="buttons" />
</TableRow>
</TableHeader>
<TableBody>
{migrations.length > 0 ? (
migrations.map((migration) => {
const versionDayjs = parseMigrationVersion(migration.version)
const label = formatMigrationVersionLabel(migration.version)
const insertedAt = versionDayjs ? versionDayjs.toISOString() : undefined
return (
<TableRow key={migration.version}>
<TableCell>{migration.version}</TableCell>
<TableCell
className={cn(
(migration?.name ?? '').length === 0 && 'text-foreground-lighter!'
)}
>
{migration?.name ?? 'Name not available'}
</TableCell>
<TableCell>
<Tooltip>
<TooltipTrigger>
{!!insertedAt ? (
<TimestampInfo
className="text-sm"
label={label}
utcTimestamp={insertedAt}
/>
) : (
<p className="text-foreground-lighter">Unknown</p>
)}
</TooltipTrigger>
{!insertedAt && (
<TooltipContent side="right" className="w-64 text-center">
This migration was not generated via the{' '}
<InlineLink
href={`${DOCS_URL}/guides/deployment/database-migrations`}
>
Supabase CLI
</InlineLink>{' '}
and hence we're unable to parse when this migration was
inserted at.
</TooltipContent>
)}
</Tooltip>
</TableCell>
<TableCell align="right">
<Button
variant="default"
onClick={() => setSelectedMigration(migration)}
>
View migration SQL
</Button>
</TableCell>
</TableRow>
)
})
) : (
<TableRow>
<TableCell colSpan={3}>
<p className="text-sm text-foreground">No results found</p>
<p className="text-sm text-foreground-light">
Your search for "{search}" did not return any results
</p>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</Card>
</div>
)}
</div>
)}
</div>
<SidePanel
size="large"
visible={selectedMigration !== undefined}
header={`Migration: ${selectedMigration?.version}`}
onCancel={() => setSelectedMigration(undefined)}
customFooter={
<div className="flex items-center justify-end p-4 border-t border-overlay-border">
<Button variant="default" onClick={() => setSelectedMigration(undefined)}>
Close
</Button>
</div>
}
>
<div className="h-full">
<div className="relative h-full">
<CodeEditor
// The CodeEditor does not react to content only changes,
// specifically when two projects have migrations with the same version but different content.
// Setting the key ensure it always update when the migration changes
key={`${project?.ref}-${selectedMigration?.version}`}
isReadOnly
id={selectedMigration?.version ?? ''}
language="pgsql"
value={
selectedMigration?.statements?.join(';\n') +
(selectedMigration?.statements?.length ? ';' : '')
}
/>
</div>
</div>
</SidePanel>
</>
)
}