mirror of
https://github.com/supabase/supabase.git
synced 2026-10-18 07:39:07 +03:00
## Context Part of efforts to consolidate all the code editors that we have in the repository `CodeEditor` will serve as the base monaco editor file that all UIs should consume from It's aimed to be generic and just stores the common logic that will be generally used where-ever we need a code editor (editor options, base editor set up on mount, etc) The idea is that `CodeEditor` holds just 3 default actions (run queyr, format document and placeholder fill) If any editor needs specific behaviours (e.g SQL Editor), they can declare them in the `onMount` prop of `CodeEditor` which gives some flexibility ## Changes involved - Use `CodeEditor` component for SQL Editor's `MonacoEditor` - Shifted Cmd K behaviour into `CodeEditor` since that's probably needed everywhere that we render that UI - Deprecate `MonacoEditor` from the table editor's `grid` folder - All files that were using that component to use `CodeEditor` component instead <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Refactor** * Migrated the studio’s code editing UI (including JSON, text, and payload viewers) to a unified CodeEditor experience. * **New Features** * Added plaintext language support for read-only/truncated views. * **Behavior Changes** * Improved editor startup by setting cursor position consistently and deferring autofocus. * Streamlined editor context-menu actions to the core set (run query, format, placeholder fill). * Updated SQL editor wiring for more consistent command/menu and selection handling. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
239 lines
8.8 KiB
TypeScript
239 lines
8.8 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, 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 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: 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`}
|
|
>
|
|
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
|
|
isReadOnly
|
|
id={selectedMigration?.version ?? ''}
|
|
language="pgsql"
|
|
defaultValue={
|
|
selectedMigration?.statements?.join(';\n') +
|
|
(selectedMigration?.statements?.length ? ';' : '')
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</SidePanel>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default Migrations
|