From 1b8c8b7398f2e613c7bfa0113ac6533518038f0e Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Thu, 16 Oct 2025 00:52:05 -0400 Subject: [PATCH 01/10] fix(feedback): generate image urls on backend (#39559) * fix(feedback): generate image urls on backend * Fix support form skip generate attachment urls if no attachments * Refactor to use useLocalStorageQuery * Nit --------- Co-authored-by: Joshen Lim --- .../Organization/ProjectClaim/layout.tsx | 2 +- .../interfaces/Support/AttachmentUpload.tsx | 8 +- .../interfaces/Support/SupportForm.utils.tsx | 4 +- .../FeedbackDropdown/FeedbackDropdown.tsx | 15 +-- .../FeedbackDropdown.utils.ts | 57 ++++----- .../FeedbackDropdown/FeedbackWidget.tsx | 120 +++++++----------- .../LayoutHeader/FeedbackDropdown/index.ts | 1 - .../LayoutHeader/LayoutHeader.tsx | 2 +- .../generate-attachment-urls-mutation.ts | 10 +- .../pages/api/generate-attachment-url.ts | 4 +- packages/common/constants/local-storage.ts | 2 + 11 files changed, 96 insertions(+), 129 deletions(-) delete mode 100644 apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/index.ts diff --git a/apps/studio/components/interfaces/Organization/ProjectClaim/layout.tsx b/apps/studio/components/interfaces/Organization/ProjectClaim/layout.tsx index a01576487491c..5185617854040 100644 --- a/apps/studio/components/interfaces/Organization/ProjectClaim/layout.tsx +++ b/apps/studio/components/interfaces/Organization/ProjectClaim/layout.tsx @@ -2,7 +2,7 @@ import Image from 'next/image' import { PropsWithChildren, ReactNode } from 'react' import { UserDropdown } from 'components/interfaces/UserDropdown' -import { FeedbackDropdown } from 'components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown' +import { FeedbackDropdown } from 'components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown' import { BASE_PATH } from 'lib/constants' import { cn, Separator } from 'ui' diff --git a/apps/studio/components/interfaces/Support/AttachmentUpload.tsx b/apps/studio/components/interfaces/Support/AttachmentUpload.tsx index d875d3ee46c1e..36cd6ea6d41f2 100644 --- a/apps/studio/components/interfaces/Support/AttachmentUpload.tsx +++ b/apps/studio/components/interfaces/Support/AttachmentUpload.tsx @@ -21,8 +21,6 @@ import { createSupportStorageClient } from './support-storage-client' const MAX_ATTACHMENTS = 5 const uploadAttachments = async ({ userId, files }: { userId: string; files: File[] }) => { - if (files.length === 0) return [] - const supportSupabaseClient = createSupportStorageClient() const filesToUpload = Array.from(files) @@ -104,11 +102,13 @@ export function useAttachmentUpload() { return [] } + if (uploadedFiles.length === 0) return + const filenames = await uploadAttachments({ userId: profile.gotrue_id, files: uploadedFiles }) - const urls = await generateAttachmentURLs({ filenames }) + const urls = await generateAttachmentURLs({ bucket: 'support-attachments', filenames }) return urls // eslint-disable-next-line react-hooks/exhaustive-deps - }, [uploadedFiles]) + }, [profile, uploadedFiles]) return useMemo( () => ({ diff --git a/apps/studio/components/interfaces/Support/SupportForm.utils.tsx b/apps/studio/components/interfaces/Support/SupportForm.utils.tsx index f22daad621d74..e33793c126dac 100644 --- a/apps/studio/components/interfaces/Support/SupportForm.utils.tsx +++ b/apps/studio/components/interfaces/Support/SupportForm.utils.tsx @@ -24,12 +24,12 @@ export const NO_ORG_MARKER = 'no-org' export const formatMessage = ({ message, - attachments, + attachments = [], error, commit, }: { message: string - attachments: string[] + attachments?: string[] error: string | null | undefined commit: string | undefined }) => { diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx index 3602fb585b9f4..54e30a670a711 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx @@ -5,10 +5,8 @@ import { useState } from 'react' import { Button, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_ } from 'ui' import { FeedbackWidget } from './FeedbackWidget' -const FeedbackDropdown = ({ className }: { className?: string }) => { +export const FeedbackDropdown = ({ className }: { className?: string }) => { const [isOpen, setIsOpen] = useState(false) - const [feedback, setFeedback] = useState('') - const [screenshot, setScreenshot] = useState() const [stage, setStage] = useState<'select' | 'widget'>('select') return ( @@ -17,7 +15,6 @@ const FeedbackDropdown = ({ className }: { className?: string }) => { open={isOpen} onOpenChange={(e) => { setIsOpen(e) - if (!e) setScreenshot(undefined) if (!e) setStage('select') }} > @@ -63,15 +60,7 @@ const FeedbackDropdown = ({ className }: { className?: string }) => { )} - {stage === 'widget' && ( - setIsOpen(false)} - feedback={feedback} - setFeedback={setFeedback} - screenshot={screenshot} - setScreenshot={setScreenshot} - /> - )} + {stage === 'widget' && setIsOpen(false)} />} ) diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.utils.ts b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.utils.ts index 2cabae9c7bcf7..497c8cca5501e 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.utils.ts +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.utils.ts @@ -1,9 +1,7 @@ -import { createClient } from '@supabase/supabase-js' +import { createSupportStorageClient } from 'components/interfaces/Support/support-storage-client' +import { generateAttachmentURLs } from 'data/support/generate-attachment-urls-mutation' import { uuidv4 } from 'lib/helpers' -const SUPPORT_API_URL = process.env.NEXT_PUBLIC_SUPPORT_API_URL || '' -const SUPPORT_API_KEY = process.env.NEXT_PUBLIC_SUPPORT_ANON_KEY || '' - export const convertB64toBlob = (image: string) => { const contentType = 'image/png' const byteCharacters = atob(image.substr(`data:${contentType};base64,`.length)) @@ -25,40 +23,37 @@ export const convertB64toBlob = (image: string) => { return blob } -export const uploadAttachment = async (ref: string, image: string) => { - const supabaseClient = createClient(SUPPORT_API_URL, SUPPORT_API_KEY, { - auth: { - persistSession: false, - autoRefreshToken: false, - // @ts-ignore - multiTab: false, - detectSessionInUrl: false, - localStorage: { - getItem: (key: string) => undefined, - setItem: (key: string, value: string) => {}, - removeItem: (key: string) => {}, - }, - }, - }) +type UploadAttachmentArgs = { + image: string + userId?: string +} + +export const uploadAttachment = async ({ image, userId }: UploadAttachmentArgs) => { + if (!userId) { + console.error( + '[FeedbackWidget > uploadAttachment] Unable to upload screenshot, missing user ID' + ) + return undefined + } + + const supabaseClient = createSupportStorageClient() const blob = convertB64toBlob(image) - const name = `${ref || 'no-project'}/${uuidv4()}.png` + const filename = `${userId}/${uuidv4()}.png` const options = { cacheControl: '3600' } + const { data: file, error: uploadError } = await supabaseClient.storage .from('feedback-attachments') - .upload(name, blob, options) + .upload(filename, blob, options) - if (uploadError) { - console.error('Failed to upload:', uploadError) + if (uploadError || !file) { + console.error('Failed to upload screenshot attachment:', uploadError) return undefined } - if (file) { - const { data } = await supabaseClient.storage - .from('feedback-attachments') - .createSignedUrls([file.path], 10 * 365 * 24 * 60 * 60) - return data?.[0].signedUrl - } - - return undefined + const signedUrls = await generateAttachmentURLs({ + bucket: 'feedback-attachments', + filenames: [file.path], + }) + return signedUrls[0] } diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx index 8ad7b313a3e56..8d11bf84f5575 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx @@ -1,3 +1,4 @@ +import { useDebounce } from '@uidotdev/usehooks' import { AnimatePresence, motion } from 'framer-motion' import { toPng } from 'html-to-image' import { Camera, CircleCheck, Image as ImageIcon, Upload, X } from 'lucide-react' @@ -5,16 +6,17 @@ import Link from 'next/link' import { useRouter } from 'next/router' import { ChangeEvent, useEffect, useRef, useState } from 'react' import { toast } from 'sonner' -import { useDebounce } from 'use-debounce' -import { useParams } from 'common' +import { LOCAL_STORAGE_KEYS, useParams } from 'common' import { InlineLink } from 'components/ui/InlineLink' import { useFeedbackCategoryQuery } from 'data/feedback/feedback-category' import { useSendFeedbackMutation } from 'data/feedback/feedback-send' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { DOCS_URL } from 'lib/constants' import { timeout } from 'lib/helpers' +import { useProfile } from 'lib/profile' import { Button, DropdownMenu, @@ -29,47 +31,40 @@ import { Admonition } from 'ui-patterns' import { convertB64toBlob, uploadAttachment } from './FeedbackDropdown.utils' interface FeedbackWidgetProps { - feedback: string - screenshot: string | undefined onClose: () => void - setFeedback: (value: string) => void - setScreenshot: (value: string | undefined) => void } -export const FeedbackWidget = ({ - feedback, - screenshot, - onClose, - setFeedback, - setScreenshot, -}: FeedbackWidgetProps) => { - const FEEDBACK_STORAGE_KEY = 'feedback_content' - const SCREENSHOT_STORAGE_KEY = 'screenshot' - +export const FeedbackWidget = ({ onClose }: FeedbackWidgetProps) => { const router = useRouter() + const { profile } = useProfile() const { ref, slug } = useParams() const { data: org } = useSelectedOrganizationQuery() - const uploadButtonRef = useRef(null) + const uploadButtonRef = useRef(null) + const [feedback, setFeedback] = useState('') const [isSending, setSending] = useState(false) const [isSavingScreenshot, setIsSavingScreenshot] = useState(false) const [isFeedbackSent, setIsFeedbackSent] = useState(false) - const [debouncedFeedback] = useDebounce(feedback, 450) - const { data: category } = useFeedbackCategoryQuery({ - prompt: debouncedFeedback, - }) + const debouncedFeedback = useDebounce(feedback, 500) - const { mutate: sendEvent } = useSendEventMutation() + const [storedFeedback, setStoredFeedback] = useLocalStorageQuery( + LOCAL_STORAGE_KEYS.FEEDBACK_WIDGET_CONTENT, + null + ) + const [screenshot, setScreenshot, { isSuccess }] = useLocalStorageQuery( + LOCAL_STORAGE_KEYS.FEEDBACK_WIDGET_SCREENSHOT, + null + ) + const { data: category } = useFeedbackCategoryQuery({ prompt: debouncedFeedback }) + + const { mutate: sendEvent } = useSendEventMutation() const { mutate: submitFeedback } = useSendFeedbackMutation({ onSuccess: () => { setIsFeedbackSent(true) setFeedback('') - setScreenshot(undefined) - localStorage.removeItem(FEEDBACK_STORAGE_KEY) - localStorage.removeItem(SCREENSHOT_STORAGE_KEY) - + setScreenshot(null) setSending(false) }, onError: (error) => { @@ -78,28 +73,6 @@ export const FeedbackWidget = ({ }, }) - useEffect(() => { - const storedFeedback = localStorage.getItem(FEEDBACK_STORAGE_KEY) - if (storedFeedback) { - setFeedback(storedFeedback) - } - - const storedScreenshot = localStorage.getItem(SCREENSHOT_STORAGE_KEY) - if (storedScreenshot) { - setScreenshot(storedScreenshot) - } - }, []) - - useEffect(() => { - localStorage.setItem(FEEDBACK_STORAGE_KEY, feedback) - }, [feedback]) - - useEffect(() => { - if (screenshot) { - localStorage.setItem(SCREENSHOT_STORAGE_KEY, screenshot) - } - }, [screenshot]) - const captureScreenshot = async () => { setIsSavingScreenshot(true) @@ -113,14 +86,9 @@ export const FeedbackWidget = ({ // Give time for dropdown to close await timeout(100) toPng(document.body, { filter }) - .then((dataUrl: any) => { - localStorage.setItem(SCREENSHOT_STORAGE_KEY, dataUrl) - setScreenshot(dataUrl) - }) + .then((dataUrl: any) => setScreenshot(dataUrl)) .catch(() => toast.error('Failed to capture screenshot')) - .finally(() => { - setIsSavingScreenshot(false) - }) + .finally(() => setIsSavingScreenshot(false)) } const onFilesUpload = async (event: ChangeEvent) => { @@ -130,10 +98,7 @@ export const FeedbackWidget = ({ const reader = new FileReader() reader.onload = function (event) { const dataUrl = event.target?.result - if (typeof dataUrl === 'string') { - setScreenshot(dataUrl) - localStorage.setItem(SCREENSHOT_STORAGE_KEY, dataUrl) - } + if (typeof dataUrl === 'string') setScreenshot(dataUrl) } reader.readAsDataURL(file) event.target.value = '' @@ -148,10 +113,7 @@ export const FeedbackWidget = ({ const reader = new FileReader() reader.onload = function (event) { const dataUrl = event.target?.result - if (typeof dataUrl === 'string') { - setScreenshot(dataUrl) - localStorage.setItem(SCREENSHOT_STORAGE_KEY, dataUrl) - } + if (typeof dataUrl === 'string') setScreenshot(dataUrl) } reader.readAsDataURL(blob) } @@ -163,9 +125,13 @@ export const FeedbackWidget = ({ } else if (feedback.length > 0) { setSending(true) - const attachmentUrl = screenshot - ? await uploadAttachment(ref as string, screenshot) - : undefined + const attachmentUrl = + screenshot && profile?.gotrue_id + ? await uploadAttachment({ + image: screenshot, + userId: profile.gotrue_id, + }) + : undefined const formattedFeedback = attachmentUrl !== undefined ? `${feedback}\n\nAttachments:\n${attachmentUrl}` : feedback @@ -178,6 +144,15 @@ export const FeedbackWidget = ({ } } + useEffect(() => { + if (storedFeedback) setFeedback(storedFeedback) + if (screenshot) setScreenshot(screenshot) + }, [isSuccess]) + + useEffect(() => { + if (debouncedFeedback.length > 0) setStoredFeedback(debouncedFeedback) + }, [debouncedFeedback]) + return isFeedbackSent ? ( ) : ( @@ -246,7 +221,7 @@ export const FeedbackWidget = ({

- {screenshot !== undefined ? ( + {!!screenshot ? (
{ @@ -254,7 +229,7 @@ export const FeedbackWidget = ({ const blobUrl = URL.createObjectURL(blob) window.open(blobUrl, '_blank') }} - className="cursor-pointer rounded h-[26px] w-[30px] border border-control relative bg-cover bg-center bg-no-repeat" + className="cursor-pointer rounded h-[26px] w-[26px] border border-control relative bg-cover bg-center bg-no-repeat" > + className="w-7" + icon={} + /> Date: Thu, 16 Oct 2025 16:36:10 +1000 Subject: [PATCH 02/10] Hide GettingStarted if mature (#39582) * hide if mature * use project --- apps/studio/components/interfaces/HomeNew/Home.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/studio/components/interfaces/HomeNew/Home.tsx b/apps/studio/components/interfaces/HomeNew/Home.tsx index 820fb1728aefc..ffb9b1de5c802 100644 --- a/apps/studio/components/interfaces/HomeNew/Home.tsx +++ b/apps/studio/components/interfaces/HomeNew/Home.tsx @@ -1,3 +1,4 @@ +import dayjs from 'dayjs' import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core' import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' import { useEffect, useRef } from 'react' @@ -26,6 +27,8 @@ export const HomeV2 = () => { const { data: organization } = useSelectedOrganizationQuery() const { mutate: sendEvent } = useSendEventMutation() + const isMatureProject = dayjs(project?.inserted_at).isBefore(dayjs().subtract(10, 'day')) + const hasShownEnableBranchingModalRef = useRef(false) const isPaused = project?.status === PROJECT_STATUS.INACTIVE @@ -103,8 +106,13 @@ export const HomeV2 = () => { ) } - if (id === 'getting-started') { - return gettingStartedState === 'hidden' ? null : ( + if ( + id === 'getting-started' && + !isMatureProject && + project && + gettingStartedState !== 'hidden' + ) { + return ( Date: Thu, 16 Oct 2025 08:46:24 +0200 Subject: [PATCH 03/10] docs: Add note about custom domains in vercel (#39570) --- apps/docs/content/guides/integrations/vercel-marketplace.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/docs/content/guides/integrations/vercel-marketplace.mdx b/apps/docs/content/guides/integrations/vercel-marketplace.mdx index 6a16f263f81a8..2db3ac8df8013 100644 --- a/apps/docs/content/guides/integrations/vercel-marketplace.mdx +++ b/apps/docs/content/guides/integrations/vercel-marketplace.mdx @@ -94,7 +94,8 @@ Note: Supabase Organization billing cycle is separate from Vercel's. Plan change When using Vercel Marketplace, the following limitations apply: -- Projects can only be created or removed via the Vercel dashboard. +- Projects can only be created via the Vercel dashboard. - Organizations cannot be removed manually; they are removed only if you uninstall the Vercel Marketplace Integration. - Owners cannot be added manually within the Supabase dashboard. - Invoices and payments must be managed through the Vercel dashboard, not the Supabase dashboard. +- [Custom Domains](/docs/guides/platform/custom-domains) are not supported, and we always use the base `SUPABASE_URL` for the Vercel environment variables. From 2822c259bfe7dccfce99ea99febf9d09e5471dec Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 16 Oct 2025 16:02:05 +0800 Subject: [PATCH 04/10] Custom domain update link to check TXT record (#39581) --- .../Settings/General/CustomDomainConfig/CustomDomainVerify.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx b/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx index 6e0784ffd400d..eb4ee5ac5c2fd 100644 --- a/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx +++ b/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx @@ -111,7 +111,7 @@ const CustomDomainVerify = () => { here From 5e0b969a29fb0ebb30f1a3d64bddf4c674967713 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Thu, 16 Oct 2025 16:16:46 +0800 Subject: [PATCH 05/10] fix: show replica as healthy if it is (#39587) --- .../InfrastructureConfiguration/InstanceNode.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx index 98f1611f4c609..503291d1975e1 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx @@ -294,8 +294,7 @@ export const ReplicaNode = ({ data }: NodeProps) => { Restarting ) : status === REPLICA_STATUS.RESIZING ? ( Resizing - ) : initStatus === ReplicaInitializationStatus.Completed && - status === REPLICA_STATUS.ACTIVE_HEALTHY ? ( + ) : status === REPLICA_STATUS.ACTIVE_HEALTHY ? ( Healthy ) : ( Unhealthy From b34da8d7eca562be5e1341413d140dd76ee19f25 Mon Sep 17 00:00:00 2001 From: Chandana Anumula <129955975+canumula@users.noreply.github.com> Date: Thu, 16 Oct 2025 13:55:33 +0530 Subject: [PATCH 06/10] To update physical backups are enabled when database size > 15GB (#39586) --- apps/docs/content/guides/platform/backups.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/guides/platform/backups.mdx b/apps/docs/content/guides/platform/backups.mdx index b18781e0ec7e7..55b43ec5041c0 100644 --- a/apps/docs/content/guides/platform/backups.mdx +++ b/apps/docs/content/guides/platform/backups.mdx @@ -26,7 +26,7 @@ Database backups can be categorized into two types: **logical** and **physical** To enable physical backups, you have three options: - Enable [Point-in-Time Recovery (PITR)](#point-in-time-recovery) -- [Increase your disk size](/docs/guides/platform/database-size) to greater than 15GB +- [Increase your database size](/docs/guides/platform/database-size) to greater than 15GB - [Create a read replica](/docs/guides/platform/read-replicas) Once a project satisfies at least one of the requirements for physical backups then logical backups are no longer made. However, your project may revert back to logical backups if you remove add-ons. From d515fb66f27b82ccaf36c0e2376c703072701bc7 Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:42:27 +0200 Subject: [PATCH 07/10] feat: add err handling to auth overview (#39589) err handling --- .../Auth/Overview/OverviewUsage.tsx | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx b/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx index 2c2e7f9c22aa2..4fd3fd697ba22 100644 --- a/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx +++ b/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx @@ -25,6 +25,7 @@ import { } from './OverviewUsage.constants' import { useQuery } from '@tanstack/react-query' import dayjs from 'dayjs' +import AlertError from 'components/ui/AlertError' export const StatCard = ({ title, @@ -95,13 +96,21 @@ export const StatCard = ({ export const OverviewUsage = () => { const { ref } = useParams() - const { data: currentData, isLoading: currentLoading } = useQuery({ + const { + data: currentData, + isLoading: currentLoading, + error: currentError, + } = useQuery({ queryKey: ['auth-metrics', ref, 'current'], queryFn: () => fetchAllAuthMetrics(ref as string, 'current'), enabled: !!ref, }) - const { data: previousData, isLoading: previousLoading } = useQuery({ + const { + data: previousData, + isLoading: previousLoading, + error: previousError, + } = useQuery({ queryKey: ['auth-metrics', ref, 'previous'], queryFn: () => fetchAllAuthMetrics(ref as string, 'previous'), enabled: !!ref, @@ -109,6 +118,11 @@ export const OverviewUsage = () => { const metrics = processAllAuthMetrics(currentData?.result || [], previousData?.result || []) const isLoading = currentLoading || previousLoading + const isError = !!previousError || !!currentError + const errorMessage = + (previousError as any)?.message || + (currentError as any)?.message || + 'There was an error fetching the auth metrics.' const activeUsersChange = calculatePercentageChange( metrics.current.activeUsers, @@ -124,6 +138,15 @@ export const OverviewUsage = () => { return ( + {isError && ( + + )}
Usage Date: Thu, 16 Oct 2025 11:07:48 +0200 Subject: [PATCH 08/10] auth overview: adds query id (#39591) add --- .../interfaces/Auth/Overview/OverviewUsage.constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.constants.ts b/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.constants.ts index 0e858125dfa3d..eb9977bf3f9f8 100644 --- a/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.constants.ts +++ b/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.constants.ts @@ -15,6 +15,7 @@ export const getDateRanges = () => { } export const AUTH_COMBINED_QUERY = () => ` +-- auth-overview with base as ( select json_value(event_message, "$.auth_event.action") as action, From c60d81805d4d92a60d747f2840f19ae1cb7c471a Mon Sep 17 00:00:00 2001 From: Monica Khoury <99693443+monicakh@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:46:37 +0300 Subject: [PATCH 09/10] Docs: expand 'Dropping a trigger' section for restricted schemas (#39592) --- apps/docs/content/guides/database/postgres/triggers.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/docs/content/guides/database/postgres/triggers.mdx b/apps/docs/content/guides/database/postgres/triggers.mdx index e405dfff54943..81cc59964aaef 100644 --- a/apps/docs/content/guides/database/postgres/triggers.mdx +++ b/apps/docs/content/guides/database/postgres/triggers.mdx @@ -109,6 +109,14 @@ You can delete a trigger using the `drop trigger` command: drop trigger "trigger_name" on "table_name"; ``` +If your trigger is inside a restricted schema, you won't be able to drop it due to permission restrictions. In those cases, you can drop the function it depends on instead using the CASCADE clause to automatically remove all triggers that call it: + +```sql +drop function if exists restricted_schema.function_name() cascade; +``` + +Make sure you take a backup of the function before removing it in case you're planning to recreate it later. + ## Resources - Official Postgres Docs: [Triggers](https://www.postgresql.org/docs/current/triggers.html) From 5e850ef3338ec1b67ac4e40ba6d3e3f949525c98 Mon Sep 17 00:00:00 2001 From: "kemal.earth" <606977+kemaldotearth@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:00:30 +0100 Subject: [PATCH 10/10] fix(ui): dialog close clickable area (#39163) * fix: dialog close clickable area Small fix for a customer who pointed out the area that looks clickable isnt * feat: try alans suggestion with pseudo element * feat: add pseudo area for sheets too --- packages/ui/src/components/shadcn/ui/dialog.tsx | 9 +++++++-- packages/ui/src/components/shadcn/ui/sheet.tsx | 7 ++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/shadcn/ui/dialog.tsx b/packages/ui/src/components/shadcn/ui/dialog.tsx index bdd8db2a63d84..98f625f482440 100644 --- a/packages/ui/src/components/shadcn/ui/dialog.tsx +++ b/packages/ui/src/components/shadcn/ui/dialog.tsx @@ -102,8 +102,13 @@ const DialogContent = React.forwardRef< > {children} {!hideClose && ( - - + + Close )} diff --git a/packages/ui/src/components/shadcn/ui/sheet.tsx b/packages/ui/src/components/shadcn/ui/sheet.tsx index 0330af9409312..b6803b39d6ef5 100644 --- a/packages/ui/src/components/shadcn/ui/sheet.tsx +++ b/packages/ui/src/components/shadcn/ui/sheet.tsx @@ -167,7 +167,12 @@ const SheetContent = React.forwardRef< > {children} {showClose ? ( - + Close