Skip to content

Commit e36ae95

Browse files
authored
Refactor polling logic for Analytics Buckets while syncing with namespace (supabase#40381)
* Refactor long polling logic for analytics bucket to automatically long poll when landing back on page and data has yet to be synced * Nit clean up refactor * Clean up * Nit
1 parent 0c5fb02 commit e36ae95

File tree

11 files changed

+239
-107
lines changed

11 files changed

+239
-107
lines changed

apps/studio/components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/AnalyticsBucketDetails.utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,10 @@ export const getAnalyticsBucketFDWName = (bucketId: string) => {
1515
export const getAnalyticsBucketFDWServerName = (bucketId: string) => {
1616
return `${snakeCase(bucketId)}_fdw_server`
1717
}
18+
19+
export const getNamespaceTableNameFromPostgresTableName = (table: {
20+
name: string
21+
schema: string
22+
}) => {
23+
return `${snakeCase(`${table.schema}.${table.name}`)}_changelog`
24+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { noop } from 'lodash'
2+
import Link from 'next/link'
3+
4+
import { useParams } from 'common'
5+
import { FormattedWrapperTable } from 'components/interfaces/Integrations/Wrappers/Wrappers.utils'
6+
import {
7+
ScaffoldHeader,
8+
ScaffoldSectionDescription,
9+
ScaffoldSectionTitle,
10+
} from 'components/layouts/Scaffold'
11+
import { useReplicationPipelineStatusQuery } from 'data/etl/pipeline-status-query'
12+
import { Button } from 'ui'
13+
import { ConnectTablesDialog } from './ConnectTablesDialog'
14+
import { useAnalyticsBucketAssociatedEntities } from './useAnalyticsBucketAssociatedEntities'
15+
16+
interface BucketHeaderProps {
17+
showActions?: boolean
18+
namespaces?: {
19+
namespace: string
20+
schema: string
21+
tables: FormattedWrapperTable[]
22+
}[]
23+
onSuccessConnectTables?: () => void
24+
}
25+
26+
export const BucketHeader = ({
27+
showActions = true,
28+
namespaces = [],
29+
onSuccessConnectTables = noop,
30+
}: BucketHeaderProps) => {
31+
const { ref: projectRef, bucketId } = useParams()
32+
33+
const { pipeline } = useAnalyticsBucketAssociatedEntities({ projectRef, bucketId })
34+
const { data } = useReplicationPipelineStatusQuery({ projectRef, pipelineId: pipeline?.id })
35+
const pipelineStatus = data?.status.name
36+
const isPipelineRunning = pipelineStatus === 'started'
37+
38+
return (
39+
<ScaffoldHeader className="pt-0 flex flex-row justify-between items-end gap-x-8">
40+
<div>
41+
<ScaffoldSectionTitle>Tables</ScaffoldSectionTitle>
42+
<ScaffoldSectionDescription>
43+
Analytics tables stored in this bucket
44+
</ScaffoldSectionDescription>
45+
</div>
46+
{showActions && (
47+
<div className="flex items-center gap-x-2">
48+
{!!pipeline && isPipelineRunning && (
49+
<Button asChild type="default">
50+
<Link href={`/project/${projectRef}/database/etl/${pipeline.replicator_id}`}>
51+
View replication
52+
</Link>
53+
</Button>
54+
)}
55+
{namespaces.length > 0 && (
56+
<ConnectTablesDialog onSuccessConnectTables={onSuccessConnectTables} />
57+
)}
58+
</div>
59+
)}
60+
</ScaffoldHeader>
61+
)
62+
}

apps/studio/components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/ConnectTablesDialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const PROGRESS_INDICATORS = {
9292
}
9393

9494
interface ConnectTablesDialogProps {
95-
onSuccessConnectTables: (tables: { schema: string; name: string }[]) => void
95+
onSuccessConnectTables: () => void
9696
}
9797

9898
export const ConnectTablesDialog = ({ onSuccessConnectTables }: ConnectTablesDialogProps) => {
@@ -245,7 +245,7 @@ export const ConnectTablesDialogContent = ({
245245
setConnectingStep(PROGRESS_STAGE.START_PIPELINE)
246246
await startPipeline({ projectRef, pipelineId })
247247

248-
onSuccessConnectTables?.(publicationTables)
248+
onSuccessConnectTables?.()
249249
toast.success(`Connected ${values.tables.length} tables to Analytics bucket!`)
250250
form.reset()
251251
onClose()
@@ -287,7 +287,7 @@ export const ConnectTablesDialogContent = ({
287287
setConnectingStep(PROGRESS_STAGE.START_PIPELINE)
288288
await startPipeline({ projectRef, pipelineId: pipeline.id })
289289

290-
onSuccessConnectTables?.(tablesToBeAdded)
290+
onSuccessConnectTables?.()
291291
toast.success('Successfully updated connected tables! Pipeline is being restarted')
292292
onClose()
293293
} catch (error: any) {

apps/studio/components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/NamespaceWithTables/TableRowComponent.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { snakeCase, uniq } from 'lodash'
1+
import { uniq } from 'lodash'
22
import { Loader2, MoreVertical, Pause, Play, Trash } from 'lucide-react'
33
import Link from 'next/link'
44
import { useMemo, useState } from 'react'
@@ -34,7 +34,10 @@ import {
3434
TooltipTrigger,
3535
} from 'ui'
3636
import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal'
37-
import { getAnalyticsBucketFDWServerName } from '../AnalyticsBucketDetails.utils'
37+
import {
38+
getAnalyticsBucketFDWServerName,
39+
getNamespaceTableNameFromPostgresTableName,
40+
} from '../AnalyticsBucketDetails.utils'
3841
import { useAnalyticsBucketAssociatedEntities } from '../useAnalyticsBucketAssociatedEntities'
3942
import { useAnalyticsBucketWrapperInstance } from '../useAnalyticsBucketWrapperInstance'
4043
import { inferPostgresTableFromNamespaceTable } from './NamespaceWithTables.utils'
@@ -116,7 +119,7 @@ export const TableRowComponent = ({
116119
// [Joshen ALPHA] Assumption here is that all the namespace tables have _changelog as suffix
117120
// May need to update if that assumption falls short (e.g for those dealing with iceberg APIs directly)
118121
const updatedTables = publication.tables.filter(
119-
(x) => table.name !== snakeCase(`${x.schema}.${x.name}_changelog`)
122+
(x) => table.name !== getNamespaceTableNameFromPostgresTableName(x)
120123
)
121124
await updatePublication({
122125
projectRef,
@@ -142,7 +145,9 @@ export const TableRowComponent = ({
142145
if (!pipeline) return toast.error('Unable to find existing pipeline')
143146

144147
// [Joshen ALPHA] This has potential to be flaky - we should see how we can get the table name and schema better
145-
const pgTable = tables?.find((t) => snakeCase(`${t.schema}.${t.name}_changelog`) === table.name)
148+
const pgTable = tables?.find(
149+
(t) => getNamespaceTableNameFromPostgresTableName(t) === table.name
150+
)
146151
if (!pgTable) return toast.error('Unable to find corresponding Postgres table')
147152

148153
try {

apps/studio/components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/NamespaceWithTables/index.tsx

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ChevronRight, Info, Loader2, Plus, RefreshCw } from 'lucide-react'
2-
import { useMemo, useState } from 'react'
2+
import { useEffect, useMemo, useState } from 'react'
33

44
import { useParams } from 'common'
55
import type { WrapperMeta } from 'components/interfaces/Integrations/Wrappers/Wrappers.types'
@@ -25,6 +25,7 @@ import {
2525
TooltipContent,
2626
TooltipTrigger,
2727
} from 'ui'
28+
import { getNamespaceTableNameFromPostgresTableName } from '../AnalyticsBucketDetails.utils'
2829
import { useAnalyticsBucketAssociatedEntities } from '../useAnalyticsBucketAssociatedEntities'
2930
import { TableRowComponent } from './TableRowComponent'
3031

@@ -37,7 +38,6 @@ type NamespaceWithTablesProps = {
3738
wrapperInstance: FDW
3839
wrapperValues: Record<string, string>
3940
wrapperMeta: WrapperMeta
40-
tablesToPoll: { schema: string; name: string }[]
4141
pollIntervalNamespaceTables: number
4242
setPollIntervalNamespaceTables: (value: number) => void
4343
}
@@ -51,7 +51,6 @@ export const NamespaceWithTables = ({
5151
wrapperInstance,
5252
wrapperValues,
5353
wrapperMeta,
54-
tablesToPoll,
5554
pollIntervalNamespaceTables,
5655
setPollIntervalNamespaceTables,
5756
}: NamespaceWithTablesProps) => {
@@ -61,31 +60,42 @@ export const NamespaceWithTables = ({
6160

6261
const { publication } = useAnalyticsBucketAssociatedEntities({ projectRef, bucketId })
6362

64-
const { data: tablesData, isLoading: isLoadingNamespaceTables } = useIcebergNamespaceTablesQuery(
63+
const {
64+
data: tablesData = [],
65+
isLoading: isLoadingNamespaceTables,
66+
isSuccess: isSuccessNamespaceTables,
67+
} = useIcebergNamespaceTablesQuery(
6568
{
6669
catalogUri: wrapperValues.catalog_uri,
6770
warehouse: wrapperValues.warehouse,
6871
namespace: namespace,
6972
projectRef,
7073
},
7174
{
72-
refetchInterval: (data) => {
75+
refetchInterval: (data = []) => {
7376
if (pollIntervalNamespaceTables === 0) return false
74-
if (tablesToPoll.length > 0) {
75-
const hasMissingTables =
76-
(data ?? []).filter((t) => !tables.find((table) => table.table.split('.')[1] === t))
77-
.length > 0
7877

79-
if (hasMissingTables) {
80-
setPollIntervalNamespaceTables(0)
81-
return false
82-
}
78+
const publicationTables = publication?.tables ?? []
79+
const isSynced = !publicationTables.some(
80+
(x) => !data.includes(getNamespaceTableNameFromPostgresTableName(x))
81+
)
82+
if (isSynced) {
83+
setPollIntervalNamespaceTables(0)
84+
return false
8385
}
86+
8487
return pollIntervalNamespaceTables
8588
},
8689
}
8790
)
8891

92+
const publicationTables = publication?.tables ?? []
93+
const publicationTablesNotSyncedToNamespaceTables = publicationTables.filter(
94+
(x) => !tablesData.includes(getNamespaceTableNameFromPostgresTableName(x))
95+
)
96+
const isSyncedPublicationTablesAndNamespaceTables =
97+
publicationTablesNotSyncedToNamespaceTables.length === 0
98+
8999
const { mutateAsync: importForeignSchema, isLoading: isImportingForeignSchema } =
90100
useFDWImportForeignSchemaMutation()
91101

@@ -140,6 +150,13 @@ export const NamespaceWithTables = ({
140150
return `fdw_analytics_${namespace.replaceAll('-', '_')}`
141151
}, [schema, namespace])
142152

153+
useEffect(() => {
154+
if (isSuccessNamespaceTables && !isSyncedPublicationTablesAndNamespaceTables) {
155+
setPollIntervalNamespaceTables(4000)
156+
}
157+
// eslint-disable-next-line react-hooks/exhaustive-deps
158+
}, [isSuccessNamespaceTables, isSyncedPublicationTablesAndNamespaceTables])
159+
143160
return (
144161
<Card>
145162
<CardHeader className="flex flex-row justify-between items-center px-4 py-5 space-y-0">
@@ -179,14 +196,28 @@ export const NamespaceWithTables = ({
179196
)}
180197
</CardTitle>
181198

182-
<div className="flex flex-row gap-x-2">
183-
{tablesToPoll.length > 0 && (
184-
<div className="flex items-center gap-x-2 ml-6 text-foreground-lighter">
185-
<Loader2 size={14} className="animate-spin" />
186-
<p className="text-sm">
187-
Connecting {tablesToPoll.length} table{tablesToPoll.length > 1 ? 's' : ''}
188-
</p>
189-
</div>
199+
<div className="flex flex-row gap-x-6">
200+
{pollIntervalNamespaceTables > 0 && (
201+
<Tooltip>
202+
<TooltipTrigger>
203+
<div className="flex items-center gap-x-2 text-foreground-lighter">
204+
<Loader2 size={14} className="animate-spin" />
205+
<p className="text-sm">
206+
Connecting {publicationTablesNotSyncedToNamespaceTables.length} table
207+
{publicationTablesNotSyncedToNamespaceTables.length > 1 ? 's' : ''}
208+
</p>
209+
</div>
210+
</TooltipTrigger>
211+
<TooltipContent side="bottom" align="end">
212+
<p className="mb-1">Waiting for namespace table to be created for:</p>
213+
<ul className="list-disc pl-6">
214+
{publicationTablesNotSyncedToNamespaceTables.map((x) => {
215+
const value = `${x.schema}.${x.name}`
216+
return <li key={value}>{value}</li>
217+
})}
218+
</ul>
219+
</TooltipContent>
220+
</Tooltip>
190221
)}
191222
{missingTables.length > 0 && (
192223
<Button
@@ -202,7 +233,7 @@ export const NamespaceWithTables = ({
202233
</div>
203234
</CardHeader>
204235

205-
{tablesToPoll.length > 0 && <LoadingLine loading />}
236+
{pollIntervalNamespaceTables > 0 && <LoadingLine loading />}
206237

207238
<Table>
208239
<TableHeader>

apps/studio/components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/SimpleConfigurationDetails.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { InlineLink } from 'components/ui/InlineLink'
1111
import { DOCS_URL } from 'lib/constants'
1212
import { Card } from 'ui'
13-
import { DESCRIPTIONS, LABELS, OPTION_ORDER } from './constants'
13+
import { DESCRIPTIONS, LABELS, OPTION_ORDER } from './AnalyticsBucketDetails.constants'
1414
import { CopyEnvButton } from './CopyEnvButton'
1515
import { DecryptedReadOnlyInput } from './DecryptedReadOnlyInput'
1616
import { useAnalyticsBucketWrapperInstance } from './useAnalyticsBucketWrapperInstance'
@@ -27,7 +27,7 @@ export const SimpleConfigurationDetails = ({ bucketName }: { bucketName?: string
2727

2828
return (
2929
<ScaffoldSection isFullWidth>
30-
<ScaffoldHeader className="flex flex-row justify-between items-end gap-x-8">
30+
<ScaffoldHeader className="flex flex-row justify-between items-end gap-x-8 pt-0">
3131
<div>
3232
<ScaffoldSectionTitle>Connection details</ScaffoldSectionTitle>
3333
<ScaffoldSectionDescription>

0 commit comments

Comments
 (0)