| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- 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, TimestampInfo } from 'ui-patterns'
- import { Input } from 'ui-patterns/DataInputs/Input'
- import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
- 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'
- 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 type="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: any) => 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`}
- >
- Briven CLI
- </InlineLink>{' '}
- and hence we're unable to parse when this migration was
- inserted at.
- </TooltipContent>
- )}
- </Tooltip>
- </TableCell>
- <TableCell align="right">
- <Button
- type="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 type="default" onClick={() => setSelectedMigration(undefined)}>
- Close
- </Button>
- </div>
- }
- >
- <div className="h-full">
- <div className="relative h-full">
- <CodeEditor
- isReadOnly
- id={selectedMigration?.version ?? ''}
- language="pgsql"
- defaultValue={
- selectedMigration?.statements?.join(';\n') +
- (selectedMigration?.statements?.length ? ';' : '')
- }
- />
- </div>
- </div>
- </SidePanel>
- </>
- )
- }
- export default Migrations
|