Files
supabase/apps/studio/components/interfaces/Database/Replication/EnablePipelinesCallout.tsx
Danny White 6c829b32da fix(studio): enable Pipelines before opening creation (#51311)
## Problem

Add pipeline opens the creation sheet before users enable Pipelines. A
pending or failed source lookup also lets creation open with an unknown
enablement state.

## Solution

Show the existing enablement dialog first when required, then open
creation after successful enablement. Cancellation and failed enablement
keep creation closed; enabling through the page menu does not open
creation.

Disable both Add pipeline buttons and their keyboard shortcut until the
source lookup succeeds. Failed lookups show an error with Retry,
including the local replication configuration message. Analytics Bucket
keeps its existing creation path.

| Before | After |
| --- | --- |
| <img width="1275" height="919" alt="Pipelines Database Shears Toolshed
Supabase"
src="https://github.com/user-attachments/assets/cf328732-4342-4758-bde2-98b393341d6a"
/> | _No longer in sheet; dialog is shown conditionally before sheet._ |
| <img width="1275" height="919" alt="Pipelines Database Shears Toolshed
Supabase"
src="https://github.com/user-attachments/assets/f593e271-d29c-4955-8849-0bb64946d4cc"
/> | <img width="1275" height="919" alt="Pipelines Database Shears
Toolshed Supabase"
src="https://github.com/user-attachments/assets/3257d391-b892-44df-85d1-5a2108072312"
/>|
| _“Enable Pipelines”_ | _“Enable”_ |

| After |
| --- |
| <img width="1275" height="919" alt="Pipelines Database Shears Toolshed
Supabase"
src="https://github.com/user-attachments/assets/b11770a1-4785-49ee-950d-d821892b3245"
/> |
| _Loading_ |
| <img width="1275" height="919" alt="Pipelines Database Shears Toolshed
Supabase"
src="https://github.com/user-attachments/assets/710f82be-ca6f-4337-a5ce-b65f9d7f6a32"
/> |
| _Lookup failed_ |
| <img width="1275" height="919" alt="Pipelines Database Shears Toolshed
Supabase"
src="https://github.com/user-attachments/assets/68486ca8-37e8-4b27-89ae-8004f159c38a"
/> |
| _Plan-access loading, throttled_ |

## Review instructions

Use a project with Pipelines access and a working replication API (which
should work on [deploy
preview](https://studio-staging-git-dnywh-fixpipeline-enable-create-supabase.vercel.app/)).
Test the PR preview or locally
([instructions](https://app.notion.com/p/supabase/Danny-s-Local-ETL-Pipelines-Setup-3b25004b775f8058a108f8f67fc813e9?source=copy_link)).
In DevTools Network, enable **Disable cache** before each reload.
Analytics Bucket intentionally bypasses the source-status guard.

1. **Loading:** select **Slow 3G**, reload, and watch the request ending
in `/replication/<ref>/sources`. While it is pending, both **Add
pipeline** buttons must be disabled and **Shift+N** must open nothing.
Both buttons replace the plus with a loading spinner and have no loading
tooltip. Restore **No throttling** afterwards.
2. **Lookup failed:** right-click that source request and choose **Block
request URL**, then reload. After retries finish, expect **Failed to
retrieve pipeline enablement status** with **Retry**, disabled Add
buttons whose tooltip matches the error title, and no sheet/dialog from
**Shift+N**. An unconfigured local replication API instead shows
**Replication unavailable locally**. Unblock the request before clicking
**Retry**.
3. **Lookup succeeded:** on a disposable project with Pipelines
disabled, successful Retry restores Add pipeline. Clicking it opens
**Enable Pipelines**. Cancel stays on the list; **Enable** opens the
sheet after successful enablement. On an already enabled project, Add
pipeline opens the sheet directly. Enabling through the page's three-dot
menu stays on the list. Analytics Bucket opens its sheet without ETL
enablement.
4. **Plan-access loading is separate:** in Chrome 145 or newer, find
`/organizations/<slug>/entitlements` in Network, right-click it and
choose **Throttle request**. In the **Request conditions** drawer,
select Slow 3G for that request only, leaving global throttling off.
Reload on a Pro organisation with Pipelines disabled and open Add
pipeline after the source lookup succeeds. While entitlements remain
pending, expect body shimmers, the accessible “Checking Pipelines
access…” status, a disabled loading **Enable** button, and no upgrade
prompt. Remove the request condition afterwards. If your DevTools lacks
per-request throttling, use the component tests for deterministic
coverage.
2026-10-08 10:42:39 +11:00

146 lines
4.8 KiB
TypeScript

import { useParams } from 'common'
import { useState } from 'react'
import { toast } from 'sonner'
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
DialogTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { DestinationType } from './DestinationPanel/DestinationPanel.types'
import { InlineLink } from '@/components/ui/InlineLink'
import { UpgradePlanButton } from '@/components/ui/UpgradePlanButton'
import { useCreateTenantSourceMutation } from '@/data/replication/create-tenant-source-mutation'
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
import { DOCS_URL } from '@/lib/constants'
type EnablePipelinesModalProps =
| { open: boolean; onOpenChange: (open: boolean) => void; onSuccess?: () => void }
| { open?: never; onOpenChange?: never; onSuccess?: () => void }
export const EnablePipelinesModal = ({
open: extOpen,
onOpenChange,
onSuccess,
}: EnablePipelinesModalProps) => {
const { ref: projectRef } = useParams()
const [_open, _setOpen] = useState(false)
const open = extOpen ?? _open
const setOpen = onOpenChange ?? _setOpen
const hideTrigger = extOpen !== undefined && onOpenChange !== undefined
const { hasAccess, isLoading } = useCheckEntitlements('replication.etl')
const { mutate: createTenantSource, isPending: creatingTenantSource } =
useCreateTenantSourceMutation({
onSuccess: () => {
toast.success('Pipelines enabled')
onSuccess?.()
setOpen(false)
},
onError: (error) => {
toast.error(`Failed to enable Pipelines: ${error.message}`)
},
})
const onEnablePipelines = async () => {
if (isLoading || !hasAccess) return
if (!projectRef) return console.error('Project ref is required')
createTenantSource({ projectRef })
}
return (
<Dialog open={open} onOpenChange={setOpen}>
{!hideTrigger && (
<DialogTrigger asChild>
<Button variant="primary">Enable</Button>
</DialogTrigger>
)}
<DialogContent size="small">
<DialogHeader>
<DialogTitle>Enable Pipelines</DialogTitle>
</DialogHeader>
<DialogSectionSeparator />
<DialogSection className="flex flex-col gap-y-3">
<p role="status" aria-live="polite" className="sr-only">
{isLoading ? 'Checking Pipelines access…' : ''}
</p>
{isLoading && (
<div aria-hidden="true">
<GenericSkeletonLoader />
</div>
)}
{!isLoading && hasAccess && (
<>
<p className="text-sm text-foreground-light">
Pipelines bills for configured pipeline hours and Postgres row data processed during
initial sync and ongoing replication. Review{' '}
<InlineLink href={`${DOCS_URL}/guides/platform/manage-your-usage/pipelines`}>
Pipelines pricing
</InlineLink>{' '}
before enabling.
</p>
<p className="text-sm text-foreground-light">
Pipelines is in public alpha and may change.
</p>
</>
)}
{!isLoading && !hasAccess && (
<p className="text-sm text-foreground-light">Pipelines requires the Pro plan.</p>
)}
</DialogSection>
<DialogFooter>
<Button disabled={creatingTenantSource} onClick={() => setOpen(false)}>
Cancel
</Button>
{isLoading || hasAccess ? (
<Button
variant="primary"
loading={isLoading || creatingTenantSource}
disabled={isLoading}
onClick={onEnablePipelines}
>
Enable
</Button>
) : (
<UpgradePlanButton source="replication" featureProposition="use replication" />
)}
</DialogFooter>
</DialogContent>
</Dialog>
)
}
export const EnablePipelinesCallout = ({ type }: { type?: DestinationType | null }) => {
const { hasAccess } = useCheckEntitlements('replication.etl')
return (
<Admonition
type="note"
layout="responsive"
title={hasAccess ? 'Enable Pipelines' : 'Upgrade to Pro for Pipelines'}
description={
hasAccess
? `Pipelines must be enabled before this project can replicate database changes to ${type ?? 'external destinations'}.`
: `The Pro plan is required to replicate database changes to ${type ?? 'external destinations'} with Pipelines.`
}
actions={
hasAccess ? (
<EnablePipelinesModal />
) : (
<UpgradePlanButton source="replication" featureProposition="use replication" />
)
}
/>
)
}