cleanup replication and basic warehouse addition

This commit is contained in:
Danny White committed 2026-07-09 15:18:59 -04:00
1 parent 72aa214b0c
commit d8d312fee9
28 files changed
+1199 -48

No files matched your search

@@ -57,7 +57,7 @@ export type ConditionalValue<T> =
// Schema Types - Modes
// ============================================================================
export const CONNECT_MODES = ['framework', 'direct', 'orm', 'mcp', 'server'] as const
export const CONNECT_MODES = ['framework', 'direct', 'orm', 'mcp', 'server', 'warehouse'] as const
export type ConnectMode = (typeof CONNECT_MODES)[number]
export interface ModeDefinition {
@@ -1,4 +1,4 @@
import { Box, Cable, Database, Server, Sparkles } from 'lucide-react'
import { Box, Cable, Database, Server, Sparkles, Warehouse } from 'lucide-react'
import {
cn,
RadioGroupStacked,
@@ -28,6 +28,7 @@ const MODE_ICONS: Record<string, React.ReactNode> = {
orm: <Cable size={16} strokeWidth={1.5} />,
mcp: <Sparkles size={16} strokeWidth={1.5} />,
server: <Server size={16} strokeWidth={1.5} />,
warehouse: <Warehouse size={16} strokeWidth={1.5} />,
}
interface ConnectConfigSectionProps {
@@ -12,6 +12,7 @@ import { useAvailableConnectModes } from './useAvailableConnectModes'
import { useConnectSheetParams } from './useConnectSheetParams'
import { useConnectSheetShortcut } from './useConnectSheetShortcut'
import { useConnectState } from './useConnectState'
import { WarehouseConnectPanel } from './WarehouseConnectPanel'
import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
@@ -233,7 +234,11 @@ export const ConnectSheet = () => {
</div>
)}
<ConnectStepsSection steps={resolvedSteps} state={state} projectKeys={projectKeys} />
{state.mode === 'warehouse' ? (
<WarehouseConnectPanel />
) : (
<ConnectStepsSection steps={resolvedSteps} state={state} projectKeys={projectKeys} />
)}
</div>
</SheetContent>
</Sheet>
@@ -0,0 +1,59 @@
import { useParams } from 'common'
import { useMemo } from 'react'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import { useWarehouseProjectState } from '@/components/interfaces/Database/Warehouse/warehouseDemoStore'
import { buildWarehouseConnectionString } from '@/components/interfaces/Database/Warehouse/warehouseReplication.utils'
export function WarehouseConnectPanel() {
const { ref: projectRef } = useParams()
const warehouseState = useWarehouseProjectState(projectRef)
const connectionString = useMemo(
() =>
buildWarehouseConnectionString({
projectRef: projectRef ?? '',
databaseName: 'postgres',
}),
[projectRef]
)
if (!warehouseState.enabled) {
return (
<div className="p-8">
<p className="text-sm text-foreground-light">
Enable Warehouse from Database → Replication to view connection credentials.
</p>
</div>
)
}
return (
<div className="flex flex-col divide-y">
<div className="p-8 flex flex-col gap-y-4">
<div>
<p className="text-sm text-foreground">Warehouse endpoint</p>
<p className="text-sm text-foreground-light mt-1">
Connect directly to your Warehouse replica. Schema and table names match Postgres (for
example, <code>public.events</code> on both endpoints).
</p>
</div>
<div>
<p className="text-sm text-foreground mb-2">Host</p>
<CodeBlock language="bash" hideLineNumbers>
{warehouseState.warehouseHost}
</CodeBlock>
</div>
<div>
<p className="text-sm text-foreground mb-2">Connection string</p>
<CodeBlock language="bash" hideLineNumbers>
{connectionString}
</CodeBlock>
</div>
<p className="text-xs text-foreground-lighter">
Replicated schemas: {warehouseState.includedSchemas.join(', ') || 'none'}
</p>
</div>
</div>
)
}
@@ -269,6 +269,12 @@ export const connectSchema: ConnectSchema = {
description: 'Connect your agent',
fields: ['mcpClient', 'mcpReadonly', 'mcpFeatures'],
},
{
id: 'warehouse',
label: 'Warehouse',
description: 'Warehouse endpoint',
fields: [],
},
],
// -------------------------------------------------------------------------
@@ -1,9 +1,16 @@
import { useParams } from 'common'
import { useMemo } from 'react'
import type { ConnectMode } from './Connect.types'
import { useWarehouseProjectState } from '@/components/interfaces/Database/Warehouse/warehouseDemoStore'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useIsWarehouseEnabled } from '@/hooks/misc/useIsWarehouseEnabled'
export function useAvailableConnectModes(): ConnectMode[] {
const { ref: projectRef } = useParams()
const isWarehouseFeatureEnabled = useIsWarehouseEnabled()
const warehouseState = useWarehouseProjectState(projectRef)
const warehouseProjectEnabled = warehouseState.enabled
const {
projectConnectionShowAppFrameworks: showAppFrameworks,
projectConnectionShowMobileFrameworks: showMobileFrameworks,
@@ -21,7 +28,14 @@ export function useAvailableConnectModes(): ConnectMode[] {
{ id: 'direct', enabled: true },
{ id: 'orm', enabled: showOrms },
{ id: 'mcp', enabled: true },
{ id: 'warehouse', enabled: isWarehouseFeatureEnabled && warehouseProjectEnabled },
]
return allModes.filter((m) => m.enabled).map((m) => m.id)
}, [showAppFrameworks, showMobileFrameworks, showOrms])
}, [
showAppFrameworks,
showMobileFrameworks,
showOrms,
isWarehouseFeatureEnabled,
warehouseProjectEnabled,
])
}
@@ -1,5 +1,6 @@
import { useQueryClient } from '@tanstack/react-query'
import { useParams } from 'common'
import { Database } from 'icons'
import { MoreVertical, Plus, Search, X } from 'lucide-react'
import { parseAsStringEnum, useQueryState } from 'nuqs'
import { useEffect, useMemo, useRef, useState } from 'react'
@@ -7,7 +8,6 @@ import {
Button,
Card,
CardContent,
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
@@ -21,6 +21,7 @@ import {
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { Input } from 'ui-patterns/DataInputs/Input'
import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { REPLICA_STATUS } from '../../Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants'
@@ -35,6 +36,8 @@ import {
useIsETLIcebergPrivateAlpha,
useIsETLSnowflakePrivateAlpha,
} from './useIsETLPrivateAlpha'
import { useWarehouseProjectState } from '@/components/interfaces/Database/Warehouse/warehouseDemoStore'
import { WarehouseDestinationRow } from '@/components/interfaces/Database/Warehouse/WarehouseDestinationRow'
import { AlertError } from '@/components/ui/AlertError'
import { DocsButton } from '@/components/ui/DocsButton'
import { Shortcut } from '@/components/ui/Shortcut'
@@ -46,6 +49,7 @@ import { useReplicationPipelinesQuery } from '@/data/replication/pipelines-query
import { useReplicationSourcesQuery } from '@/data/replication/sources-query'
import { checkLocalETLNotSetUp } from '@/data/replication/utils'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useIsWarehouseEnabled } from '@/hooks/misc/useIsWarehouseEnabled'
import { DOCS_URL } from '@/lib/constants'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
@@ -58,6 +62,9 @@ export const Destinations = () => {
const etlEnableIceberg = useIsETLIcebergPrivateAlpha()
const etlEnableDucklake = useIsETLDucklakePrivateAlpha()
const etlEnableSnowflake = useIsETLSnowflakePrivateAlpha()
const isWarehouseFeatureEnabled = useIsWarehouseEnabled()
const warehouseState = useWarehouseProjectState(projectRef)
const hasManagedWarehouse = isWarehouseFeatureEnabled && warehouseState.enabled
const { infrastructureReadReplicas } = useIsFeatureEnabled(['infrastructure:read_replicas'])
const newDestinationDefaultType = infrastructureReadReplicas
@@ -285,14 +292,15 @@ export const Destinations = () => {
{isLocalETLNotSetUp && (
<Admonition
type="default"
title="ETL API not set up locally — destinations cannot be managed"
type="warning"
title="Replication unavailable locally"
description="Configure the replication API to manage destinations in local development."
/>
)}
{isLoading ? (
<GenericSkeletonLoader />
) : hasReplicas || hasDestinations ? (
) : hasReplicas || hasDestinations || hasManagedWarehouse ? (
<Card>
<CardContent className="p-0">
<Table>
@@ -313,6 +321,8 @@ export const Destinations = () => {
</TableRow>
</TableHeader>
<TableBody>
{hasManagedWarehouse && <WarehouseDestinationRow />}
{filteredReplicas.map((replica) => {
return (
<ReadReplicaRow
@@ -330,6 +340,7 @@ export const Destinations = () => {
{!isLoading &&
filteredDestinations.length === 0 &&
filteredReplicas.length === 0 &&
!hasManagedWarehouse &&
(hasReplicas || hasDestinations) && (
<TableRow>
<TableCell colSpan={6}>
@@ -347,27 +358,21 @@ export const Destinations = () => {
) : (
!isLoading &&
!hasErrorsFetchingData && (
<div
className={cn(
'w-full',
'border border-dashed bg-surface-100 border-overlay',
'flex flex-col px-16 rounded-lg justify-center items-center py-8 mt-4'
)}
<EmptyStatePresentational
icon={Database}
title="Add a replication destination"
description="Deploy a Read Replica to reduce latency and isolate workloads, or connect a Pipelines destination for analytics."
className="mt-4"
>
<h4>Replication keeps your data in sync across systems</h4>
<p className="text-foreground-light text-sm text-balance text-center mt-1">
Deploy Read Replicas for lower latency and workload isolation, or add a Pipelines
destination for analytics workloads.
</p>
<Button
variant="default"
icon={<Plus />}
disabled={!newDestinationDefaultType}
onClick={openDestinationPanel}
className="mt-4"
>
Add destination
</Button>
</div>
</EmptyStatePresentational>
)
)}
</div>
@@ -37,6 +37,10 @@ import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning'
import { formatAllEntities } from './Tables.utils'
import { buildTableEditorUrl } from '@/components/grid/SupabaseGrid.utils'
import {
isTableReplicated,
useWarehouseProjectState,
} from '@/components/interfaces/Database/Warehouse/warehouseDemoStore'
import { AlertError } from '@/components/ui/AlertError'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip'
@@ -51,6 +55,7 @@ import { usePrefetchEditorTablePage } from '@/data/prefetchers/project.$ref.edit
import { useInfiniteTablesQuery } from '@/data/tables/tables-query'
import { useViewsQuery } from '@/data/views/views-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useIsWarehouseEnabled } from '@/hooks/misc/useIsWarehouseEnabled'
import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas'
@@ -75,6 +80,9 @@ export const TableList = ({
const router = useRouter()
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const isWarehouseFeatureEnabled = useIsWarehouseEnabled()
const warehouseState = useWarehouseProjectState(ref)
const showReplicatedColumn = isWarehouseFeatureEnabled && warehouseState.enabled
const prefetchEditorTablePage = usePrefetchEditorTablePage()
@@ -377,6 +385,7 @@ export const TableList = ({
<TableHead key="columns">Columns</TableHead>
<TableHead key="rows">Rows (Estimated)</TableHead>
<TableHead key="size">Size (Estimated)</TableHead>
{showReplicatedColumn && <TableHead key="replicated">Replicated</TableHead>}
<TableHead key="realtime">Realtime</TableHead>
<TableHead key="buttons"></TableHead>
</TableRow>
@@ -385,7 +394,7 @@ export const TableList = ({
<>
{entities.length === 0 && filterString.length === 0 && (
<TableRow key={selectedSchema}>
<TableCell colSpan={7}>
<TableCell colSpan={showReplicatedColumn ? 8 : 7}>
{visibleTypes.length === 0 ? (
<>
<p className="text-sm text-foreground">
@@ -420,7 +429,7 @@ export const TableList = ({
)}
{entities.length === 0 && filterString.length > 0 && (
<TableRow key={selectedSchema}>
<TableCell colSpan={7}>
<TableCell colSpan={showReplicatedColumn ? 8 : 7}>
<p className="text-sm text-foreground">No results found</p>
<p className="text-sm text-foreground-light">
Your search for "{filterString}" did not return any results
@@ -489,6 +498,21 @@ export const TableList = ({
<p className="text-foreground-muted">–</p>
)}
</TableCell>
{showReplicatedColumn && (
<TableCell>
{isTableReplicated(ref, x.schema, x.name) ? (
<div className="flex items-center gap-x-2">
<Check size={16} strokeWidth={2} className="text-brand-link" />
<p className="text-foreground-light">Yes</p>
</div>
) : (
<div className="flex items-center gap-x-2">
<X size={16} strokeWidth={2} className="text-foreground-muted" />
<p className="text-foreground-lighter">No</p>
</div>
)}
</TableCell>
)}
<TableCell>
{(realtimePublication?.tables ?? []).find(
(table) => table.id === x.id
@@ -0,0 +1,79 @@
# Warehouse API contract (planned)
> **Prototype note:** Studio wholesale branch uses `warehouseDemoStore` only. Endpoints below are the target platform contract; not implemented in this branch.
## Model
- **Project-wide** enable/disable (not per-table hero API)
- Separate **`warehouse.*` host** with **1:1 schema.table names**
- Managed publication: `supabase_warehouse`
- Disable **deletes** all Warehouse replicas; re-enable triggers full backfill
## Endpoints (planned)
### `POST /platform/projects/{ref}/warehouse/enable`
Enable Warehouse for the project.
**Request body:**
```json
{
"included_schemas": ["public", "storage"]
}
```
**Response:**
```json
{
"enabled": true,
"replication_phase": "provisioning",
"pipeline_id": "7022",
"warehouse_host": "warehouse.<ref>.supabase.co",
"publication_name": "supabase_warehouse",
"included_schemas": ["public", "storage"]
}
```
### `PATCH /platform/projects/{ref}/warehouse/schemas`
Update which schemas are replicated.
**Request body:**
```json
{
"included_schemas": ["public"]
}
```
Adding schemas triggers backfill for new tables. Removing schemas drops Warehouse replicas for those schemas.
### `DELETE /platform/projects/{ref}/warehouse`
Disable Warehouse and delete all replicas.
**Response:** `204 No Content`
### `GET /platform/projects/{ref}/warehouse`
Project Warehouse status (phase, lag, included schemas, pipeline metadata).
## Table replicated check (Studio)
A table is **replicated** when:
1. Project Warehouse is enabled
2. Table's schema is in `included_schemas`
3. Table is not on the system denylist (e.g. `supabase_migrations`)
Studio table list **Replicated** column is read-only metadata derived from this.
## SQL query target (Studio)
Studio SQL Editor target toggle (`postgres` | `warehouse`) is a **query routing hint** for the execution layer. Wholesale prototype does not route queries; it shows `Served by: Warehouse` in the results footer when Warehouse is selected.
## Config (out of Studio scope)
Notion simplification referenced `[warehouse] enabled = true` in `config.toml`. Studio wholesale MVP does not edit config; enable/disable is project-scoped in the Replication UI.
@@ -0,0 +1,75 @@
# Warehouse product naming
Studio copy for Warehouse uses **replicate** as the shared verb and **qualified nouns** for the destination. The goal is one replication mental model across destinations (Warehouse, read replicas, external pipelines) without teaching another product concept.
## Principles
1. **Verb stays lean** — `replicate`, `replicating`, `stop replicating`
2. **Noun carries the destination** — always qualify: **Warehouse replica**, **read replica**
3. **Never bare "replica"** in user-facing UI without a destination prefix
4. **Avoid loaded terms** — do not use `link`, `linked`, `copy`, or `mirror` in product copy (platform API types may still use them)
## Endpoint model (wholesale MVP)
Warehouse uses a **separate host** with **identical schema and table names**:
| Postgres (transactional) | Warehouse (analytical) |
| ---------------------------- | ----------------------------------- |
| `public.events` | `public.events` |
| Host: `db.<ref>.supabase.co` | Host: `warehouse.<ref>.supabase.co` |
Do **not** use `public_warehouse.events` or other renamed schemas in user-facing copy. That was a pre-pivot exploration.
## User-facing vocabulary
| Concept | Use | Avoid |
| -------------------- | --------------------------------------------------------- | ------------------------------------- |
| Project action | **Enable Warehouse** | Turn on Warehouse, Link project |
| In progress | **Replicating** / **Backfilling** | Syncing, Linking |
| Steady state | **Streaming** / **Active** | Replica live (unless emphasis needed) |
| The analytical table | **Warehouse replica** (`public.events` on Warehouse host) | Linked table, Warehouse copy |
| Ongoing process | **Warehouse replication** | Warehouse link, sync |
| Remove | **Disable Warehouse** | Unlink, Detach, Delete copy |
| Table list column | **Replicated** (generic; not Warehouse-branded) | Warehouse column, Linked |
| Postgres product | **Read replica** | Replica (alone) |
## Status labels
| Internal phase | UI label (Destinations) | UI label (card chip) |
| -------------- | ----------------------- | -------------------- |
| `provisioning` | Starting | Starting |
| `backfilling` | Backfilling | Backfilling |
| `streaming` | Active | Active |
| `failed` | Failed | Failed |
## Example copy
| Surface | Copy |
| ---------------------- | --------------------------------------------------- |
| Enable CTA | Enable Warehouse |
| Enable dialog title | Enable Warehouse |
| Disable confirm title | Disable Warehouse? |
| Disable confirm body | This deletes all Warehouse replicas |
| Replication card (off) | Analytical replica on a separate Warehouse endpoint |
| SQL Editor toggle | Postgres \| Warehouse |
| SQL Editor footer | Served by: Warehouse |
| Connect tab | Warehouse |
## Internal code (not user-facing)
| Internal | Meaning |
| -------------------- | ------------------------------- |
| `includedSchemas` | Schemas replicated to Warehouse |
| `replicationPhase` | Project pipeline phase |
| `publicationName` | `supabase_warehouse` (managed) |
| `warehouseDemoStore` | Prototype mock state |
See `WAREHOUSE_API_CONTRACT.md` for planned platform endpoints.
## Terms we rejected
| Term | Why |
| --------------------------- | --------------------------------------------------- |
| **Link / linked** | Reads like foreign keys or ETL pipeline config |
| **Copy** | Implies a one-off snapshot, not ongoing replication |
| **public_warehouse schema** | Pre-pivot; replaced by 1:1 endpoint mapping |
@@ -0,0 +1,49 @@
# Warehouse Wholesale MVP
Branch: `dnywh/prototype/warehouse-wholesale`
## Intent
Project-wide Warehouse prototype for Studio. Postgres remains the transactional database; Warehouse is an analytical replica on a **separate `warehouse.*` host** with **1:1 schema and table names** (`public.events` on both).
All platform behavior is mocked via `warehouseDemoStore` (localStorage). No real API endpoints.
## In scope
- **Replication page** — `WarehouseProjectCard` (enable/disable, schema scope, observability)
- **Destinations table** — read-only managed row (`DuckLake` / `supabase_warehouse`)
- **Table list** — read-only **Replicated** column
- **SQL Editor** — Postgres / Warehouse target toggle + mock "Served by: Warehouse" footer
- **Connect sheet** — Warehouse tab with connection credentials when enabled
- **Feature flag** — `useIsWarehouseEnabled` (ConfigCat `warehouse` org slug allowlist)
## Out of scope
- Per-table enablement hero UX
- `?view=warehouse`, table-detail storage tab
- `public_warehouse.*` FDW naming (pre-pivot model)
- Catalog integration install gate
- Real query routing to Warehouse
- config.toml editing in Studio
## Demo store
State key: `supabase-warehouse-wholesale-demo` in localStorage, keyed by project ref.
```ts
// apps/studio/components/interfaces/Database/Warehouse/warehouseDemoStore.ts
enableWarehouseProject(projectRef, includedSchemas)
disableWarehouseProject(projectRef)
updateWarehouseIncludedSchemas(projectRef, schemas)
isTableReplicated(projectRef, schema, table)
```
Enable simulates `provisioning` → `backfilling` → `streaming` phase progression.
## QA checklist
1. Flag off → no Warehouse UI
2. Flag on, disabled → Replication card with Enable CTA only
3. Enable → observability metrics, schema checkboxes, Destinations row, Replicated column, SQL toggle, Connect Warehouse tab
4. Uncheck schema → tables in that schema show Replicated = No
5. Disable → confirm destructive modal, all Warehouse UI hidden
@@ -0,0 +1,64 @@
import { useParams } from 'common'
import { Database, Minus } from 'lucide-react'
import { TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { MANAGED_WAREHOUSE_PUBLICATION_TOOLTIP } from './managedWarehouse.resources'
import { useWarehouseProjectState } from './warehouseDemoStore'
import {
formatReplicationLagSeconds,
getWarehouseDestinationStatusLabel,
} from './warehouseReplication.utils'
import { WarehouseSyncChip } from './WarehouseSyncChip'
export function WarehouseDestinationRow() {
const { ref: projectRef } = useParams()
const state = useWarehouseProjectState(projectRef)
if (!state.enabled || !state.pipelineId) return null
const statusLabel = getWarehouseDestinationStatusLabel(state.replicationPhase)
const lag = formatReplicationLagSeconds(state.lagSeconds)
const isCaughtUp = state.lagSeconds === 0 || state.lagSeconds === 2
return (
<TableRow>
<TableCell>
<Database size={18} className="text-foreground-light" />
</TableCell>
<TableCell className="max-w-[180px]">
<div>
<p>DuckLake (Pipeline ID: {state.pipelineId})</p>
<p className="text-foreground-lighter">{state.destinationName}</p>
</div>
</TableCell>
<TableCell>
<WarehouseSyncChip phase={state.replicationPhase} />
{statusLabel === 'Failed' && (
<p className="text-xs text-foreground-lighter mt-1">Managed Warehouse pipeline</p>
)}
</TableCell>
<TableCell>
{state.replicationPhase === 'streaming' || state.replicationPhase === 'backfilling' ? (
isCaughtUp ? (
<p className="text-foreground-light">Caught up</p>
) : (
<p className="text-foreground-light">{lag}</p>
)
) : (
<Minus size={18} className="text-foreground-lighter" />
)}
</TableCell>
<TableCell>
<Tooltip>
<TooltipTrigger asChild>
<span className="text-foreground-light">{state.publicationName}</span>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
{MANAGED_WAREHOUSE_PUBLICATION_TOOLTIP}
</TooltipContent>
</Tooltip>
</TableCell>
<TableCell />
</TableRow>
)
}
@@ -0,0 +1,62 @@
import { useParams } from 'common'
import { useState } from 'react'
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { disableWarehouseProject } from './warehouseDemoStore'
interface WarehouseDisableModalProps {
open: boolean
onOpenChange: (open: boolean) => void
}
export function WarehouseDisableModal({ open, onOpenChange }: WarehouseDisableModalProps) {
const { ref: projectRef } = useParams()
const [isDisabling, setIsDisabling] = useState(false)
const onDisable = async () => {
if (!projectRef) return
setIsDisabling(true)
disableWarehouseProject(projectRef)
setIsDisabling(false)
onOpenChange(false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent size="medium">
<DialogHeader>
<DialogTitle>Disable Warehouse?</DialogTitle>
</DialogHeader>
<DialogSection>
<p className="text-sm text-foreground-light mb-4">
This deletes all Warehouse replicas for this project.
</p>
<Admonition
type="destructive"
title="Warehouse data will be deleted"
description="Disabling Warehouse removes all replicated tables from the Warehouse endpoint. Re-enabling requires a full backfill."
/>
</DialogSection>
<DialogSectionSeparator />
<DialogFooter>
<Button variant="default" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button variant="danger" loading={isDisabling} onClick={onDisable}>
Disable Warehouse
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
@@ -0,0 +1,103 @@
import { useParams } from 'common'
import { useEffect, useState } from 'react'
import {
Button,
Checkbox,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
Label,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { enableWarehouseProject } from './warehouseDemoStore'
import { useDefaultWarehouseSchemas } from './WarehouseSchemaScope'
interface WarehouseEnableModalProps {
open: boolean
onOpenChange: (open: boolean) => void
}
export function WarehouseEnableModal({ open, onOpenChange }: WarehouseEnableModalProps) {
const { ref: projectRef } = useParams()
const defaultSchemas = useDefaultWarehouseSchemas()
const [selectedSchemas, setSelectedSchemas] = useState<string[]>(defaultSchemas)
const [isEnabling, setIsEnabling] = useState(false)
useEffect(() => {
if (open) setSelectedSchemas(defaultSchemas)
}, [open, defaultSchemas])
const selectedSet = new Set(selectedSchemas)
const onToggle = (schema: string, checked: boolean) => {
setSelectedSchemas((prev) => (checked ? [...prev, schema] : prev.filter((s) => s !== schema)))
}
const onEnable = async () => {
if (!projectRef || selectedSchemas.length === 0) return
setIsEnabling(true)
enableWarehouseProject(projectRef, selectedSchemas)
setIsEnabling(false)
onOpenChange(false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent size="medium">
<DialogHeader>
<DialogTitle>Enable Warehouse</DialogTitle>
</DialogHeader>
<DialogSection>
<p className="text-sm text-foreground-light">
Create an analytical replica of your Postgres data on a separate Warehouse endpoint.
Tables use the same schema and table names (for example, <code>public.events</code>).
</p>
<p className="text-sm text-foreground-light mt-3">
Select which schemas to replicate. You can change this later from the Warehouse card.
</p>
<div className="flex flex-col gap-y-2 max-h-64 overflow-y-auto mt-4">
{defaultSchemas.map((schema) => (
<FormItemLayout
key={schema}
isReactForm={false}
layout="flex"
label={schema}
className="items-center"
>
<Checkbox
id={`enable-warehouse-schema-${schema}`}
checked={selectedSet.has(schema)}
onCheckedChange={(checked) => onToggle(schema, checked === true)}
/>
<Label htmlFor={`enable-warehouse-schema-${schema}`} className="sr-only">
Include schema {schema}
</Label>
</FormItemLayout>
))}
</div>
<Button
variant="default"
onClick={() => setSelectedSchemas(defaultSchemas)}
className="mt-3"
>
Select all
</Button>
</DialogSection>
<DialogSectionSeparator />
<DialogFooter>
<Button variant="default" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button loading={isEnabling} disabled={selectedSchemas.length === 0} onClick={onEnable}>
Enable Warehouse
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
@@ -0,0 +1,50 @@
import { useParams } from 'common'
import { Card, CardContent } from 'ui'
import { ChartMetric } from 'ui-patterns/Chart'
import { useWarehouseProjectState } from './warehouseDemoStore'
import { formatReplicationLagSeconds, formatReplicationPhase } from './warehouseReplication.utils'
import { WarehouseSyncChip } from './WarehouseSyncChip'
export function WarehouseObservabilityPanel() {
const { ref: projectRef } = useParams()
const state = useWarehouseProjectState(projectRef)
if (!state.enabled) return null
return (
<Card>
<CardContent className="p-0">
<div className="grid grid-cols-1 sm:grid-cols-3 divide-y sm:divide-y-0 sm:divide-x">
<div className="px-4 py-3">
<ChartMetric
label="Replication phase"
value={formatReplicationPhase(state.replicationPhase)}
tooltip="Project-wide Warehouse pipeline phase."
className="[&_span]:text-sm"
/>
<div className="mt-2">
<WarehouseSyncChip phase={state.replicationPhase} />
</div>
</div>
<div className="px-4 py-3">
<ChartMetric
label="Replication lag"
value={formatReplicationLagSeconds(state.lagSeconds)}
tooltip="Approximate lag between Postgres and Warehouse."
className="[&_span]:text-sm"
/>
</div>
<div className="px-4 py-3">
<ChartMetric
label="Replicated schemas"
value={state.includedSchemas.length.toLocaleString()}
tooltip="Schemas currently replicated to Warehouse."
className="[&_span]:text-sm"
/>
</div>
</div>
</CardContent>
</Card>
)
}
@@ -0,0 +1,84 @@
import { useParams } from 'common'
import { parseAsBoolean, useQueryState } from 'nuqs'
import { useState } from 'react'
import { Button, Card, CardContent } from 'ui'
import { useWarehouseProjectState } from './warehouseDemoStore'
import { WarehouseDisableModal } from './WarehouseDisableModal'
import { WarehouseEnableModal } from './WarehouseEnableModal'
import { WarehouseObservabilityPanel } from './WarehouseObservabilityPanel'
import { WarehouseSchemaScope } from './WarehouseSchemaScope'
import { WarehouseSyncChip } from './WarehouseSyncChip'
import { useIsWarehouseEnabled } from '@/hooks/misc/useIsWarehouseEnabled'
export function WarehouseProjectCard() {
const { ref: projectRef } = useParams()
const isWarehouseFeatureEnabled = useIsWarehouseEnabled()
const warehouseState = useWarehouseProjectState(projectRef)
const [showEnableModal, setShowEnableModal] = useState(false)
const [showDisableModal, setShowDisableModal] = useState(false)
const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false))
const [, setConnectTab] = useQueryState('connectTab')
if (!isWarehouseFeatureEnabled) return null
return (
<>
<Card className="mb-6">
<CardContent className="p-6 flex flex-col gap-y-4">
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
<div className="flex flex-col gap-y-1">
<div className="flex items-center gap-x-2">
<h4 className="text-foreground text-base">Warehouse</h4>
{warehouseState.enabled && (
<WarehouseSyncChip phase={warehouseState.replicationPhase} />
)}
</div>
<p className="text-sm text-foreground-light max-w-2xl">
{warehouseState.enabled
? 'Analytical replica of your Postgres data on a separate Warehouse endpoint. Query the same schema and table names against the Warehouse host.'
: 'Enable an analytical replica of your Postgres data on a separate Warehouse endpoint for analytics workloads.'}
</p>
</div>
<div className="flex items-center gap-x-2 shrink-0">
{warehouseState.enabled ? (
<>
<Button
variant="default"
onClick={() => {
setConnectTab('warehouse')
setShowConnect(true)
}}
>
Connect
</Button>
<Button variant="danger" onClick={() => setShowDisableModal(true)}>
Disable Warehouse
</Button>
</>
) : (
<Button onClick={() => setShowEnableModal(true)}>Enable Warehouse</Button>
)}
</div>
</div>
{warehouseState.enabled && (
<>
<WarehouseObservabilityPanel />
<WarehouseSchemaScope
disabled={
warehouseState.replicationPhase === 'provisioning' ||
warehouseState.replicationPhase === 'backfilling'
}
/>
</>
)}
</CardContent>
</Card>
<WarehouseEnableModal open={showEnableModal} onOpenChange={setShowEnableModal} />
<WarehouseDisableModal open={showDisableModal} onOpenChange={setShowDisableModal} />
</>
)
}
@@ -0,0 +1,85 @@
import { useParams } from 'common'
import { useMemo } from 'react'
import { Checkbox, Label } from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import {
getDefaultIncludedSchemas,
isReplicableSchema,
updateWarehouseIncludedSchemas,
useWarehouseProjectState,
} from './warehouseDemoStore'
import { useSchemasQuery } from '@/data/database/schemas-query'
interface WarehouseSchemaScopeProps {
disabled?: boolean
}
export function WarehouseSchemaScope({ disabled = false }: WarehouseSchemaScopeProps) {
const { ref: projectRef } = useParams()
const { data: schemas = [] } = useSchemasQuery({ projectRef })
const warehouseState = useWarehouseProjectState(projectRef)
const replicableSchemas = useMemo(
() =>
schemas
.map((s) => s.name)
.filter(isReplicableSchema)
.sort(),
[schemas]
)
const includedSet = new Set(warehouseState.includedSchemas)
const onToggle = (schema: string, checked: boolean) => {
if (!projectRef) return
const next = checked
? [...warehouseState.includedSchemas, schema]
: warehouseState.includedSchemas.filter((s) => s !== schema)
updateWarehouseIncludedSchemas(projectRef, next)
}
if (!warehouseState.enabled) return null
return (
<div className="flex flex-col gap-y-3">
<div>
<p className="text-sm text-foreground">Replicated schemas</p>
<p className="text-sm text-foreground-light">
Tables in selected schemas are replicated to Warehouse with the same schema and table
names.
</p>
</div>
<div className="flex flex-col gap-y-2">
{replicableSchemas.map((schema) => (
<FormItemLayout
key={schema}
isReactForm={false}
layout="flex"
label={schema}
className="items-center"
>
<Checkbox
id={`warehouse-schema-${schema}`}
checked={includedSet.has(schema)}
disabled={disabled}
onCheckedChange={(checked) => onToggle(schema, checked === true)}
/>
<Label htmlFor={`warehouse-schema-${schema}`} className="sr-only">
Replicate schema {schema}
</Label>
</FormItemLayout>
))}
{replicableSchemas.length === 0 && (
<p className="text-sm text-foreground-light">No replicable schemas found.</p>
)}
</div>
</div>
)
}
export function useDefaultWarehouseSchemas(): string[] {
const { ref: projectRef } = useParams()
const { data: schemas = [] } = useSchemasQuery({ projectRef })
return useMemo(() => getDefaultIncludedSchemas(schemas.map((s) => s.name)), [schemas])
}
@@ -0,0 +1,23 @@
import { Badge } from 'ui'
import type { ReplicationPhase } from './warehouseDemoStore'
import { getWarehouseDestinationStatusLabel } from './warehouseReplication.utils'
interface WarehouseSyncChipProps {
phase: ReplicationPhase
}
export function WarehouseSyncChip({ phase }: WarehouseSyncChipProps) {
const label = getWarehouseDestinationStatusLabel(phase)
const variant =
phase === 'failed'
? 'destructive'
: phase === 'backfilling' || phase === 'provisioning'
? 'warning'
: phase === 'streaming'
? 'success'
: 'default'
return <Badge variant={variant}>{label}</Badge>
}
@@ -0,0 +1,8 @@
export const SUPABASE_MANAGED_WAREHOUSE_RESOURCE_NAME = 'supabase_warehouse'
export function isSupabaseManagedWarehousePublicationName(name: string): boolean {
return name === SUPABASE_MANAGED_WAREHOUSE_RESOURCE_NAME
}
export const MANAGED_WAREHOUSE_PUBLICATION_TOOLTIP =
'Managed by Supabase for Warehouse replication. Configure schemas on the Warehouse card above.'
@@ -0,0 +1,185 @@
import { proxy, subscribe, useSnapshot } from 'valtio'
import { SUPABASE_MANAGED_WAREHOUSE_RESOURCE_NAME } from './managedWarehouse.resources'
import { getMockWarehouseHost } from './warehouseReplication.utils'
import { INTERNAL_SCHEMAS } from '@/hooks/useProtectedSchemas'
export type ReplicationPhase = 'idle' | 'provisioning' | 'backfilling' | 'streaming' | 'failed'
export interface WarehouseProjectState {
enabled: boolean
replicationPhase: ReplicationPhase
lagSeconds: number | null
includedSchemas: string[]
pipelineId: string | null
destinationName: string
publicationName: string
warehouseHost: string
enabledAt: string | null
}
const DEFAULT_STATE: WarehouseProjectState = {
enabled: false,
replicationPhase: 'idle',
lagSeconds: null,
includedSchemas: [],
pipelineId: null,
destinationName: 'DuckLake',
publicationName: SUPABASE_MANAGED_WAREHOUSE_RESOURCE_NAME,
warehouseHost: '',
enabledAt: null,
}
const STORAGE_KEY = 'supabase-warehouse-wholesale-demo'
export const warehouseDemoStore = proxy<{
projects: Record<string, WarehouseProjectState>
}>({
projects: {},
})
function loadFromStorage(): void {
if (typeof window === 'undefined') return
try {
const raw = window.localStorage.getItem(STORAGE_KEY)
if (!raw) return
const parsed = JSON.parse(raw) as Record<string, WarehouseProjectState>
warehouseDemoStore.projects = parsed
} catch {
// ignore corrupt storage
}
}
function saveToStorage(): void {
if (typeof window === 'undefined') return
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(warehouseDemoStore.projects))
}
loadFromStorage()
subscribe(warehouseDemoStore, saveToStorage)
const SYSTEM_TABLE_DENYLIST = new Set(['supabase_migrations', 'schema_migrations'])
const NON_REPLICABLE_SCHEMAS = new Set(INTERNAL_SCHEMAS)
export function isReplicableSchema(schema: string): boolean {
return !NON_REPLICABLE_SCHEMAS.has(schema)
}
export function getDefaultIncludedSchemas(availableSchemas: string[]): string[] {
return availableSchemas.filter(isReplicableSchema)
}
function getProjectState(projectRef: string): WarehouseProjectState {
return warehouseDemoStore.projects[projectRef] ?? { ...DEFAULT_STATE }
}
function setProjectState(projectRef: string, next: WarehouseProjectState): void {
warehouseDemoStore.projects[projectRef] = next
}
export function useWarehouseProjectState(projectRef: string | undefined) {
const snap = useSnapshot(warehouseDemoStore)
if (!projectRef) return { ...DEFAULT_STATE }
return snap.projects[projectRef] ?? { ...DEFAULT_STATE }
}
export function isWarehouseProjectEnabled(projectRef: string | undefined): boolean {
if (!projectRef) return false
return getProjectState(projectRef).enabled
}
export function isTableReplicated(
projectRef: string | undefined,
schema: string,
table: string
): boolean {
if (!projectRef) return false
const state = getProjectState(projectRef)
if (!state.enabled) return false
if (!state.includedSchemas.includes(schema)) return false
if (SYSTEM_TABLE_DENYLIST.has(table)) return false
return isReplicableSchema(schema)
}
let phaseTimer: ReturnType<typeof setTimeout> | null = null
function clearPhaseTimer(): void {
if (phaseTimer) {
clearTimeout(phaseTimer)
phaseTimer = null
}
}
function simulatePhaseProgression(projectRef: string): void {
clearPhaseTimer()
phaseTimer = setTimeout(() => {
const current = getProjectState(projectRef)
if (!current.enabled || current.replicationPhase !== 'provisioning') return
setProjectState(projectRef, {
...current,
replicationPhase: 'backfilling',
lagSeconds: 45,
})
phaseTimer = setTimeout(() => {
const backfilling = getProjectState(projectRef)
if (!backfilling.enabled || backfilling.replicationPhase !== 'backfilling') return
setProjectState(projectRef, {
...backfilling,
replicationPhase: 'streaming',
lagSeconds: 2,
})
}, 6000)
}, 3000)
}
export function enableWarehouseProject(projectRef: string, includedSchemas: string[]): void {
const pipelineId = String(7000 + Math.floor(Math.random() * 100))
setProjectState(projectRef, {
enabled: true,
replicationPhase: 'provisioning',
lagSeconds: null,
includedSchemas,
pipelineId,
destinationName: 'DuckLake',
publicationName: SUPABASE_MANAGED_WAREHOUSE_RESOURCE_NAME,
warehouseHost: getMockWarehouseHost(projectRef),
enabledAt: new Date().toISOString(),
})
simulatePhaseProgression(projectRef)
}
export function disableWarehouseProject(projectRef: string): void {
clearPhaseTimer()
setProjectState(projectRef, { ...DEFAULT_STATE })
}
export function updateWarehouseIncludedSchemas(
projectRef: string,
includedSchemas: string[]
): void {
const current = getProjectState(projectRef)
if (!current.enabled) return
const hadNewSchemas = includedSchemas.some((s) => !current.includedSchemas.includes(s))
setProjectState(projectRef, {
...current,
includedSchemas,
replicationPhase: hadNewSchemas ? 'backfilling' : current.replicationPhase,
lagSeconds: hadNewSchemas ? 30 : current.lagSeconds,
})
if (hadNewSchemas) {
phaseTimer = setTimeout(() => {
const state = getProjectState(projectRef)
if (!state.enabled) return
setProjectState(projectRef, {
...state,
replicationPhase: 'streaming',
lagSeconds: 2,
})
}, 4000)
}
}
@@ -0,0 +1,62 @@
import type { ReplicationPhase, WarehouseProjectState } from './warehouseDemoStore'
export function formatReplicationPhase(phase: ReplicationPhase): string {
switch (phase) {
case 'idle':
return 'Not enabled'
case 'provisioning':
return 'Provisioning'
case 'backfilling':
return 'Backfilling'
case 'streaming':
return 'Streaming'
case 'failed':
return 'Error'
default:
return phase
}
}
export function formatReplicationLagSeconds(lagSeconds: number | null): string {
if (lagSeconds === null) return '—'
if (lagSeconds === 0) return 'Caught up'
if (lagSeconds < 60) return `${lagSeconds}s`
if (lagSeconds < 3600) return `${Math.round(lagSeconds / 60)}m`
return `${(lagSeconds / 3600).toFixed(1)}h`
}
export function getWarehouseDestinationStatusLabel(phase: ReplicationPhase): string {
switch (phase) {
case 'provisioning':
return 'Starting'
case 'backfilling':
return 'Backfilling'
case 'streaming':
return 'Active'
case 'failed':
return 'Failed'
default:
return 'Unknown'
}
}
export function getMockWarehouseHost(projectRef: string): string {
return `warehouse.${projectRef}.supabase.co`
}
export function buildWarehouseConnectionString({
projectRef,
databaseName = 'postgres',
port = 5432,
}: {
projectRef: string
databaseName?: string
port?: number
}): string {
const host = getMockWarehouseHost(projectRef)
return `postgresql://postgres.[YOUR-PASSWORD]@${host}:${port}/${databaseName}`
}
export function isWarehouseReplicationHealthy(state: WarehouseProjectState): boolean {
return state.enabled && state.replicationPhase === 'streaming' && (state.lagSeconds ?? 0) < 120
}
@@ -56,6 +56,7 @@ import {
isUpdateWithoutWhere,
suffixWithLimit,
} from './SQLEditor.utils'
import { useSqlQueryTarget } from './SqlWarehouseTargetToggle'
import { useAddDefinitions } from './useAddDefinitions'
import { UtilityPanel } from './UtilityPanel/UtilityPanel'
import {
@@ -129,6 +130,7 @@ export const SQLEditor = () => {
const getImpersonatedRoleState = useGetImpersonatedRoleState()
const databaseSelectorState = useDatabaseSelectorStateSnapshot()
const { aiOptInLevel } = useOrgAiOptInLevel()
const sqlQueryTarget = useSqlQueryTarget()
// [Ali] Kill switch to hide the SQL Editor Explain tab and its entry points
const disablePrettyExplain = useFlag('DisablePrettyExplainOnSqlEditor')
@@ -1063,31 +1065,36 @@ export const SQLEditor = () => {
<div className="h-9">
{results?.rows !== undefined && !isExecuting && (
<GridFooter className="flex items-center justify-between gap-2">
<Tooltip>
<TooltipTrigger>
<p className="text-xs">
<span className="text-foreground">
{results.rows.length} row{results.rows.length > 1 ? 's' : ''}
</span>
<span className="text-foreground-lighter ml-1">
{results.autoLimit !== undefined &&
` (Limited to only ${results.autoLimit} rows)`}
</span>
</p>
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<p className="flex flex-col gap-y-1">
<span>
Results are automatically limited to preserve browser performance, in
particular if your query returns an exceptionally large number of rows.
</span>
<div className="flex items-center gap-3">
<Tooltip>
<TooltipTrigger>
<p className="text-xs">
<span className="text-foreground">
{results.rows.length} row{results.rows.length > 1 ? 's' : ''}
</span>
<span className="text-foreground-lighter ml-1">
{results.autoLimit !== undefined &&
` (Limited to only ${results.autoLimit} rows)`}
</span>
</p>
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<p className="flex flex-col gap-y-1">
<span>
Results are automatically limited to preserve browser performance, in
particular if your query returns an exceptionally large number of rows.
</span>
<span className="text-foreground-light">
You may change or remove this limit from the dropdown on the right
</span>
</p>
</TooltipContent>
</Tooltip>
<span className="text-foreground-light">
You may change or remove this limit from the dropdown on the right
</span>
</p>
</TooltipContent>
</Tooltip>
{sqlQueryTarget === 'warehouse' && (
<p className="text-xs text-foreground-light">Served by: Warehouse</p>
)}
</div>
{results.autoLimit !== undefined && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -0,0 +1,70 @@
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { cn } from 'ui'
import { isWarehouseProjectEnabled } from '@/components/interfaces/Database/Warehouse/warehouseDemoStore'
import { useIsWarehouseEnabled } from '@/hooks/misc/useIsWarehouseEnabled'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
export type SqlQueryTarget = 'postgres' | 'warehouse'
interface SqlWarehouseTargetToggleProps {
className?: string
}
export function SqlWarehouseTargetToggle({ className }: SqlWarehouseTargetToggleProps) {
const { ref: projectRef } = useParams()
const isWarehouseFeatureEnabled = useIsWarehouseEnabled()
const warehouseEnabled = isWarehouseProjectEnabled(projectRef)
const [queryTarget, setQueryTarget] = useLocalStorageQuery<SqlQueryTarget>(
LOCAL_STORAGE_KEYS.SQL_EDITOR_QUERY_TARGET(projectRef as string),
'postgres'
)
if (!isWarehouseFeatureEnabled || !warehouseEnabled) return null
return (
<div
className={cn(
'flex items-center rounded-md border border-overlay bg-surface-200 p-0.5 text-xs',
className
)}
role="group"
aria-label="SQL query target"
>
<button
type="button"
className={cn(
'rounded px-2 py-1 transition-colors',
queryTarget === 'postgres'
? 'bg-surface-100 text-foreground shadow-sm'
: 'text-foreground-light hover:text-foreground'
)}
onClick={() => setQueryTarget('postgres')}
>
Postgres
</button>
<button
type="button"
className={cn(
'rounded px-2 py-1 transition-colors',
queryTarget === 'warehouse'
? 'bg-surface-100 text-foreground shadow-sm'
: 'text-foreground-light hover:text-foreground'
)}
onClick={() => setQueryTarget('warehouse')}
>
Warehouse
</button>
</div>
)
}
export function useSqlQueryTarget(): SqlQueryTarget {
const { ref: projectRef } = useParams()
const [queryTarget] = useLocalStorageQuery<SqlQueryTarget>(
LOCAL_STORAGE_KEYS.SQL_EDITOR_QUERY_TARGET(projectRef as string),
'postgres'
)
return queryTarget
}
@@ -16,6 +16,7 @@ import {
TooltipTrigger,
} from 'ui'
import { SqlWarehouseTargetToggle } from '../SqlWarehouseTargetToggle'
import { AutosaveStatus } from './AutosaveStatus'
import { SqlRunButton } from './RunButton'
import { SqlSaveButton } from './SaveButton'
@@ -212,6 +213,7 @@ export const UtilityActions = ({
</div>
<div className="flex items-center gap-x-2">
<SqlWarehouseTargetToggle />
<div className="flex items-center">
{IS_PLATFORM && (
<DatabaseSelector
@@ -0,0 +1,22 @@
import { useFlag } from 'common'
import { useSelectedOrganizationQuery } from './useSelectedOrganization'
/**
* Whether the Warehouse product is enabled for the current session.
*
* Gates every Warehouse surface while in preview. Backed by the `warehouse`
* ConfigCat flag, configured as a comma-separated organization slug allowlist.
* Use `all` to enable every organization.
*/
export function useIsWarehouseEnabled(): boolean {
const flagValue = useFlag<string>('warehouse')
const { data: organization } = useSelectedOrganizationQuery()
const allowedOrgSlugs =
typeof flagValue === 'string' ? flagValue.split(',').map((slug) => slug.trim()) : []
if (allowedOrgSlugs.includes('all')) return true
return allowedOrgSlugs.includes(organization?.slug ?? '')
}
@@ -2,6 +2,7 @@ import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { Destinations } from '@/components/interfaces/Database/Replication/Destinations'
import { ReplicationDiagram } from '@/components/interfaces/Database/Replication/ReplicationDiagram'
import { WarehouseProjectCard } from '@/components/interfaces/Database/Warehouse/WarehouseProjectCard'
import DatabaseLayout from '@/components/layouts/DatabaseLayout/DatabaseLayout'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold'
@@ -55,6 +56,11 @@ const DatabaseReplicationPage: NextPageWithLayout = () => {
</ScaffoldContainer>
) : (
<>
<ScaffoldContainer>
<ScaffoldSection isFullWidth>
<WarehouseProjectCard />
</ScaffoldSection>
</ScaffoldContainer>
<ReplicationDiagram />
<ScaffoldContainer>
<ScaffoldSection isFullWidth className="pt-6!">
@@ -47,6 +47,7 @@ export const LOCAL_STORAGE_KEYS = {
SQL_EDITOR_AI_SCHEMA: (ref: string) => `supabase_sql-editor-ai-schema-enabled-${ref}`,
SQL_EDITOR_AI_OPEN: 'supabase_sql-editor-ai-open',
SQL_EDITOR_LAST_SELECTED_DB: (ref: string) => `sql-editor-last-selected-db-${ref}`,
SQL_EDITOR_QUERY_TARGET: (ref: string) => `sql-editor-query-target-${ref}`,
SQL_EDITOR_SQL_BLOCK_ACKNOWLEDGED: (ref: string) => `sql-editor-sql-block-acknowledged-${ref}`,
SQL_EDITOR_SECTION_STATE: (ref: string) => `sql-editor-section-state-${ref}`,
SQL_EDITOR_SORT: (ref: string) => `sql-editor-sort-${ref}`,
+1 -1
View File
@@ -2099,7 +2099,7 @@ export interface HomeConnectActionClickedEvent {
/**
* The connect action/tile that was clicked
*/
mode: 'framework' | 'direct' | 'orm' | 'mcp' | 'server' | 'api_keys'
mode: 'framework' | 'direct' | 'orm' | 'mcp' | 'server' | 'warehouse' | 'api_keys'
}
groups: TelemetryGroups
}