mirror of
https://github.com/supabase/supabase.git
synced 2026-07-24 08:30:05 -04:00
8935043a1b
## What kind of change does this PR introduce? Studio UI cleanup. ## What is the current behaviour? The SQL editor query warning modal repeats itself with an admonition, a nested warning panel, and extra confirmation copy. Single-warning cases are harder to scan than they need to be. ## What is the new behaviour? The warning now uses `AlertDialog` and resolves each detected issue into concise title/body copy. Single-warning cases show one short description, while multi-warning cases show a compact list. The existing RLS actions are preserved. ## Testing instructions Manual SQL editor checks: Open the Studio SQL editor, paste each snippet, click **Run**, verify the warning copy, then click **Cancel**. These snippets are only intended to trigger the warning UI, so do not confirm the dangerous ones. Destructive operation warning: ```sql drop table if exists public.codex_warning_destructive; ``` UPDATE without WHERE warning: ```sql update public.codex_warning_table set id = id; ``` Prevent database connections warning: ```sql alter database postgres connection limit 0; ``` Missing RLS warning: ```sql create schema if not exists codex_warning; create table codex_warning.missing_rls (id bigint); ``` Multiple issues warning: ```sql drop table if exists public.codex_warning_destructive; update public.codex_warning_table set id = id; create schema if not exists codex_warning; create table codex_warning.missing_rls_multi (id bigint); ``` ## Additional context | Before | After | | --- | --- | | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-BDBD32C7-FCE8-4623-ACF2-D2554233EBB4" src="https://github.com/user-attachments/assets/aaed16c2-9910-424a-8a3c-f9815139b1bf" /> | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-C8964C98-1CF9-4992-89D6-86C081C884E8" src="https://github.com/user-attachments/assets/d291c559-1e64-4c63-b918-b20b58d9a2a5" /> | | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-C341A032-B5B0-49A2-8EA2-E3E6EEC54E4F" src="https://github.com/user-attachments/assets/667d9d1a-e34b-4411-9f91-4972ee8d1a23" /> | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-FC66ADE9-6AF1-44D2-A6B6-F7B2FC935C0E" src="https://github.com/user-attachments/assets/1348377e-6606-47c0-aa95-128d7f86ed56" /> | | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-43AAD9FA-7FAC-4DCE-A713-00E8FC76B343" src="https://github.com/user-attachments/assets/bdadedc2-f17d-4011-ae67-5248097b3e92" /> | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-279EC81D-31C7-49C7-B4A8-EEEF1738740A" src="https://github.com/user-attachments/assets/0c178fff-ff49-4522-870d-7a3401c6af30" /> | | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-D405AED5-613F-4C78-909E-F718C67CF17E" src="https://github.com/user-attachments/assets/a4399935-3596-471b-854a-c689e2e0df07" /> | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-725FBCE6-5606-4BC3-B13F-6210DBADF6F2" src="https://github.com/user-attachments/assets/0dcd08fc-cfb4-4d67-b167-eb6eaa768764" /> | | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-EA6563AF-DE7E-4AB8-9164-AC66164CA581" src="https://github.com/user-attachments/assets/ef56fe0f-0243-4ff3-a2b2-ee8b3fe2330a" /> | <img width="1024" height="759" alt="Codex Warning Table Maintenance SQL Editor Hammer Toolshed Supabas-EC85EFB1-715E-4841-BBCA-51F88B539595" src="https://github.com/user-attachments/assets/5be05527-920d-4f23-92eb-c7cd0bbff13e" /> | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Prevented unintended modal dismissal and duplicate handling when confirming. * **Refactor** * Redesigned SQL editor warning modal with structured, consolidated warnings, adaptive title/confirmation copy, and centralized handling of missing-RLS table names. * Added conditional "Run and enable RLS" confirmation when available. * **Tests** * Updated end-to-end tests to match the new modal headings and body text. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45912) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
189 lines
5.6 KiB
TypeScript
189 lines
5.6 KiB
TypeScript
import { useCallback, useEffect, useRef, type ReactNode } from 'react'
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from 'ui'
|
|
|
|
import { type PotentialIssues } from './SQLEditor.types'
|
|
|
|
interface RunQueryWarningModalProps {
|
|
visible: boolean
|
|
potentialIssues: PotentialIssues | undefined
|
|
onCancel: () => void
|
|
onConfirm: () => void
|
|
onConfirmWithRLS?: () => void
|
|
}
|
|
|
|
type WarningMessage = {
|
|
id: string
|
|
summary: ReactNode
|
|
description: ReactNode
|
|
}
|
|
|
|
type MissingRLSTable = NonNullable<PotentialIssues['createTablesMissingRLS']>[number]
|
|
|
|
const getMissingRLSTableName = (table: MissingRLSTable) =>
|
|
table.schema ? `${table.schema}.${table.tableName}` : table.tableName
|
|
|
|
export const RunQueryWarningModal = ({
|
|
visible,
|
|
potentialIssues,
|
|
onCancel,
|
|
onConfirm,
|
|
onConfirmWithRLS,
|
|
}: RunQueryWarningModalProps) => {
|
|
const {
|
|
hasDestructiveOperations,
|
|
hasUpdateWithoutWhere,
|
|
hasAlterDatabasePreventConnection,
|
|
createTablesMissingRLS,
|
|
} = potentialIssues || {}
|
|
|
|
const missingRLSTables = createTablesMissingRLS ?? []
|
|
const hasMissingRLS = missingRLSTables.length > 0
|
|
const isConfirmingRef = useRef(false)
|
|
|
|
useEffect(() => {
|
|
if (visible) {
|
|
isConfirmingRef.current = false
|
|
}
|
|
}, [visible])
|
|
|
|
const handleOpenChange = useCallback(
|
|
(open: boolean) => {
|
|
if (open) return
|
|
|
|
if (isConfirmingRef.current) {
|
|
isConfirmingRef.current = false
|
|
return
|
|
}
|
|
|
|
onCancel()
|
|
},
|
|
[onCancel]
|
|
)
|
|
|
|
const handleConfirm = useCallback(() => {
|
|
isConfirmingRef.current = true
|
|
onConfirm()
|
|
}, [onConfirm])
|
|
|
|
const handleConfirmWithRLS = useCallback(() => {
|
|
if (!onConfirmWithRLS) return
|
|
|
|
isConfirmingRef.current = true
|
|
onConfirmWithRLS()
|
|
}, [onConfirmWithRLS])
|
|
|
|
const warnings: WarningMessage[] = []
|
|
|
|
if (hasDestructiveOperations) {
|
|
warnings.push({
|
|
id: 'destructive-operations',
|
|
summary: 'This query includes destructive operations',
|
|
description: 'It may permanently change or remove data, tables, schemas, or other objects.',
|
|
})
|
|
}
|
|
|
|
if (hasUpdateWithoutWhere) {
|
|
warnings.push({
|
|
id: 'update-without-where',
|
|
summary: (
|
|
<>
|
|
This query runs an <code className="text-code-inline">UPDATE</code> without a{' '}
|
|
<code className="text-code-inline">WHERE</code> clause
|
|
</>
|
|
),
|
|
description: 'It may update every row in the target table.',
|
|
})
|
|
}
|
|
|
|
if (hasAlterDatabasePreventConnection) {
|
|
warnings.push({
|
|
id: 'prevent-database-connections',
|
|
summary: 'This query may prevent new database connections',
|
|
description:
|
|
'The dashboard may lose access until the setting is restored from a direct database connection.',
|
|
})
|
|
}
|
|
|
|
if (hasMissingRLS) {
|
|
const tableName =
|
|
missingRLSTables.length === 1 ? getMissingRLSTableName(missingRLSTables[0]) : undefined
|
|
|
|
warnings.push({
|
|
id: 'missing-rls',
|
|
summary:
|
|
missingRLSTables.length === 1
|
|
? 'This query creates a table without enabling Row Level Security'
|
|
: 'This query creates tables without enabling Row Level Security',
|
|
description: (
|
|
<>
|
|
Clients using anon or authenticated keys may be able to access{' '}
|
|
{tableName ? <code className="text-code-inline">{tableName}</code> : 'these tables'}.
|
|
</>
|
|
),
|
|
})
|
|
}
|
|
|
|
const canEnableRLS = hasMissingRLS && onConfirmWithRLS !== undefined
|
|
const confirmationCopy = canEnableRLS
|
|
? warnings.length > 1
|
|
? 'Review each issue, then choose whether to enable Row Level Security before running this query.'
|
|
: 'Choose whether to enable Row Level Security before running this query.'
|
|
: 'Run this query only if you intend these changes and understand the risks.'
|
|
const title = warnings.length > 1 ? 'Potential issues detected' : 'Potential issue detected'
|
|
|
|
return (
|
|
<AlertDialog open={visible} onOpenChange={handleOpenChange}>
|
|
<AlertDialogContent size="small">
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>{title}</AlertDialogTitle>
|
|
<AlertDialogDescription asChild>
|
|
{warnings.length === 0 ? (
|
|
<div>
|
|
<p>Are you sure you want to run this query?</p>
|
|
</div>
|
|
) : warnings.length === 1 ? (
|
|
<div>
|
|
<p>
|
|
{warnings[0].summary}. {warnings[0].description}
|
|
</p>
|
|
<p className="mt-3">{confirmationCopy}</p>
|
|
</div>
|
|
) : (
|
|
<div>
|
|
<p>This query has multiple potential issues:</p>
|
|
<ul>
|
|
{warnings.map((warning) => (
|
|
<li key={warning.id} className="mt-3">
|
|
<span className="font-medium text-foreground">{warning.summary}.</span>{' '}
|
|
<span>{warning.description}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<p className="mt-3">{confirmationCopy}</p>
|
|
</div>
|
|
)}
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
<AlertDialogAction variant="warning" onClick={handleConfirm}>
|
|
{canEnableRLS ? 'Run without RLS' : 'Run query'}
|
|
</AlertDialogAction>
|
|
{canEnableRLS && (
|
|
<AlertDialogAction onClick={handleConfirmWithRLS}>Run and enable RLS</AlertDialogAction>
|
|
)}
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
)
|
|
}
|