From 602b879e4aa26290a09296fd475307fc722c3eb8 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 12 May 2026 18:21:03 +0800 Subject: [PATCH 1/7] Fix table editor editing a row, setting a timestamptz column to NULL doesn't work (#45824) ## Context There's a bug atm with the Table Editor whereby hitting "Set to NULL" on a date time format column, doesn't set it to `null` but rather an empty string - which then causes an error when trying to update the row, so this PR addresses that image ## Summary by CodeRabbit * **Bug Fixes** * Fixed date/time input field handling to properly support NULL value assignment. The "Set to NULL" action now correctly applies NULL values instead of empty strings in the table editor. [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45824) --- .../SidePanelEditor/RowEditor/DateTimeInput/index.tsx | 4 ++-- .../TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/DateTimeInput/index.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/DateTimeInput/index.tsx index f7e280f896e0a..84032262ea4af 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/DateTimeInput/index.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/DateTimeInput/index.tsx @@ -19,7 +19,7 @@ interface DateTimeInputProps { value: string isNullable: boolean description: string | ReactNode - onChange: (value: string) => void + onChange: (value: string | null) => void disabled?: boolean } @@ -65,7 +65,7 @@ export const DateTimeInput = ({ {isNullable && ( - onChange('')}>Set to NULL + onChange(null)}>Set to NULL )} diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx index c1c0289d9be20..300f170d6edee 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx @@ -276,7 +276,7 @@ export const InputField = ({ {field.comment &&

