useAnalyticsBucketAssociatedEntities.tsx170 lines · main
| 1 | import { PermissionAction } from '@supabase/shared-types/out/constants' |
| 2 | |
| 3 | import { |
| 4 | getAnalyticsBucketPublicationName, |
| 5 | getAnalyticsBucketS3KeyName, |
| 6 | getAnalyticsBucketsDestinationName, |
| 7 | } from './AnalyticsBucketDetails.utils' |
| 8 | import { useAnalyticsBucketWrapperInstance } from './useAnalyticsBucketWrapperInstance' |
| 9 | import { useFDWDeleteMutation } from '@/data/fdw/fdw-delete-mutation' |
| 10 | import { useDeleteDestinationPipelineMutation } from '@/data/replication/delete-destination-pipeline-mutation' |
| 11 | import { useReplicationDestinationsQuery } from '@/data/replication/destinations-query' |
| 12 | import { useReplicationPipelinesQuery } from '@/data/replication/pipelines-query' |
| 13 | import { useDeletePublicationMutation } from '@/data/replication/publication-delete-mutation' |
| 14 | import { useReplicationPublicationsQuery } from '@/data/replication/publications-query' |
| 15 | import { useReplicationSourcesQuery } from '@/data/replication/sources-query' |
| 16 | import { useS3AccessKeyDeleteMutation } from '@/data/storage/s3-access-key-delete-mutation' |
| 17 | import { useStorageCredentialsQuery } from '@/data/storage/s3-access-key-query' |
| 18 | import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' |
| 19 | import { 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 | */ |
| 25 | export 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 | |
| 83 | export 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 | } |