useAnalyticsBucketAssociatedEntities.tsx170 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2
3import {
4 getAnalyticsBucketPublicationName,
5 getAnalyticsBucketS3KeyName,
6 getAnalyticsBucketsDestinationName,
7} from './AnalyticsBucketDetails.utils'
8import { useAnalyticsBucketWrapperInstance } from './useAnalyticsBucketWrapperInstance'
9import { useFDWDeleteMutation } from '@/data/fdw/fdw-delete-mutation'
10import { useDeleteDestinationPipelineMutation } from '@/data/replication/delete-destination-pipeline-mutation'
11import { useReplicationDestinationsQuery } from '@/data/replication/destinations-query'
12import { useReplicationPipelinesQuery } from '@/data/replication/pipelines-query'
13import { useDeletePublicationMutation } from '@/data/replication/publication-delete-mutation'
14import { useReplicationPublicationsQuery } from '@/data/replication/publications-query'
15import { useReplicationSourcesQuery } from '@/data/replication/sources-query'
16import { useS3AccessKeyDeleteMutation } from '@/data/storage/s3-access-key-delete-mutation'
17import { useStorageCredentialsQuery } from '@/data/storage/s3-access-key-query'
18import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
19import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
20
21/**
22 * Returns all the data that's associated to a specified analytics bucket (e.g publications, S3 keys, etc)
23 * Used for cleaning up analytics bucket after deletion
24 */
25export const useAnalyticsBucketAssociatedEntities = (
26 { projectRef, bucketId }: { projectRef?: string; bucketId?: string },
27 options: { enabled: boolean } = { enabled: true }
28) => {
29 const { can: canReadS3Credentials } = useAsyncCheckPermissions(
30 PermissionAction.STORAGE_ADMIN_READ,
31 '*'
32 )
33
34 const {
35 data: icebergWrapper,
36 meta: icebergWrapperMeta,
37 isLoading: isLoadingWrapperInstance,
38 } = useAnalyticsBucketWrapperInstance({ bucketId }, { enabled: options.enabled })
39
40 const { data: s3AccessKeys } = useStorageCredentialsQuery(
41 { projectRef },
42 { enabled: canReadS3Credentials && options.enabled }
43 )
44 const s3AccessKey = (s3AccessKeys?.data ?? []).find(
45 (x) => x.description === getAnalyticsBucketS3KeyName(bucketId ?? '')
46 )
47
48 const { data: sourcesData } = useReplicationSourcesQuery(
49 { projectRef },
50 { enabled: options.enabled }
51 )
52 const sourceId = sourcesData?.sources.find((s) => s.name === projectRef)?.id
53
54 const { data: publications = [] } = useReplicationPublicationsQuery(
55 { projectRef, sourceId },
56 { enabled: options.enabled }
57 )
58 const publication = publications.find(
59 (p) => p.name === getAnalyticsBucketPublicationName(bucketId ?? '')
60 )
61
62 const { data: destinationsData } = useReplicationDestinationsQuery({ projectRef })
63 const destinations = destinationsData?.destinations ?? []
64 const destination = destinations.find(
65 (x) => x.name === getAnalyticsBucketsDestinationName(bucketId ?? '')
66 )
67
68 const { data: pipelines } = useReplicationPipelinesQuery({ projectRef })
69 const pipeline = pipelines?.pipelines.find((x) => x.config.publication_name === publication?.name)
70
71 return {
72 icebergWrapper,
73 icebergWrapperMeta,
74 s3AccessKey,
75 sourceId,
76 publication,
77 pipeline,
78 destination,
79 isLoadingWrapperInstance,
80 }
81}
82
83export const useAnalyticsBucketDeleteCleanUp = ({
84 projectRef,
85 bucketId,
86}: {
87 projectRef?: string
88 bucketId?: string
89}) => {
90 const { data: project } = useSelectedProjectQuery()
91 const {
92 icebergWrapper,
93 icebergWrapperMeta,
94 s3AccessKey,
95 publication,
96 sourceId,
97 pipeline,
98 destination,
99 } = useAnalyticsBucketAssociatedEntities({ projectRef, bucketId: bucketId })
100
101 // Default error handlers from all mutations will be silenced
102 const { mutateAsync: deleteFDW, isPending: isDeletingWrapper } = useFDWDeleteMutation({
103 onError: () => {},
104 })
105 const { mutateAsync: deleteS3AccessKey, isPending: isDeletingKey } = useS3AccessKeyDeleteMutation(
106 { onError: () => {} }
107 )
108 const { mutateAsync: deletePublication, isPending: isDeletingPublication } =
109 useDeletePublicationMutation({ onError: () => {} })
110 const { mutateAsync: deletePipeline, isPending: isDeletingPipeline } =
111 useDeleteDestinationPipelineMutation({ onError: () => {} })
112
113 const isDeleting =
114 isDeletingWrapper || isDeletingKey || isDeletingPublication || isDeletingPipeline
115
116 const mutateAsync = async () => {
117 const connectionString = project?.connectionString
118
119 if (!!icebergWrapper && !!icebergWrapperMeta) {
120 try {
121 await deleteFDW({
122 projectRef,
123 connectionString,
124 wrapper: icebergWrapper,
125 wrapperMeta: icebergWrapperMeta,
126 })
127 } catch (error: any) {
128 console.error(`Failed to delete iceberg wrapper for ${bucketId}:`, error.message)
129 }
130 } else {
131 console.warn(`Unable to find and delete iceberg wrapper for ${bucketId}`)
132 }
133
134 if (!!s3AccessKey) {
135 try {
136 await deleteS3AccessKey({ projectRef, id: s3AccessKey.id })
137 } catch (error: any) {
138 console.error(`Failed to delete S3 access key for: ${bucketId}`, error.message)
139 }
140 } else {
141 console.warn(`Unable to find and delete corresponding S3 access key for ${bucketId}`)
142 }
143
144 if (!!pipeline && !!destination) {
145 try {
146 await deletePipeline({
147 projectRef,
148 destinationId: destination?.id,
149 pipelineId: pipeline.id,
150 })
151 } catch (error: any) {
152 console.error(`Failed to delete replication pipeline for: ${bucketId}`, error.message)
153 }
154 } else {
155 console.warn(`Unable to find and delete replication pipeline for ${bucketId}`)
156 }
157
158 if (!!publication && !!sourceId) {
159 try {
160 await deletePublication({ projectRef, sourceId, publicationName: publication.name })
161 } catch (error: any) {
162 console.error(`Failed to delete replication publication for: ${bucketId}`, error.message)
163 }
164 } else {
165 console.warn(`Unable to find and delete replication publication for ${bucketId}`)
166 }
167 }
168
169 return { mutateAsync, isPending: isDeleting }
170}