{field.comment}

} } - onChange={(value: any) => onUpdateField({ [field.name]: value })} + onChange={(value: string | null) => onUpdateField({ [field.name]: value })} disabled={!isEditable} /> ) From 52237e1ae481066fed60a899357a911658b3b95f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 12 May 2026 21:28:10 +0800 Subject: [PATCH 2/7] Open advanced settings + scroll to that collapsible if submitting has an error (#45818) ## Context Compute and Disk page: There's an odd scenario whereby down sizing the compute might involve some validation errors in the advanced settings, in which case because the advanced settings is in a collapsible and requires the user to scroll down, there's hence no visual indication of where the error is until the user opens the advanced settings to see the inline error. Am hence opting to open the advanced settings + scroll to it for this particular scenario, if submitting the form causes a validation issue on either IOPS, throughput or max disk size field inputs (the rest in this section do not have validation checks on that afaict and hence wouldn't be applicable to this scenario) ## To test Can be tested locally - Upsize compute to 8XL + change throughput to 750 - Once completed, try to down size to XL and hit "review changes" - It should be clear from a UX POV if there are any errors Also added an unrelated nit tidy up for ExitSurveyModal ## Summary by CodeRabbit * **Bug Fixes** * Advanced disk settings now auto-open and scroll into view (with a brief delay) when validation errors occur for throughput, IOPS, or size, improving error visibility. * **User Interface** * Improved advanced settings panel animation and separators for clearer visual boundaries. * Updated project deletion layout for the reason chooser and message input, providing a more consistent form appearance. [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45818) --- .../DiskManagement/DiskManagementForm.tsx | 103 +++++++++++------- .../DeleteProjectPanel/DeleteProjectModal.tsx | 36 +++--- 2 files changed, 81 insertions(+), 58 deletions(-) diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index 76c075fbf7e22..f105c7d6e6d15 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -3,7 +3,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { AnimatePresence, motion } from 'framer-motion' import { ChevronRight } from 'lucide-react' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useForm } from 'react-hook-form' import { CloudProvider } from 'shared-data' import { toast } from 'sonner' @@ -13,6 +13,7 @@ import { Collapsible_Shadcn_, CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_, + DialogSectionSeparator, Form, Separator, } from 'ui' @@ -77,6 +78,8 @@ export function DiskManagementForm() { const { data: org } = useSelectedOrganizationQuery() const { setProjectStatus } = useSetProjectStatus() + const advancedSettingsRef = useRef(null) + const isSpendCapEnabled = org?.plan.id !== 'free' && !org?.usage_billing_enabled && project?.cloud_provider !== 'FLY' @@ -163,43 +166,8 @@ export function DiskManagementForm() { reValidateMode: 'onChange', }) - useEffect(() => { - if (!isDiskAttributesSuccess) return - // @ts-ignore - const { type, iops, throughput_mbps, size_gb } = data?.attributes ?? { size_gb: 0 } - const formValues = { - storageType: type, - provisionedIOPS: iops, - throughput: throughput_mbps, - totalSize: size_gb, - computeSize: form.getValues('computeSize'), - } - - if (!('requested_modification' in data)) { - if (refetchInterval !== false) { - form.reset(formValues) - setRefetchInterval(false) - toast.success('Disk configuration changes have been successfully applied!') - } - } else { - setRefetchInterval(2000) - } - }, [data, isDiskAttributesSuccess, form, refetchInterval]) - const { computeSize: modifiedComputeSize } = form.watch() - // We only support disk configurations for >=Large instances - // If a customer downgrades back to { - if (modifiedComputeSize && project?.infra_compute_size && isDialogOpen) { - if (RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize)) { - form.setValue('storageType', DiskType.GP3) - form.setValue('throughput', DISK_LIMITS['gp3'].minThroughput) - form.setValue('provisionedIOPS', DISK_LIMITS['gp3'].minIops) - } - } - }, [modifiedComputeSize, isDialogOpen, project]) - const isSuccess = isAddonsSuccess && isDiskAttributesSuccess && @@ -213,6 +181,7 @@ export function DiskManagementForm() { const isPlanUpgradeRequired = !hasAccess const { formState } = form + const errors = formState.errors const usedSize = Math.round(((diskUtil?.metrics.fs_used_bytes ?? 0) / GB) * 100) / 100 const totalSize = formState.defaultValues?.totalSize || 0 const usedPercentage = (usedSize / totalSize) * 100 @@ -328,6 +297,41 @@ export function DiskManagementForm() { } } + useEffect(() => { + if (!isDiskAttributesSuccess) return + // @ts-ignore + const { type, iops, throughput_mbps, size_gb } = data?.attributes ?? { size_gb: 0 } + const formValues = { + storageType: type, + provisionedIOPS: iops, + throughput: throughput_mbps, + totalSize: size_gb, + computeSize: form.getValues('computeSize'), + } + + if (!('requested_modification' in data)) { + if (refetchInterval !== false) { + form.reset(formValues) + setRefetchInterval(false) + toast.success('Disk configuration changes have been successfully applied!') + } + } else { + setRefetchInterval(2000) + } + }, [data, isDiskAttributesSuccess, form, refetchInterval]) + + // We only support disk configurations for >=Large instances + // If a customer downgrades back to { + if (modifiedComputeSize && project?.infra_compute_size && isDialogOpen) { + if (RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize)) { + form.setValue('storageType', DiskType.GP3) + form.setValue('throughput', DISK_LIMITS['gp3'].minThroughput) + form.setValue('provisionedIOPS', DISK_LIMITS['gp3'].minIops) + } + } + }, [modifiedComputeSize, isDialogOpen, project]) + useEffect(() => { // Initialize field values properly when data has been loaded, preserving any user changes if (isDiskAttributesSuccess || isSuccess) { @@ -336,6 +340,26 @@ export function DiskManagementForm() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSuccess, isDiskAttributesSuccess]) + useEffect(() => { + const fieldErrors = Object.keys(errors) + if (fieldErrors.length > 0) { + if ( + fieldErrors.includes('throughput') || + fieldErrors.includes('provisionedIOPS') || + fieldErrors.includes('maxSizeGb') + ) { + setAdvancedSettingsOpenState(true) + + // [Joshen] The timeout is to let the collapsible open prior to scrolling + const timeoutId = setTimeout(() => { + advancedSettingsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }) + }, 100) + + return () => clearTimeout(timeoutId) + } + } + }, [errors]) + return ( <> @@ -447,8 +471,6 @@ export function DiskManagementForm() { setAdvancedSettingsOpenState((prev) => !prev)} > @@ -467,16 +489,17 @@ export function DiskManagementForm() { />
- +
+ {/* [Joshen] This is basically ExitSurvey.tsx, ideally we have one shared component but the one in ExitSurvey has a Form wrapped around it already. Will probably need some effort to refactor but leaving that for the future. */} {!isFree && ( - <> -
-

What made you decide to delete your project?

-
-
+
+
{shuffledReasons.map((option) => { const active = selectedReason[0] === option.value @@ -181,19 +183,17 @@ export const DeleteProjectModal = ({ ) })}
-
- -