remove from table editor

This commit is contained in:
Danny White committed 2026-06-19 17:18:24 -06:00
1 parent 297db006e7
commit e7e1b444f3
3 files changed
+134 -278

No files matched your search

@@ -1,262 +0,0 @@
import { ChevronDown } from 'lucide-react'
import { useState, type ReactNode } from 'react'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from 'ui'
import { TimestampInfo } from 'ui-patterns'
import { InfoTooltip } from 'ui-patterns/info-tooltip'
import {
formatWarehouseSize,
useWarehouseTableState,
type WarehouseMode,
type WarehouseTableState,
} from './warehouseDemoStore'
import { WarehouseDetachModal } from './WarehouseDetachModal'
import { WarehouseEnablementModal, type EnablementVariant } from './WarehouseEnablementModal'
import { WarehouseSyncChip } from './WarehouseSyncChip'
import { WarehouseTimeTravelFlow } from './WarehouseTimeTravelFlow'
import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
interface WarehouseEditorPanelProps {
tableKey: string
tableName: string
postgresSize?: string
}
const MODE_LABELS: Record<WarehouseMode, string> = {
postgres: 'Postgres',
has_warehouse_copy: 'Postgres + Warehouse',
warehouse_backed: 'Warehouse',
}
const WAREHOUSE_MODE_TOOLTIPS: Partial<Record<WarehouseMode, ReactNode>> = {
has_warehouse_copy: (
<>
Keeps this table in the Postgres heap and maintains a synced columnar copy in Warehouse.
Changes in Postgres propagate to the copy; sync is one-way, not bidirectional.
</>
),
warehouse_backed: (
<>
This table&apos;s storage was moved to Warehouse. The Postgres heap for this table no longer
exists.
</>
),
}
function StorageCard({ children }: { children: ReactNode }) {
return <div className="rounded-md border bg-surface-75 divide-y text-sm">{children}</div>
}
function StorageMetaRow({ label, children }: { label: string; children: ReactNode }) {
return (
<div className="flex items-center justify-between gap-4 px-4 py-2.5">
<span className="text-foreground-lighter shrink-0">{label}</span>
<div className="text-foreground text-right min-w-0">{children}</div>
</div>
)
}
function StorageModeRow({ mode }: { mode: WarehouseMode }) {
const tooltip = WAREHOUSE_MODE_TOOLTIPS[mode]
return (
<StorageMetaRow label="Mode">
<div className="flex items-center justify-end gap-1.5">
<span>{MODE_LABELS[mode]}</span>
{tooltip && (
<InfoTooltip side="top" className="max-w-80">
{tooltip}
</InfoTooltip>
)}
</div>
</StorageMetaRow>
)
}
function StorageSyncRows({ state }: { state: WarehouseTableState }) {
return (
<>
{state.syncState && (
<StorageMetaRow label="Status">
<WarehouseSyncChip syncState={state.syncState} />
</StorageMetaRow>
)}
{state.lastSyncedAt !== undefined && (
<StorageMetaRow label="Last synced">
<TimestampInfo
className="text-sm text-foreground-light"
utcTimestamp={state.lastSyncedAt}
displayAs="local"
/>
</StorageMetaRow>
)}
{state.lagSeconds !== undefined && (
<StorageMetaRow label="Lag">
<span className="text-foreground-light">{state.lagSeconds}s</span>
</StorageMetaRow>
)}
</>
)
}
function CreateWarehouseCopyButton({
onAttach,
onMove,
}: {
onAttach: () => void
onMove: () => void
}) {
return (
<div className="flex">
<Button
type="button"
variant="default"
className="rounded-r-none hover:z-10"
onClick={onAttach}
>
Copy to Warehouse
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="default"
aria-label="More warehouse storage options"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px"
icon={<ChevronDown />}
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuItem onClick={onMove}>Move to Warehouse</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}
export function WarehouseEditorPanel({
tableKey,
tableName,
postgresSize,
}: WarehouseEditorPanelProps) {
const state = useWarehouseTableState(tableKey)
const [enableModal, setEnableModal] = useState<EnablementVariant | null>(null)
const [confirmDetach, setConfirmDetach] = useState(false)
const [detachProgress, setDetachProgress] = useState(false)
const [snapshotsOpen, setSnapshotsOpen] = useState(false)
const copyName = state.copyName ?? `warehouse.${tableName}`
const warehouseSize = formatWarehouseSize(state.warehouseSizeBytes)
return (
<>
<div className="space-y-4">
<h5>Storage</h5>
{state.mode === 'postgres' && (
<>
<StorageCard>
<StorageModeRow mode="postgres" />
<StorageMetaRow label="Size">{postgresSize ?? '—'}</StorageMetaRow>
</StorageCard>
<CreateWarehouseCopyButton
onAttach={() => setEnableModal('attach')}
onMove={() => setEnableModal('move')}
/>
</>
)}
{state.mode === 'has_warehouse_copy' && (
<>
<StorageCard>
<StorageModeRow mode="has_warehouse_copy" />
<StorageMetaRow label="Copy name">
<code className="text-code-inline break-all">{copyName}</code>
</StorageMetaRow>
<StorageMetaRow label="Postgres size">{postgresSize ?? '—'}</StorageMetaRow>
<StorageMetaRow label="Copy size">{warehouseSize}</StorageMetaRow>
<StorageSyncRows state={state} />
</StorageCard>
<Button type="button" variant="danger" onClick={() => setConfirmDetach(true)}>
Detach Warehouse copy
</Button>
</>
)}
{state.mode === 'warehouse_backed' && (
<>
<StorageCard>
<StorageModeRow mode="warehouse_backed" />
<StorageMetaRow label="Size">{warehouseSize}</StorageMetaRow>
{state.migrationCompletedAt && (
<StorageMetaRow label="Moved">
<TimestampInfo
className="text-sm text-foreground-light"
utcTimestamp={state.migrationCompletedAt}
displayAs="local"
/>
</StorageMetaRow>
)}
</StorageCard>
<Button type="button" variant="default" onClick={() => setSnapshotsOpen(true)}>
View snapshots
</Button>
</>
)}
</div>
{enableModal && (
<WarehouseEnablementModal
open={true}
variant={enableModal}
tableKey={tableKey}
tableName={tableName}
onOpenChange={(open) => {
if (!open) setEnableModal(null)
}}
/>
)}
<DiscardChangesConfirmationDialog
visible={confirmDetach}
onCancel={() => setConfirmDetach(false)}
onClose={() => {
setConfirmDetach(false)
setDetachProgress(true)
}}
size="small"
title="Detach Warehouse copy"
description={
<>
Detaching deletes the Warehouse copy{' '}
<code className="text-code-inline break-keep">{copyName}</code>. Your source table and
its data in Postgres are unaffected.
</>
}
confirmLabel="Detach"
cancelLabel="Cancel"
/>
<WarehouseDetachModal
open={detachProgress}
tableKey={tableKey}
copyName={copyName}
onOpenChange={setDetachProgress}
/>
{state.mode === 'warehouse_backed' && (
<WarehouseTimeTravelFlow
tableKey={tableKey}
sheetOpen={snapshotsOpen}
onSheetOpenChange={setSnapshotsOpen}
/>
)}
</>
)
}
@@ -25,7 +25,6 @@ import {
validateFields,
} from './TableEditor.utils'
import { RLSToggleDialog } from '@/components/interfaces/Database/RLSToggleDialog'
import { WarehouseEditorPanel } from '@/components/interfaces/Database/Warehouse/WarehouseEditorPanel'
import { DocsButton } from '@/components/ui/DocsButton'
import { useDatabasePublicationsQuery } from '@/data/database-publications/database-publications-query'
import { CONSTRAINT_TYPE, useTableConstraintsQuery } from '@/data/database/constraints-query'
@@ -626,19 +625,6 @@ export const TableEditor = ({
handler={apiAccessToggleHandler}
/>
</SidePanel.Content>
{!isNewRecord && !isDuplicating && (
<>
<SidePanel.Separator />
<SidePanel.Content className="py-6">
<WarehouseEditorPanel
tableKey={`${table?.schema ?? 'public'}.${table?.name ?? ''}`}
tableName={table?.name ?? ''}
postgresSize={table?.size}
/>
</SidePanel.Content>
</>
)}
</>
</SidePanel>
)
@@ -1,7 +1,18 @@
import { useParams } from 'common'
import { Copy, Download, Edit, Globe, Lock, MoreVertical, Trash } from 'lucide-react'
import {
Box,
Copy,
Download,
Edit,
Globe,
History,
Lock,
MoreVertical,
Trash,
Unlink,
} from 'lucide-react'
import Link from 'next/link'
import { type CSSProperties } from 'react'
import { useState, type CSSProperties } from 'react'
import { toast } from 'sonner'
import {
Badge,
@@ -21,11 +32,20 @@ import {
TooltipTrigger,
TreeViewItemVariant,
} from 'ui'
import { useSnapshot } from 'valtio'
import { useExportAllRowsAsCsv, useExportAllRowsAsSql } from './ExportAllRows'
import { useTableFilter } from '@/components/grid/hooks/useTableFilter'
import { buildTableEditorUrl } from '@/components/grid/SupabaseGrid.utils'
import { warehouseDemoStore } from '@/components/interfaces/Database/Warehouse/warehouseDemoStore'
import { WarehouseDetachModal } from '@/components/interfaces/Database/Warehouse/WarehouseDetachModal'
import {
WarehouseEnablementModal,
type EnablementVariant,
} from '@/components/interfaces/Database/Warehouse/WarehouseEnablementModal'
import { WarehouseTimeTravelFlow } from '@/components/interfaces/Database/Warehouse/WarehouseTimeTravelFlow'
import { getEntityLintDetails } from '@/components/interfaces/TableGridEditor/TableEntity.utils'
import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
import { EntityTypeIcon } from '@/components/ui/EntityTypeIcon'
import { InlineLink } from '@/components/ui/InlineLink'
import { getTableDefinition } from '@/data/database/table-definition-query'
@@ -177,6 +197,18 @@ export const EntityListItem = ({
totalRows: rowCount,
})
// Warehouse storage (demo). Storage is an infra operation, so the actions live
// here in the table's menu rather than in the Edit Table form.
const warehouseSnap = useSnapshot(warehouseDemoStore)
const warehouseKey = `${entity.schema}.${entity.name}`
const warehouseState = warehouseSnap.tables[warehouseKey]
const warehouseMode = warehouseState?.mode ?? 'postgres'
const warehouseCopyName = warehouseState?.copyName ?? `warehouse.${entity.name}`
const [warehouseModal, setWarehouseModal] = useState<EnablementVariant | null>(null)
const [detachConfirm, setDetachConfirm] = useState(false)
const [detachProgress, setDetachProgress] = useState(false)
const [snapshotsOpen, setSnapshotsOpen] = useState(false)
return (
<EditorTablePageLink
title={entity.name}
@@ -411,6 +443,62 @@ export const EntityListItem = ({
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator />
{warehouseMode === 'postgres' && (
<DropdownMenuSub>
<DropdownMenuSubTrigger className="gap-x-2">
<Box size={12} className="shrink-0" />
Storage
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem
key="warehouse-copy"
onClick={(e) => {
e.stopPropagation()
setWarehouseModal('attach')
}}
>
<span>Copy to Warehouse</span>
</DropdownMenuItem>
<DropdownMenuItem
key="warehouse-move"
onClick={(e) => {
e.stopPropagation()
setWarehouseModal('move')
}}
>
<span>Move to Warehouse</span>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
)}
{warehouseMode === 'has_warehouse_copy' && (
<DropdownMenuItem
key="warehouse-detach"
className="gap-x-2"
onClick={(e) => {
e.stopPropagation()
setDetachConfirm(true)
}}
>
<Unlink size={12} className="shrink-0" />
<span>Detach Warehouse copy</span>
</DropdownMenuItem>
)}
{warehouseMode === 'warehouse_backed' && (
<DropdownMenuItem
key="warehouse-snapshots"
className="gap-x-2"
onClick={(e) => {
e.stopPropagation()
setSnapshotsOpen(true)
}}
>
<History size={12} className="shrink-0" />
<span>View snapshots</span>
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem
key="delete-table"
@@ -527,6 +615,50 @@ export const EntityListItem = ({
</>
{exportCsvConfirmationModal}
{exportSqlConfirmationModal}
{warehouseModal && (
<WarehouseEnablementModal
open
variant={warehouseModal}
tableKey={warehouseKey}
tableName={entity.name}
onOpenChange={(open) => {
if (!open) setWarehouseModal(null)
}}
/>
)}
<DiscardChangesConfirmationDialog
visible={detachConfirm}
onCancel={() => setDetachConfirm(false)}
onClose={() => {
setDetachConfirm(false)
setDetachProgress(true)
}}
size="small"
title="Detach Warehouse copy"
description={
<>
Detaching deletes the Warehouse copy{' '}
<code className="text-code-inline break-keep">{warehouseCopyName}</code>. Your source
table and its data in Postgres are unaffected.
</>
}
confirmLabel="Detach"
cancelLabel="Cancel"
/>
<WarehouseDetachModal
open={detachProgress}
tableKey={warehouseKey}
copyName={warehouseCopyName}
onOpenChange={setDetachProgress}
/>
{warehouseMode === 'warehouse_backed' && (
<WarehouseTimeTravelFlow
tableKey={warehouseKey}
sheetOpen={snapshotsOpen}
onSheetOpenChange={setSnapshotsOpen}
/>
)}
</EditorTablePageLink>
)
}