import { useQueryClient } from '@tanstack/react-query' import { useParams } from 'common' import { MoreVertical, Plus, Search, Workflow, X } from 'lucide-react' import Link from 'next/link' import { parseAsStringEnum, useQueryState } from 'nuqs' import { useEffect, useMemo, useRef, useState } from 'react' import { Button, Card, CardContent, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { DestinationPanel } from './DestinationPanel/DestinationPanel' import { DestinationType } from './DestinationPanel/DestinationPanel.types' import { DestinationRow } from './DestinationRow' import { DisablePipelinesDialog } from './DisablePipelinesDialog' import { EnablePipelinesModal } from './EnablePipelinesCallout' import { useIsETLBigQueryPrivateAlpha, useIsETLClickHousePrivateAlpha, useIsETLDucklakePrivateAlpha, useIsETLIcebergPrivateAlpha, useIsETLSnowflakePrivateAlpha, } from './useIsETLPrivateAlpha' import { useRedirectLegacyReadReplicaDestination } from './useRedirectLegacyReadReplicaDestination' import { AlertError } from '@/components/ui/AlertError' import { DocsButton } from '@/components/ui/DocsButton' import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip' import { Shortcut } from '@/components/ui/Shortcut' import { useReplicationDestinationsQuery } from '@/data/replication/destinations-query' import { replicationKeys } from '@/data/replication/keys' import { fetchReplicationPipelineVersion } from '@/data/replication/pipeline-version-query' import { useReplicationPipelinesQuery } from '@/data/replication/pipelines-query' import { useReplicationSourcesQuery } from '@/data/replication/sources-query' import { checkLocalETLNotSetUp } from '@/data/replication/utils' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { DOCS_URL } from '@/lib/constants' import { onSearchInputEscape } from '@/lib/keyboard' import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import { useShortcut } from '@/state/shortcuts/useShortcut' export const Destinations = () => { const queryClient = useQueryClient() const { ref: projectRef } = useParams() const { data: organization } = useSelectedOrganizationQuery() useRedirectLegacyReadReplicaDestination() const etlEnableBigQuery = useIsETLBigQueryPrivateAlpha() const etlEnableIceberg = useIsETLIcebergPrivateAlpha() const etlEnableDucklake = useIsETLDucklakePrivateAlpha() const etlEnableSnowflake = useIsETLSnowflakePrivateAlpha() const etlEnableClickHouse = useIsETLClickHousePrivateAlpha() const newDestinationDefaultType: DestinationType | null = etlEnableBigQuery ? 'BigQuery' : etlEnableIceberg ? 'Analytics Bucket' : etlEnableDucklake ? 'DuckLake' : etlEnableSnowflake ? 'Snowflake' : etlEnableClickHouse ? 'ClickHouse' : null const prefetchedRef = useRef(false) const searchInputRef = useRef(null) const [filterString, setFilterString] = useState('') const [showEnablePipelinesDialog, setShowEnablePipelinesDialog] = useState(false) const [showDisablePipelinesDialog, setShowDisablePipelinesDialog] = useState(false) const [, setDestinationType] = useQueryState( 'destinationType', parseAsStringEnum([ 'BigQuery', 'Analytics Bucket', 'DuckLake', 'Snowflake', 'ClickHouse', ]).withOptions({ history: 'push', clearOnDefault: true, }) ) const { data: destinationsData, error: destinationsError, isPending: isDestinationsLoading, isError: isDestinationsError, isSuccess: isDestinationsSuccess, } = useReplicationDestinationsQuery({ projectRef, }) const destinations = destinationsData?.destinations ?? [] const hasDestinations = isDestinationsSuccess && destinationsData?.destinations.length > 0 const filteredDestinations = filterString.length === 0 ? (destinations ?? []) : (destinations ?? []).filter((destination) => destination.name.toLowerCase().includes(filterString.toLowerCase()) ) const { data: pipelinesData, isSuccess: isPipelinesSuccess } = useReplicationPipelinesQuery({ projectRef, }) const pipelines = pipelinesData?.pipelines ?? [] const { data: sourcesData, isSuccess: isSourcesSuccess } = useReplicationSourcesQuery({ projectRef, }) const externalReplicationSource = useMemo( () => sourcesData?.sources.find((source) => source.name === projectRef), [projectRef, sourcesData?.sources] ) const replicationNotEnabled = isSourcesSuccess && !externalReplicationSource const canDisablePipelines = isSourcesSuccess && isDestinationsSuccess && isPipelinesSuccess && !!externalReplicationSource && destinations.length === 0 && pipelines.length === 0 const isLoading = isDestinationsLoading const isLocalETLNotSetUp = checkLocalETLNotSetUp(destinationsError) const hasErrorsFetchingData = !isLocalETLNotSetUp && isDestinationsError const openDestinationPanel = () => { if (!newDestinationDefaultType) return setDestinationType(newDestinationDefaultType) } useShortcut( SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH, () => { searchInputRef.current?.focus() searchInputRef.current?.select() }, { label: 'Search destinations' } ) useShortcut(SHORTCUT_IDS.LIST_PAGE_RESET_FILTERS, () => setFilterString('')) useEffect(() => { if ( projectRef && !prefetchedRef.current && pipelinesData?.pipelines && pipelinesData.pipelines.length > 0 && isPipelinesSuccess ) { prefetchedRef.current = true pipelinesData.pipelines.forEach((p) => { if (!p?.id) return queryClient.prefetchQuery({ queryKey: replicationKeys.pipelinesVersion(projectRef, p.id), queryFn: ({ signal }) => fetchReplicationPipelineVersion({ projectRef, pipelineId: p.id }, signal), staleTime: Infinity, }) }) } }, [projectRef, pipelinesData?.pipelines, isPipelinesSuccess, queryClient]) return (
} value={filterString} className="w-full lg:w-52" onChange={(e) => setFilterString(e.target.value)} onKeyDown={onSearchInputEscape(filterString, setFilterString)} actions={ filterString.length > 0 && (
{hasErrorsFetchingData && ( )} {isLoading ? ( ) : hasDestinations ? ( Name Status Lag Publication {filteredDestinations.map((destination) => ( ))} {!isLoading && filteredDestinations.length === 0 && hasDestinations && (

No results found

Your search for "{filterString}" did not return any results.

)}
) : ( !isLoading && !hasErrorsFetchingData && ( ) )}
) }