Files
supabase/apps/studio/components/interfaces/SQLEditor/RunQueryWarningModal.tsx
Danny White 8935043a1b simplify sql editor warning dialog (#45912)
## 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 -->

[![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/45912)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-15 10:00:58 +10:00

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>
)
}