1- import { snakeCase } from 'lodash'
2- import { MoreVertical , Pause , Play } from 'lucide-react'
1+ import { snakeCase , uniq } from 'lodash'
2+ import { MoreVertical , Pause , Play , Trash } from 'lucide-react'
33import Link from 'next/link'
44import { useState } from 'react'
55import { toast } from 'sonner'
66
77import { useParams } from 'common'
8+ import {
9+ convertKVStringArrayToJson ,
10+ formatWrapperTables ,
11+ } from 'components/interfaces/Integrations/Wrappers/Wrappers.utils'
12+ import { getDecryptedParameters } from 'components/interfaces/Storage/ImportForeignSchemaDialog.utils'
13+ import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip'
814import { useUpdatePublicationMutation } from 'data/etl/publication-update-mutation'
915import { useStartPipelineMutation } from 'data/etl/start-pipeline-mutation'
1016import { useReplicationTablesQuery } from 'data/etl/tables-query'
17+ import { useFDWUpdateMutation } from 'data/fdw/fdw-update-mutation'
18+ import { useIcebergNamespaceTableDeleteMutation } from 'data/storage/iceberg-namespace-table-delete-mutation'
1119import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
1220import { SqlEditor } from 'icons'
1321import {
@@ -21,37 +29,52 @@ import {
2129 TableRow ,
2230} from 'ui'
2331import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
32+ import { getAnalyticsBucketFDWServerName } from '../AnalyticsBucketDetails.utils'
2433import { useAnalyticsBucketAssociatedEntities } from '../useAnalyticsBucketAssociatedEntities'
34+ import { useAnalyticsBucketWrapperInstance } from '../useAnalyticsBucketWrapperInstance'
35+
36+ interface TableRowComponentProps {
37+ index : number
38+ table : { id : number ; name : string ; isConnected : boolean }
39+ schema : string
40+ namespace : string
41+ token : string
42+ isLoading ?: boolean
43+ }
2544
2645export const TableRowComponent = ( {
2746 index,
2847 table,
2948 schema,
49+ namespace,
50+ token,
3051 isLoading,
31- } : {
32- index : number
33- table : { id : number ; name : string ; isConnected : boolean }
34- schema : string
35- isLoading ?: boolean
36- } ) => {
52+ } : TableRowComponentProps ) => {
3753 const { ref : projectRef , bucketId } = useParams ( )
3854 const { data : project } = useSelectedProjectQuery ( )
3955
4056 const [ showStopReplicationModal , setShowStopReplicationModal ] = useState ( false )
4157 const [ showStartReplicationModal , setShowStartReplicationModal ] = useState ( false )
58+ const [ showRemoveTableModal , setShowRemoveTableModal ] = useState ( false )
4259 const [ isUpdatingReplication , setIsUpdatingReplication ] = useState ( false )
60+ const [ isRemovingTable , setIsRemovingTable ] = useState ( false )
4361
4462 const { sourceId, publication, pipeline } = useAnalyticsBucketAssociatedEntities ( {
4563 projectRef,
4664 bucketId,
4765 } )
4866
4967 const { data : tables } = useReplicationTablesQuery ( { projectRef, sourceId } )
68+ const { data : wrapperInstance , meta : wrapperMeta } = useAnalyticsBucketWrapperInstance ( {
69+ bucketId : bucketId ,
70+ } )
5071
72+ const { mutateAsync : updateFDW } = useFDWUpdateMutation ( )
73+ const { mutateAsync : deleteNamespaceTable } = useIcebergNamespaceTableDeleteMutation ( )
5174 const { mutateAsync : updatePublication } = useUpdatePublicationMutation ( )
5275 const { mutateAsync : startPipeline } = useStartPipelineMutation ( )
5376
54- const isReplicating = publication ?. tables . find (
77+ const isReplicating = ! ! publication ?. tables . find (
5578 ( x ) => table . name === snakeCase ( `${ x . schema } .${ x . name } _changelog` )
5679 )
5780
@@ -117,6 +140,66 @@ export const TableRowComponent = ({
117140 }
118141 }
119142
143+ const onConfirmRemoveTable = async ( ) => {
144+ if ( ! bucketId ) return console . error ( 'Bucket ID is required' )
145+ if ( ! wrapperInstance || ! wrapperMeta ) return toast . error ( 'Unable to find wrapper' )
146+
147+ try {
148+ setIsRemovingTable ( true )
149+
150+ const serverName = getAnalyticsBucketFDWServerName ( bucketId )
151+ const serverOptions = await getDecryptedParameters ( {
152+ ref : project ?. ref ,
153+ connectionString : project ?. connectionString ?? undefined ,
154+ wrapper : wrapperInstance ,
155+ } )
156+ const formValues : Record < string , string > = {
157+ wrapper_name : wrapperInstance . name ,
158+ server_name : wrapperInstance . server_name ,
159+ ...serverOptions ,
160+ }
161+ const targetSchemas = ( formValues [ 'supabase_target_schema' ] || '' )
162+ . split ( ',' )
163+ . map ( ( s ) => s . trim ( ) )
164+ const wrapperTables = formatWrapperTables ( wrapperInstance , wrapperMeta ) . filter (
165+ ( x ) => x . table_name !== table . name
166+ )
167+
168+ // [Joshen] Once Ivan's PR goes through, swap these out to just use useFDWDropForeignTableMutation
169+ // https://github.com/supabase/supabase/pull/40206
170+ await updateFDW ( {
171+ projectRef : project ?. ref ,
172+ connectionString : project ?. connectionString ,
173+ wrapper : wrapperInstance ,
174+ wrapperMeta,
175+ formState : {
176+ ...formValues ,
177+ server_name : serverName ,
178+ supabase_target_schema : uniq ( [ ...targetSchemas ] )
179+ . filter ( Boolean )
180+ . join ( ',' ) ,
181+ } ,
182+ tables : wrapperTables ,
183+ } )
184+
185+ const wrapperValues = convertKVStringArrayToJson ( wrapperInstance ?. server_options ?? [ ] )
186+ await deleteNamespaceTable ( {
187+ token,
188+ catalogUri : wrapperValues . catalog_uri ,
189+ warehouse : wrapperValues . warehouse ,
190+ namespace : namespace ,
191+ table : table . name ,
192+ } )
193+
194+ toast . success ( 'Successfully removed table!' )
195+ setShowRemoveTableModal ( false )
196+ } catch ( error : any ) {
197+ toast . error ( `Failed to remove table: ${ error . message } ` )
198+ } finally {
199+ setIsRemovingTable ( false )
200+ }
201+ }
202+
120203 return (
121204 < >
122205 < TableRow >
@@ -180,8 +263,6 @@ export const TableRowComponent = ({
180263 </ Link >
181264 </ DropdownMenuItem >
182265
183- < DropdownMenuSeparator />
184-
185266 { isReplicating ? (
186267 < DropdownMenuItem
187268 className = "flex items-center gap-x-2"
@@ -199,6 +280,23 @@ export const TableRowComponent = ({
199280 < p > Start replication</ p >
200281 </ DropdownMenuItem >
201282 ) }
283+
284+ < DropdownMenuSeparator />
285+
286+ < DropdownMenuItemTooltip
287+ disabled = { isReplicating }
288+ className = "flex items-center gap-x-2"
289+ onClick = { ( ) => setShowRemoveTableModal ( true ) }
290+ tooltip = { {
291+ content : {
292+ side : 'left' ,
293+ text : 'Stop replication on this table before removing' ,
294+ } ,
295+ } }
296+ >
297+ < Trash size = { 12 } className = "text-foreground-lighter" />
298+ < p > Remove table</ p >
299+ </ DropdownMenuItemTooltip >
202300 </ DropdownMenuContent >
203301 </ DropdownMenu >
204302 </ >
@@ -220,6 +318,7 @@ export const TableRowComponent = ({
220318 restarting replication on the table will clear and re-sync all data in it. Are you sure?
221319 </ p >
222320 </ ConfirmationModal >
321+
223322 < ConfirmationModal
224323 size = "medium"
225324 variant = "warning"
@@ -235,6 +334,20 @@ export const TableRowComponent = ({
235334 Are you sure?
236335 </ p >
237336 </ ConfirmationModal >
337+
338+ < ConfirmationModal
339+ size = "small"
340+ variant = "warning"
341+ visible = { showRemoveTableModal }
342+ loading = { isRemovingTable }
343+ title = "Confirm to remove table"
344+ description = "Data from the analytics table will be lost"
345+ confirmLabel = "Remove table"
346+ onCancel = { ( ) => setShowRemoveTableModal ( false ) }
347+ onConfirm = { ( ) => onConfirmRemoveTable ( ) }
348+ >
349+ < p className = "text-sm text-foreground-light" > Are you sure? This action cannot be undone.</ p >
350+ </ ConfirmationModal >
238351 </ >
239352 )
240353}
0 commit comments