mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
## 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 | | --- | --- | | <img width="1024" height="759" alt="Pipelines Database Sandals Field Lab Supabase" src="https://github.com/user-attachments/assets/fac744dd-2701-40ae-a65d-0801a1a86e6f" /> | <img width="1024" height="759" alt="Pipelines Database Sandals Field Lab Supabase" src="https://github.com/user-attachments/assets/075bf609-5b65-40ca-8aaf-d27335d48838" /> | | <img width="1024" height="759" alt="18436" src="https://github.com/user-attachments/assets/92f8c63b-f9b9-4dd6-b9b6-26ede932c172" /> | <img width="1076" height="759" alt="93893" src="https://github.com/user-attachments/assets/3c6f26b0-130c-4508-bf6b-d2c641805426" /> | | <img width="1024" height="759" alt="16935" src="https://github.com/user-attachments/assets/63e75045-412a-4bcc-9cf9-20245ac60871" /> | <img width="1024" height="759" alt="75021" src="https://github.com/user-attachments/assets/09771e1e-2e89-466d-8f60-22c5bacc9956" /> | ## 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) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
228 lines
8.0 KiB
TypeScript
228 lines
8.0 KiB
TypeScript
import { useFeatureFlags, useParams } from 'common'
|
|
import { ArrowUpRight } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { parseAsInteger, parseAsStringEnum, useQueryState } from 'nuqs'
|
|
import { useEffect, useRef } from 'react'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Button,
|
|
DialogSectionSeparator,
|
|
Sheet,
|
|
SheetContent,
|
|
SheetDescription,
|
|
SheetHeader,
|
|
SheetSection,
|
|
SheetTitle,
|
|
} from 'ui'
|
|
import { Admonition } from 'ui-patterns/Admonition'
|
|
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
import { EnablePipelinesCallout } from '../EnablePipelinesCallout'
|
|
import { PipelineStatusName } from '../Replication.constants'
|
|
import { useDestinationInformation } from '../useDestinationInformation'
|
|
import { useIsETLPrivateAlpha } from '../useIsETLPrivateAlpha'
|
|
import { DestinationForm } from './DestinationForm'
|
|
import { DestinationType } from './DestinationPanel.types'
|
|
import { DestinationTypeSelection } from './DestinationTypeSelection'
|
|
import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
|
|
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, IS_PLATFORM } from '@/lib/constants'
|
|
|
|
const DESTINATION_DOCS_PATHS: Partial<Record<DestinationType, string>> = {
|
|
BigQuery: '/guides/database/replication/pipelines/bigquery#configure-bigquery-as-a-destination',
|
|
ClickHouse:
|
|
'/guides/database/replication/pipelines/clickhouse#configure-clickhouse-as-a-destination',
|
|
DuckLake: '/guides/database/replication/pipelines/ducklake#choose-a-configuration-mode',
|
|
Snowflake: '/guides/database/replication/pipelines/snowflake#prepare-snowflake-resources',
|
|
}
|
|
|
|
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)
|
|
|
|
const [urlDestinationType, setDestinationType] = useQueryState(
|
|
'destinationType',
|
|
parseAsStringEnum<DestinationType>([
|
|
'BigQuery',
|
|
'Analytics Bucket',
|
|
'DuckLake',
|
|
'Snowflake',
|
|
'ClickHouse',
|
|
]).withOptions({
|
|
history: 'push',
|
|
clearOnDefault: true,
|
|
})
|
|
)
|
|
|
|
const [edit, setEdit] = useQueryState(
|
|
'edit',
|
|
parseAsInteger.withOptions({
|
|
history: 'push',
|
|
clearOnDefault: true,
|
|
})
|
|
)
|
|
|
|
const visible = urlDestinationType !== null || edit !== null
|
|
const editMode = edit !== null
|
|
|
|
const {
|
|
sourceId,
|
|
pipeline,
|
|
statusName,
|
|
replicationNotEnabled,
|
|
type: existingDestinationType,
|
|
destinationFetcher,
|
|
} = 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
|
|
? {
|
|
sourceId,
|
|
destinationId: edit,
|
|
pipelineId: pipeline?.id,
|
|
statusName,
|
|
enabled:
|
|
statusName === PipelineStatusName.STARTED || statusName === PipelineStatusName.FAILED,
|
|
}
|
|
: undefined
|
|
|
|
const checkIsDirtyRef = useRef<() => boolean>(() => false)
|
|
|
|
const onClose = () => {
|
|
checkIsDirtyRef.current = () => false
|
|
setDestinationType(null)
|
|
setEdit(null)
|
|
}
|
|
|
|
const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({
|
|
checkIsDirty: () => checkIsDirtyRef.current(),
|
|
onClose,
|
|
})
|
|
|
|
const docsUrl = `${DOCS_URL}${
|
|
(destinationType && DESTINATION_DOCS_PATHS[destinationType]) ??
|
|
'/guides/database/replication/pipelines#step-3-configure-a-destination'
|
|
}`
|
|
|
|
useEffect(() => {
|
|
if (edit !== null && invalidExistingDestination) {
|
|
toast(`Unable to find destination ID ${edit}`)
|
|
setEdit(null)
|
|
}
|
|
}, [edit, invalidExistingDestination, setEdit])
|
|
|
|
const typeSelection = (
|
|
<>
|
|
<DestinationTypeSelection />
|
|
<DialogSectionSeparator />
|
|
</>
|
|
)
|
|
|
|
const pipelinesTypeSelection = (
|
|
<>
|
|
{typeSelection}
|
|
{destinationType != null && isLocalETLNotSetUp && (
|
|
<SheetSection className="pb-0!">
|
|
<Admonition
|
|
type="warning"
|
|
title="Replication unavailable locally"
|
|
description="Configure the replication API to manage pipelines in local development."
|
|
/>
|
|
</SheetSection>
|
|
)}
|
|
</>
|
|
)
|
|
|
|
return (
|
|
<>
|
|
<Sheet open={visible} onOpenChange={handleOpenChange}>
|
|
<SheetContent size="lg" showClose={false} className="max-w-3xl">
|
|
<div className="flex flex-col h-full min-h-0" tabIndex={-1}>
|
|
<SheetHeader className="flex items-center justify-between">
|
|
<div>
|
|
<SheetTitle>{editMode ? 'Edit pipeline' : 'Add pipeline'}</SheetTitle>
|
|
<SheetDescription>
|
|
{editMode
|
|
? 'Update how this pipeline sends data to its destination.'
|
|
: 'Send tables to an external destination for analytics workloads.'}
|
|
</SheetDescription>
|
|
</div>
|
|
<DocsButton
|
|
href={docsUrl}
|
|
topic={`${destinationType ?? 'destination'} pipeline settings`}
|
|
/>
|
|
</SheetHeader>
|
|
|
|
{isAccessLoading && (
|
|
<SheetSection>
|
|
<GenericSkeletonLoader />
|
|
</SheetSection>
|
|
)}
|
|
{showAccessRequest && (
|
|
<div className="grow overflow-auto min-h-0">
|
|
{pipelinesTypeSelection}
|
|
<SheetSection>
|
|
<Admonition
|
|
type="note"
|
|
layout="responsive"
|
|
title="Request Pipelines access"
|
|
description="Pipelines is in public alpha and available to approved organizations."
|
|
actions={
|
|
<Button
|
|
asChild
|
|
variant="primary"
|
|
iconRight={<ArrowUpRight size={16} strokeWidth={1.5} />}
|
|
>
|
|
<Link
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
href="https://forms.supabase.com/pg_replicate"
|
|
>
|
|
Request access
|
|
</Link>
|
|
</Button>
|
|
}
|
|
/>
|
|
</SheetSection>
|
|
</div>
|
|
)}
|
|
{showEnablement && (
|
|
<div className="grow overflow-auto min-h-0">
|
|
{pipelinesTypeSelection}
|
|
<SheetSection>
|
|
<EnablePipelinesCallout type={destinationType} />
|
|
</SheetSection>
|
|
</div>
|
|
)}
|
|
{showDestinationForm && (
|
|
<DestinationForm
|
|
visible={visible}
|
|
selectedType={destinationType ?? 'BigQuery'}
|
|
existingDestination={existingDestination}
|
|
typeSelection={pipelinesTypeSelection}
|
|
checkIsDirtyRef={checkIsDirtyRef}
|
|
onClose={onClose}
|
|
onCancel={confirmOnClose}
|
|
/>
|
|
)}
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
<DiscardChangesConfirmationDialog {...modalProps} />
|
|
</>
|
|
)
|
|
}
|