mirror of
https://github.com/supabase/supabase.git
synced 2026-09-07 02:20:52 +08:00
Extend the "Copy as Markdown" feature in the Schema Visualizer to include Custom Types/Enums and Row Level Security (RLS) Policies in the generated output. Closes https://github.com/orgs/supabase/discussions/46108 ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Feature — extend Copy as Markdown to include enums and RLS policies ## What is the current behavior? The "Copy as Markdown" function in the Schema Visualizer generates a SCHEMA.md that only includes tables, columns, and entity relationships. Custom Types/Enums and RLS Policies are missing, making the output an incomplete representation of the schema. Related: https://github.com/orgs/supabase/discussions/46108 ## What is the new behavior? The generated markdown now includes two additional sections: **Custom Types / Enums:** - Lists each enum type with its ordered permitted values - Filtered by the currently selected schema **RLS Policies:** - Grouped by table for self-contained readability - Includes policy name, command (SELECT/INSERT/UPDATE/DELETE/ALL), roles, action (PERMISSIVE/RESTRICTIVE), USING expression, and WITH CHECK expression **Example output:** ```markdown ## Custom Types / Enums ### `order_status` `pending` | `processing` | `shipped` | `delivered` ## RLS Policies ### `orders` | Policy | Command | Roles | Action | USING | WITH CHECK | |--------|---------|-------|--------|-------|------------| | `users_own_orders` | SELECT | authenticated | PERMISSIVE | `auth.uid() = user_id` | — | ``` ## Additional context - 3 files changed: `Schemas.utils.ts`, `SchemaGraph.tsx`, `Schemas.utils.test.ts` - New utility functions `getEnumsAsMarkdown()` and `getPoliciesAsMarkdown()` with unit tests - Reuses existing `useEnumeratedTypesQuery` and `useDatabasePoliciesQuery` hooks - No breaking changes — existing markdown output is preserved, new sections are appended <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * "Copy as Markdown" now includes enumerated types and database policies alongside existing schema/table content; policies are included with their rule details and grouped by table. * **Tests** * Added tests covering enum and policy markdown generation, including matching/non-matching schema cases and policy formatting. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46189?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com>
257 lines
6.4 KiB
TypeScript
257 lines
6.4 KiB
TypeScript
import { describe, expect, test } from 'vitest'
|
|
|
|
import {
|
|
getEnumsAsMarkdown,
|
|
getPoliciesAsMarkdown,
|
|
getSchemaAsMarkdown,
|
|
getTableDefinitionAsMarkdown,
|
|
} from './Schemas.utils'
|
|
|
|
describe('Schemas.utils', () => {
|
|
test('getSchemaAsMarkdown returns properly formatted markdown', () => {
|
|
const schema = 'public'
|
|
const tables = [
|
|
{
|
|
ref: 'default',
|
|
id: 20999,
|
|
name: 'test',
|
|
description: 'An excellent description',
|
|
schema: 'public',
|
|
isForeign: false,
|
|
columns: [
|
|
{
|
|
id: '20999.1',
|
|
isPrimary: true,
|
|
name: 'id',
|
|
format: 'int8',
|
|
isNullable: false,
|
|
isUnique: false,
|
|
isUpdateable: true,
|
|
isIdentity: true,
|
|
description: '',
|
|
},
|
|
{
|
|
id: '20999.2',
|
|
isPrimary: false,
|
|
name: 'created_at',
|
|
format: 'timestamptz',
|
|
isNullable: false,
|
|
isUnique: false,
|
|
isUpdateable: true,
|
|
isIdentity: false,
|
|
description: '',
|
|
},
|
|
{
|
|
id: '20999.3',
|
|
isPrimary: false,
|
|
name: 'user_id',
|
|
format: 'uuid',
|
|
isNullable: true,
|
|
isUnique: false,
|
|
isUpdateable: true,
|
|
isIdentity: false,
|
|
description: '',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
ref: 'default',
|
|
id: 21049,
|
|
name: 'test2',
|
|
description: '',
|
|
schema: 'public',
|
|
isForeign: false,
|
|
columns: [
|
|
{
|
|
id: '21049.1',
|
|
isPrimary: true,
|
|
name: 'id',
|
|
format: 'int8',
|
|
isNullable: false,
|
|
isUnique: false,
|
|
isUpdateable: true,
|
|
isIdentity: true,
|
|
description: '',
|
|
},
|
|
{
|
|
id: '21049.2',
|
|
isPrimary: false,
|
|
name: 'created_at',
|
|
format: 'timestamptz',
|
|
isNullable: false,
|
|
isUnique: false,
|
|
isUpdateable: true,
|
|
isIdentity: false,
|
|
description: '',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 21009,
|
|
ref: 'default',
|
|
schema: 'auth',
|
|
name: 'auth.users.id',
|
|
description: '',
|
|
isForeign: true,
|
|
columns: [],
|
|
},
|
|
]
|
|
const result = getSchemaAsMarkdown(schema, tables)
|
|
expect(result).toBe(`## Table \`test\`
|
|
|
|
An excellent description
|
|
|
|
### Columns
|
|
|
|
| Name | Type | Constraints |
|
|
|------|------|-------------|
|
|
| \`id\` | \`int8\` | Primary Identity |
|
|
| \`created_at\` | \`timestamptz\` | |
|
|
| \`user_id\` | \`uuid\` | Nullable |
|
|
|
|
## Table \`test2\`
|
|
|
|
### Columns
|
|
|
|
| Name | Type | Constraints |
|
|
|------|------|-------------|
|
|
| \`id\` | \`int8\` | Primary Identity |
|
|
| \`created_at\` | \`timestamptz\` | |
|
|
|
|
`)
|
|
})
|
|
test('getTableDefinitionAsMarkdown returns properly formatted markdown', () => {
|
|
const table = {
|
|
ref: 'default',
|
|
id: 20999,
|
|
name: 'test',
|
|
description: 'An excellent description',
|
|
schema: 'public',
|
|
isForeign: false,
|
|
columns: [
|
|
{
|
|
id: '20999.1',
|
|
isPrimary: true,
|
|
name: 'id',
|
|
format: 'int8',
|
|
isNullable: false,
|
|
isUnique: false,
|
|
isUpdateable: true,
|
|
isIdentity: true,
|
|
description: '',
|
|
},
|
|
{
|
|
id: '20999.2',
|
|
isPrimary: false,
|
|
name: 'created_at',
|
|
format: 'timestamptz',
|
|
isNullable: false,
|
|
isUnique: false,
|
|
isUpdateable: true,
|
|
isIdentity: false,
|
|
description: '',
|
|
},
|
|
{
|
|
id: '20999.3',
|
|
isPrimary: false,
|
|
name: 'user_id',
|
|
format: 'uuid',
|
|
isNullable: true,
|
|
isUnique: false,
|
|
isUpdateable: true,
|
|
isIdentity: false,
|
|
description: '',
|
|
},
|
|
],
|
|
}
|
|
const result = getTableDefinitionAsMarkdown(table)
|
|
expect(result).toBe(`## Table \`test\`
|
|
|
|
An excellent description
|
|
|
|
### Columns
|
|
|
|
| Name | Type | Constraints |
|
|
|------|------|-------------|
|
|
| \`id\` | \`int8\` | Primary Identity |
|
|
| \`created_at\` | \`timestamptz\` | |
|
|
| \`user_id\` | \`uuid\` | Nullable |
|
|
`)
|
|
})
|
|
|
|
test('getEnumsAsMarkdown returns formatted enum section', () => {
|
|
const enums = [
|
|
{ name: 'order_status', schema: 'public', enums: ['pending', 'processing', 'shipped'] },
|
|
{ name: 'role_type', schema: 'public', enums: ['admin', 'user'] },
|
|
{ name: 'other_enum', schema: 'auth', enums: ['a', 'b'] },
|
|
]
|
|
const result = getEnumsAsMarkdown('public', enums)
|
|
expect(result).toBe(`## Custom Types / Enums
|
|
|
|
### \`order_status\`
|
|
|
|
\`pending\` | \`processing\` | \`shipped\`
|
|
|
|
### \`role_type\`
|
|
|
|
\`admin\` | \`user\`
|
|
|
|
`)
|
|
})
|
|
|
|
test('getEnumsAsMarkdown returns empty string when no enums match schema', () => {
|
|
const enums = [{ name: 'other_enum', schema: 'auth', enums: ['a', 'b'] }]
|
|
const result = getEnumsAsMarkdown('public', enums)
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
test('getPoliciesAsMarkdown returns formatted policy table grouped by table', () => {
|
|
const policies = [
|
|
{
|
|
name: 'users_select',
|
|
schema: 'public',
|
|
table: 'users',
|
|
command: 'SELECT',
|
|
roles: ['authenticated'],
|
|
action: 'PERMISSIVE',
|
|
definition: 'auth.uid() = id',
|
|
check: null,
|
|
},
|
|
{
|
|
name: 'users_insert',
|
|
schema: 'public',
|
|
table: 'users',
|
|
command: 'INSERT',
|
|
roles: ['authenticated'],
|
|
action: 'PERMISSIVE',
|
|
definition: null,
|
|
check: 'auth.uid() = id',
|
|
},
|
|
]
|
|
const result = getPoliciesAsMarkdown('public', policies)
|
|
expect(result).toContain('## RLS Policies')
|
|
expect(result).toContain('### `users`')
|
|
expect(result).toContain('`users_select`')
|
|
expect(result).toContain('`users_insert`')
|
|
expect(result).toContain('`auth.uid() = id`')
|
|
expect(result).toContain('—')
|
|
})
|
|
|
|
test('getPoliciesAsMarkdown returns empty string when no policies match schema', () => {
|
|
const policies = [
|
|
{
|
|
name: 'test',
|
|
schema: 'auth',
|
|
table: 'users',
|
|
command: 'SELECT',
|
|
roles: ['anon'],
|
|
action: 'PERMISSIVE',
|
|
definition: 'true',
|
|
check: null,
|
|
},
|
|
]
|
|
const result = getPoliciesAsMarkdown('public', policies)
|
|
expect(result).toBe('')
|
|
})
|
|
})
|