From 541b6fc4f5eb5bcd9e712eae5186347f7ebca563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20Caba=C3=A7o?= Date: Tue, 21 Oct 2025 14:56:49 +0100 Subject: [PATCH] fix: improve realtime inspector (#39626) * Add better error descriptions when the channel fails to join * If there are no publications, database changes will be disabled and untoggled to prevent errors --- .../Inspector/RealtimeFilterPopover/index.tsx | 24 +++++++++++++++---- .../interfaces/Realtime/Inspector/index.tsx | 24 ++++++++++++++++--- .../Realtime/Inspector/useRealtimeMessages.ts | 10 ++++---- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx index 1e5ac85c025..e001132c535 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx @@ -1,6 +1,6 @@ import { PlusCircle } from 'lucide-react' import Link from 'next/link' -import { Dispatch, SetStateAction, useState } from 'react' +import { Dispatch, SetStateAction, useState, useEffect } from 'react' import { useParams } from 'common' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' @@ -38,6 +38,11 @@ export const RealtimeFilterPopover = ({ config, onChangeConfig }: RealtimeFilter const { data: org } = useSelectedOrganizationQuery() const { mutate: sendEvent } = useSendEventMutation() + // Update tempConfig when config changes to ensure consistency + useEffect(() => { + setTempConfig(config) + }, [config]) + const onOpen = (v: boolean) => { // when opening, copy the outside config into the intermediate one if (v === true) { @@ -127,9 +132,15 @@ export const RealtimeFilterPopover = ({ config, onChangeConfig }: RealtimeFilter
-
@@ -137,17 +148,20 @@ export const RealtimeFilterPopover = ({ config, onChangeConfig }: RealtimeFilter id="toggle-db-changes" size="tiny" checked={tempConfig.enableDbChanges} + disabled={!config.enableDbChanges} onChange={() => setTempConfig({ ...tempConfig, enableDbChanges: !tempConfig.enableDbChanges }) } />

- Listen for Database inserts, updates, deletes and more + {config.enableDbChanges + ? 'Listen for Database inserts, updates, deletes and more' + : 'Enable realtime publications to listen for database changes'}

- {tempConfig.enableDbChanges && ( + {tempConfig.enableDbChanges && config.enableDbChanges && ( <>
Filter messages from database changes diff --git a/apps/studio/components/interfaces/Realtime/Inspector/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/index.tsx index 116ce210093..098da67887c 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/index.tsx @@ -1,10 +1,10 @@ import { useParams } from 'common' import { useState, useEffect } from 'react' -import { motion } from 'framer-motion' -import { MousePointer2 } from 'lucide-react' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useDatabasePublicationsQuery } from 'data/database-publications/database-publications-query' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' +import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Header } from './Header' import MessagesTable from './MessagesTable' import { SendMessageModal } from './SendMessageModal' @@ -17,6 +17,19 @@ import { EmptyRealtime } from './EmptyRealtime' export const RealtimeInspector = () => { const { ref } = useParams() const { data: org } = useSelectedOrganizationQuery() + const { data: project } = useSelectedProjectQuery() + + // Check if realtime publications are available + const { data: publications } = useDatabasePublicationsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + const realtimePublication = (publications ?? []).find( + (publication) => publication.name === 'supabase_realtime' + ) + const isRealtimeAvailable = + !!realtimePublication && + ((realtimePublication?.tables ?? []).length > 0 || realtimePublication?.tables === null) const [sendMessageShown, setSendMessageShown] = useState(false) const [realtimeConfig, setRealtimeConfig] = useState({ @@ -31,13 +44,18 @@ export const RealtimeInspector = () => { filter: undefined, bearer: null, enablePresence: true, - enableDbChanges: true, + enableDbChanges: isRealtimeAvailable, // Initialize based on publications availability enableBroadcast: true, }) const { mutate: sendEvent } = useSendEventMutation() const { logData, sendMessage } = useRealtimeMessages(realtimeConfig, setRealtimeConfig) + // Update enableDbChanges when publications change + useEffect(() => { + setRealtimeConfig((prev) => ({ ...prev, enableDbChanges: isRealtimeAvailable })) + }, [isRealtimeAvailable]) + return (
diff --git a/apps/studio/components/interfaces/Realtime/Inspector/useRealtimeMessages.ts b/apps/studio/components/interfaces/Realtime/Inspector/useRealtimeMessages.ts index adfff8d9a1e..40643867f1b 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/useRealtimeMessages.ts +++ b/apps/studio/components/interfaces/Realtime/Inspector/useRealtimeMessages.ts @@ -168,7 +168,7 @@ export const useRealtimeMessages = ( } // Finally, subscribe to the Channel we just setup - newChannel.subscribe(async (status) => { + newChannel.subscribe(async (status, err) => { if (status === 'SUBSCRIBED') { // Let LiveView know we connected so we can update the button text // pushMessageTo('#conn_info', 'broadcast_subscribed', { host: host }) @@ -192,9 +192,11 @@ export const useRealtimeMessages = ( }) } } else if (status === 'CHANNEL_ERROR') { - toast.error( - `Failed to connect to the channel ${channelName}: This may be due to restrictive RLS policies. Check your role and try again.` - ) + if (err?.message) { + toast.error(`Failed to connect with the following error: ${err.message}`) + } else { + toast.error(`Failed to connect. Please check your RLS policies and try again.`) + } newChannel.unsubscribe() setChannel(undefined)