From c856de8fda294533da196c731b6be20f8c4a5f57 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:34:10 +1000 Subject: [PATCH] fix(studio): polish pipeline creation copy and actions (#50956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The current pipeline creation sheet describes destination type with two disconnected sentences. The enablement callout repeats itself, while three Pipelines action menus have undersized triggers. ## Solution Use a complete destination-specific description for each release stage. Present the Enable Pipelines callout as a note with an action title, a short explanation, and one button. Clarify the enablement dialog copy and align the three action-menu triggers with a consistent button width and hit area. | Before | After | | --- | --- | | Pipelines Database Sandals Field
Lab Supabase | Pipelines Database Sandals
Field Lab Supabase | | 18436 | 93893 | | 16935 | 75021 | ## Review instructions 1. Open [Database > Pipelines in the Studio preview](https://studio-staging-git-dnywh-fixpipelines-creation-polish-supabase.vercel.app/dashboard/project/_/database/pipelines), click **Add pipeline**, and select BigQuery or Snowflake. Confirm the Type description reads as a complete sentence. 2. On a project where Pipelines is not enabled, open the creation sheet. Confirm the note has no extra padding or Docs link, then click **Enable** and check the dialog copy. The list menu's **Enable Pipelines** action opens the same dialog. 3. Check the action-menu triggers on the Pipelines list, pipeline detail page, and table row for consistent sizing and click targets. ## Checklist - [x] I have read [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) ## Summary by CodeRabbit * **User Experience** * Replication setup now displays a loading state while access and organization details are checked, then shows the appropriate access request, enablement notice, or destination form. * Access notices use clearer, responsive messaging with a primary “Request access” link. * Enablement messaging now reflects whether your plan includes access, and the success notification is shorter. * Destination-type notices now identify the selected type and clarify that it cannot be changed after creation. --- .../DestinationPanel/DestinationPanel.tsx | 52 ++++++++----- .../DestinationTypeSelection.test.tsx | 6 +- .../DestinationTypeSelection.tsx | 17 ++-- .../Database/Replication/Destinations.tsx | 2 +- .../Replication/EnablePipelinesCallout.tsx | 78 ++++++++----------- .../Replication/ReplicationPipelineLayout.tsx | 2 +- .../Database/Replication/RowMenu.tsx | 2 +- 7 files changed, 82 insertions(+), 77 deletions(-) diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx index 15ca208b799..a78e8182c1f 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx @@ -1,4 +1,4 @@ -import { useParams } from 'common' +import { useFeatureFlags, useParams } from 'common' import { ArrowUpRight } from 'lucide-react' import Link from 'next/link' import { parseAsInteger, parseAsStringEnum, useQueryState } from 'nuqs' @@ -6,7 +6,6 @@ import { useEffect, useRef } from 'react' import { toast } from 'sonner' import { Button, - cn, DialogSectionSeparator, Sheet, SheetContent, @@ -16,6 +15,7 @@ import { SheetTitle, } from 'ui' import { Admonition } from 'ui-patterns/Admonition' +import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { EnablePipelinesCallout } from '../EnablePipelinesCallout' import { PipelineStatusName } from '../Replication.constants' @@ -28,8 +28,9 @@ import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialo import { DocsButton } from '@/components/ui/DocsButton' import { useReplicationDestinationsQuery } from '@/data/replication/destinations-query' import { checkLocalETLNotSetUp } from '@/data/replication/utils' +import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useConfirmOnClose } from '@/hooks/ui/useConfirmOnClose' -import { DOCS_URL } from '@/lib/constants' +import { DOCS_URL, IS_PLATFORM } from '@/lib/constants' const DESTINATION_DOCS_PATHS: Partial> = { BigQuery: '/guides/database/replication/pipelines/bigquery#configure-bigquery-as-a-destination', @@ -41,7 +42,10 @@ const DESTINATION_DOCS_PATHS: Partial> = { export const DestinationPanel = () => { const { ref: projectRef } = useParams() + const { hasLoaded: flagsLoaded } = useFeatureFlags() + const { isPending: isOrganizationPending } = useSelectedOrganizationQuery() const enablePgReplicate = useIsETLPrivateAlpha() + const isAccessLoading = IS_PLATFORM && (!flagsLoaded || isOrganizationPending) const { error: destinationsError } = useReplicationDestinationsQuery({ projectRef }) const isLocalETLNotSetUp = checkLocalETLNotSetUp(destinationsError) @@ -80,6 +84,9 @@ export const DestinationPanel = () => { } = useDestinationInformation({ id: edit }) const destinationType = existingDestinationType ?? urlDestinationType const invalidExistingDestination = destinationFetcher.error?.code === 404 + const showAccessRequest = !isAccessLoading && !enablePgReplicate + const showEnablement = !isAccessLoading && enablePgReplicate && replicationNotEnabled + const showDestinationForm = !isAccessLoading && enablePgReplicate && !replicationNotEnabled const existingDestination = editMode ? { @@ -159,22 +166,24 @@ export const DestinationPanel = () => { /> - {!enablePgReplicate ? ( + {isAccessLoading && ( + + + + )} + {showAccessRequest && (
{pipelinesTypeSelection} -
-
-

Request Pipelines access

-

- Pipelines is in public alpha and - being rolled out gradually. Request access below to join the waitlist. -

-
-
+ } > { rel="noreferrer" href="https://forms.supabase.com/pg_replicate" > - Request Pipelines access + Request access - -
-
+ } + />
- ) : replicationNotEnabled ? ( + )} + {showEnablement && (
{pipelinesTypeSelection} - +
- ) : ( + )} + {showDestinationForm && ( { fireEvent.click(await screen.findByRole('combobox')) fireEvent.click(await screen.findByText('BigQuery')) - expect(await screen.findByText(/In public alpha and may change/)).toBeInTheDocument() + expect( + await screen.findByText( + 'Destination type cannot be changed after creation. BigQuery support is in public alpha.' + ) + ).toBeInTheDocument() }) test('disables the selector in edit mode so the destination type cannot be changed', async () => { diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx index f7c2eeb0279..d8ee413d9dd 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx @@ -127,18 +127,23 @@ export const DestinationTypeSelection = () => { const selectedOption = options.find((option) => option.value === destinationType) - const STAGE_DESCRIPTIONS: Record, string> = { - 'Public Alpha': 'In public alpha and may change.', - 'Early Access': 'In early access and may change.', - Deprecated: 'This destination type is deprecated.', + const STAGE_DESCRIPTIONS: Record< + NonNullable, + (type: DestinationType) => string + > = { + 'Public Alpha': (type) => `${type} support is in public alpha.`, + 'Early Access': (type) => `${type} support is in early access.`, + Deprecated: (type) => `${type} is deprecated.`, } - const stageDescription = selectedOption?.stage ? STAGE_DESCRIPTIONS[selectedOption.stage] : null + const stageDescription = selectedOption?.stage + ? STAGE_DESCRIPTIONS[selectedOption.stage](selectedOption.value) + : null const typeDescription = !editMode || stageDescription ? ( - {!editMode && 'Cannot be changed after creation.'} + {!editMode && 'Destination type cannot be changed after creation.'} {!editMode && stageDescription ? ' ' : null} {stageDescription} diff --git a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx index 1516ae4b5aa..a65f24d008b 100644 --- a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx +++ b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx @@ -300,7 +300,7 @@ export const Destinations = () => { aria-label="More actions" variant="default" icon={} - className="px-1.25" + className="w-6.5 hit-area-1" /> diff --git a/apps/studio/components/interfaces/Database/Replication/EnablePipelinesCallout.tsx b/apps/studio/components/interfaces/Database/Replication/EnablePipelinesCallout.tsx index 3a0085b9c24..a1485e9d76e 100644 --- a/apps/studio/components/interfaces/Database/Replication/EnablePipelinesCallout.tsx +++ b/apps/studio/components/interfaces/Database/Replication/EnablePipelinesCallout.tsx @@ -3,10 +3,8 @@ import { useState } from 'react' import { toast } from 'sonner' import { Button, - cn, Dialog, DialogContent, - DialogDescription, DialogFooter, DialogHeader, DialogSection, @@ -14,9 +12,9 @@ import { DialogTitle, DialogTrigger, } from 'ui' +import { Admonition } from 'ui-patterns/Admonition' import { DestinationType } from './DestinationPanel/DestinationPanel.types' -import { DocsButton } from '@/components/ui/DocsButton' import { InlineLink } from '@/components/ui/InlineLink' import { UpgradePlanButton } from '@/components/ui/UpgradePlanButton' import { useCreateTenantSourceMutation } from '@/data/replication/create-tenant-source-mutation' @@ -43,7 +41,7 @@ export const EnablePipelinesModal = ({ const { mutate: createTenantSource, isPending: creatingTenantSource } = useCreateTenantSourceMutation({ onSuccess: () => { - toast.success('Pipelines has been successfully enabled!') + toast.success('Pipelines enabled') setOpen(false) }, onError: (error) => { @@ -60,38 +58,33 @@ export const EnablePipelinesModal = ({ {!hideTrigger && ( - + )} Enable Pipelines - - {hasAccess - ? 'Enabling creates Pipelines resources for this project.' - : 'Upgrade to the Pro plan to replicate database changes to data warehouses and analytics platforms.'} - - {hasAccess && ( - <> - - + + + {hasAccess ? ( + <>

- Pipelines is in public alpha and may change as we refine it. -

-

- You’ll be billed for configured pipeline hours and for Postgres row data processed - during initial sync and ongoing replication. Review{' '} + Pipelines bills for configured pipeline hours and Postgres row data processed during + initial sync and ongoing replication. Review{' '} Pipelines pricing {' '} before enabling.

-
- - )} +

+ Pipelines is in public alpha and may change. +

+ + ) : ( +

Pipelines requires the Pro plan.

+ )} +