mirror of
https://github.com/supabase/supabase.git
synced 2026-05-06 22:18:00 +08:00
feat: Add table and column menus in schema visualiser (#43693)
## Problem - The schema visualiser lacks editing capabilities which leads to a lot of navigation (ediing tables, columns) - ReactFlow prevents users from selecting table and column names (to copy them). Diasbling drag and pan on those texts would make moving items cumbersome - Long table and column names are hidden and even hide other elements ## Solution - Add menus for both tables and columns - Truncate long names with ellipsis and add a tooltip - Hide menus when exporting to png/svg [Screen Recording 2026-03-12 at 10.10.08.webm](https://github.com/user-attachments/assets/b2780266-e874-41d1-ac82-7c2c4ba5abf2)
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
import { createContext, useContext, type ReactNode } from 'react'
|
||||
|
||||
export type ColumnEditionContextType = {
|
||||
onEditColumn: (tableId: number, columnId: string) => void
|
||||
}
|
||||
|
||||
export const ColumnEditionContext = createContext<ColumnEditionContextType | null>(null)
|
||||
|
||||
export const ColumnEditionContextProvider = ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: ReactNode
|
||||
value: ColumnEditionContextType
|
||||
}) => <ColumnEditionContext.Provider value={value}>{children}</ColumnEditionContext.Provider>
|
||||
|
||||
export const useColumnEditionContext = () => {
|
||||
const context = useContext(ColumnEditionContext)
|
||||
if (!context)
|
||||
throw new Error('useColumnEditionContext must be used inside a <ColumnEditionContextProvider>')
|
||||
return context
|
||||
}
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
|
||||
import { SidePanelEditor } from '../../TableGridEditor/SidePanelEditor/SidePanelEditor'
|
||||
import { ColumnEditionContextProvider, ColumnEditionContextType } from './ColumnEditionContext'
|
||||
import { SchemaGraphContextProvider, SchemaGraphContextType } from './SchemaGraphContext'
|
||||
import { SchemaGraphLegend } from './SchemaGraphLegend'
|
||||
import { getGraphDataFromTables, getLayoutedElementsViaDagre } from './Schemas.utils'
|
||||
import { TableNode } from './SchemaTableNode'
|
||||
@@ -210,10 +210,20 @@ export const SchemaGraph = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [isSuccessTables, isSuccessSchemas, tables, resolvedTheme])
|
||||
}, [
|
||||
isSuccessTables,
|
||||
isSuccessSchemas,
|
||||
tables,
|
||||
reactFlowInstance,
|
||||
ref,
|
||||
resolvedTheme,
|
||||
schemas,
|
||||
selectedSchema,
|
||||
])
|
||||
|
||||
const columnEditionContext = useMemo<ColumnEditionContextType>(
|
||||
const schemaGraphPanelEditorContext = useMemo<SchemaGraphContextType>(
|
||||
() => ({
|
||||
isDownloading,
|
||||
onEditColumn: (tableId, columnId) => {
|
||||
const table = tables.find((table) => table.id === tableId)
|
||||
if (!table || table.columns == null) return
|
||||
@@ -224,8 +234,15 @@ export const SchemaGraph = () => {
|
||||
setSelectedTable(table)
|
||||
snap.onEditColumn(column)
|
||||
},
|
||||
onEditTable: (tableId) => {
|
||||
const table = tables.find((table) => table.id === tableId)
|
||||
if (!table || table.columns == null) return
|
||||
|
||||
setSelectedTable(table)
|
||||
snap.onEditTable()
|
||||
},
|
||||
}),
|
||||
[tables, snap]
|
||||
[tables, snap, isDownloading]
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -349,7 +366,7 @@ export const SchemaGraph = () => {
|
||||
</Admonition>
|
||||
</div>
|
||||
) : (
|
||||
<ColumnEditionContextProvider value={columnEditionContext}>
|
||||
<SchemaGraphContextProvider value={schemaGraphPanelEditorContext}>
|
||||
<div className="w-full h-full">
|
||||
<ReactFlow
|
||||
defaultNodes={[]}
|
||||
@@ -382,7 +399,7 @@ export const SchemaGraph = () => {
|
||||
<SchemaGraphLegend />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</ColumnEditionContextProvider>
|
||||
</SchemaGraphContextProvider>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createContext, useContext, type ReactNode } from 'react'
|
||||
|
||||
export type SchemaGraphContextType = {
|
||||
isDownloading: boolean
|
||||
onEditColumn: (tableId: number, columnId: string) => void
|
||||
onEditTable: (tableId: number) => void
|
||||
}
|
||||
|
||||
export const SchemaGraphContext = createContext<SchemaGraphContextType | null>(null)
|
||||
|
||||
export const SchemaGraphContextProvider = ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: ReactNode
|
||||
value: SchemaGraphContextType
|
||||
}) => <SchemaGraphContext.Provider value={value}>{children}</SchemaGraphContext.Provider>
|
||||
|
||||
export const useSchemaGraphContext = () => {
|
||||
const context = useContext(SchemaGraphContext)
|
||||
if (!context)
|
||||
throw new Error('useSchemaGraphContext must be used inside a <SchemaGraphContextProvider>')
|
||||
return context
|
||||
}
|
||||
@@ -1,19 +1,35 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { buildTableEditorUrl } from 'components/grid/SupabaseGrid.utils'
|
||||
import { TableEditor } from 'icons'
|
||||
import {
|
||||
Copy,
|
||||
DiamondIcon,
|
||||
Edit,
|
||||
ExternalLink,
|
||||
Fingerprint,
|
||||
Hash,
|
||||
InfoIcon,
|
||||
Key,
|
||||
MoreVertical,
|
||||
Table2,
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Handle, NodeProps } from 'reactflow'
|
||||
import { Button, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
copyToClipboard,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
|
||||
import { useColumnEditionContext } from './ColumnEditionContext'
|
||||
import { useSchemaGraphContext } from './SchemaGraphContext'
|
||||
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
|
||||
// ReactFlow is scaling everything by the factor of 2
|
||||
export const TABLE_NODE_WIDTH = 320
|
||||
@@ -46,8 +62,13 @@ export const TableNode = ({
|
||||
// Important styles is a nasty hack to use Handles (required for edges calculations), but do not show them in the UI.
|
||||
// ref: https://github.com/wbkd/react-flow/discussions/2698
|
||||
const hiddenNodeConnector = '!h-px !w-px !min-w-0 !min-h-0 !cursor-grab !border-0 !opacity-0'
|
||||
const columnEditionContext = useColumnEditionContext()
|
||||
|
||||
const schemaGraphContext = useSchemaGraphContext()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const { can: canUpdateColumns } = useAsyncCheckPermissions(
|
||||
PermissionAction.TENANT_SQL_ADMIN_WRITE,
|
||||
'columns'
|
||||
)
|
||||
const router = useRouter()
|
||||
const itemHeight = 'h-[22px]'
|
||||
|
||||
return (
|
||||
@@ -71,38 +92,76 @@ export const TableNode = ({
|
||||
>
|
||||
<header
|
||||
className={cn(
|
||||
'text-[0.55rem] pl-2 pr-1 bg-alternative flex items-center justify-between',
|
||||
'text-[0.55rem] pl-2 pr-1 bg-alternative flex gap-2 items-center justify-between',
|
||||
itemHeight
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-x-1 items-center">
|
||||
<div className="min-w-0 flex flex-shrink gap-x-1 items-center">
|
||||
<Table2 strokeWidth={1} size={12} className="text-light" />
|
||||
{data.name}
|
||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis" title={data.name}>
|
||||
{data.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{data.description && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild className="cursor-default ">
|
||||
<InfoIcon size={10} className="text-light" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">{data.description}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{
|
||||
// Hide the actions while downloading the schema as png/svg
|
||||
!schemaGraphContext.isDownloading ? (
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
{data.description && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild className="cursor-default ">
|
||||
<InfoIcon size={10} className="text-light" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">{data.description}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{!placeholder && (
|
||||
<Button asChild type="text" className="px-0 w-[16px] h-[16px] rounded">
|
||||
<Link
|
||||
href={buildTableEditorUrl({
|
||||
projectRef: data.ref,
|
||||
tableId: data.id,
|
||||
schema: data.schema,
|
||||
})}
|
||||
>
|
||||
<ExternalLink size={10} className="text-foreground-light" />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{!placeholder && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type="text" className="px-0 w-[16px] h-[16px] rounded nodrag nopan">
|
||||
<MoreVertical size={10} />
|
||||
<span className="sr-only">{data.name} actions</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="end" className="w-40">
|
||||
<DropdownMenuItem
|
||||
className="flex items-center space-x-2 whitespace-nowrap"
|
||||
onClick={() => schemaGraphContext.onEditTable(data.id)}
|
||||
>
|
||||
<Edit size={12} />
|
||||
<p>Edit table</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="flex items-center space-x-2 whitespace-nowrap"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
copyToClipboard(data.name)
|
||||
}}
|
||||
>
|
||||
<Copy size={12} />
|
||||
<span>Copy name</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="flex items-center space-x-2 whitespace-nowrap"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
buildTableEditorUrl({
|
||||
projectRef: project?.ref,
|
||||
tableId: data.id,
|
||||
schema: data.schema,
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<TableEditor size={12} />
|
||||
<p>View in Table Editor</p>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</header>
|
||||
|
||||
{data.columns.map((column) => (
|
||||
@@ -117,6 +176,7 @@ export const TableNode = ({
|
||||
'pr-1',
|
||||
itemHeight
|
||||
)}
|
||||
data-testid={`${data.name}/${column.name}`}
|
||||
key={column.id}
|
||||
>
|
||||
<div
|
||||
@@ -154,11 +214,14 @@ export const TableNode = ({
|
||||
<Hash size={8} strokeWidth={1} className="flex-shrink-0 text-light" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-ellipsis overflow-hidden whitespace-nowrap max-w-[85px]">
|
||||
<div className="flex w-full justify-between min-w-0">
|
||||
<span
|
||||
className="text-ellipsis overflow-hidden whitespace-nowrap min-w-0 max-w-[80%]"
|
||||
title={column.name}
|
||||
>
|
||||
{column.name}
|
||||
</span>
|
||||
<span className="pl-2 pr-1 inline-flex justify-end font-mono text-lighter text-[0.4rem] group-hover:hidden">
|
||||
<span className="flex-shrink-0 pl-2 pr-1 inline-flex justify-end font-mono text-lighter text-[0.4rem] group-hover:hidden">
|
||||
{column.format}
|
||||
</span>
|
||||
</div>
|
||||
@@ -178,23 +241,50 @@ export const TableNode = ({
|
||||
className={cn(hiddenNodeConnector, '!right-0')}
|
||||
/>
|
||||
)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="text"
|
||||
className="hidden group-hover:inline-block absolute right-0 px-0 mr-1 w-[16px] h-[16px] rounded"
|
||||
onClick={() => {
|
||||
columnEditionContext.onEditColumn(data.id, column.id)
|
||||
}}
|
||||
// Use opacity to hide the button so that it remains accessible (users can tab to it)
|
||||
className="opacity-0 focus:opacity-100 group-hover:opacity-100 data-[state=open]:opacity-100 absolute right-0 top-1/2 -translate-y-1/2 px-0 mr-1 w-[16px] h-[16px] rounded"
|
||||
>
|
||||
<Edit size={10} className="text-foreground-light" />
|
||||
<MoreVertical size={10} />
|
||||
<span className="sr-only">
|
||||
Edit {data.name} {column.name} column
|
||||
{data.name} {column.name} actions
|
||||
</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Edit column</TooltipContent>
|
||||
</Tooltip>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="end" className="w-32">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
disabled={!canUpdateColumns}
|
||||
onClick={() => schemaGraphContext.onEditColumn(data.id, column.id)}
|
||||
className="space-x-2"
|
||||
>
|
||||
<Edit size={12} />
|
||||
<p>Edit column</p>
|
||||
</DropdownMenuItem>
|
||||
</TooltipTrigger>
|
||||
{!canUpdateColumns && (
|
||||
<TooltipContent side="bottom">
|
||||
Additional permissions required to edit column
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="space-x-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
copyToClipboard(column.name)
|
||||
}}
|
||||
>
|
||||
<Copy size={12} />
|
||||
<span>Copy name</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { GeneratedPolicy } from 'components/interfaces/Auth/Policies/Policies.utils'
|
||||
import { DocsButton } from 'components/ui/DocsButton'
|
||||
import { useDatabasePublicationsQuery } from 'data/database-publications/database-publications-query'
|
||||
import { CONSTRAINT_TYPE, useTableConstraintsQuery } from 'data/database/constraints-query'
|
||||
@@ -329,11 +328,6 @@ export const TableEditor = ({
|
||||
|
||||
if (!tableFields) return null
|
||||
|
||||
const isExposed = isApiGrantTogglesEnabled
|
||||
? !!apiAccessToggleHandler.data?.schemaExposed &&
|
||||
checkDataApiPrivilegesNonEmpty(apiAccessToggleHandler.data.privileges)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<SidePanel
|
||||
data-testid="table-editor-side-panel"
|
||||
@@ -358,6 +352,7 @@ export const TableEditor = ({
|
||||
<Input
|
||||
data-testid="table-name-input"
|
||||
label="Name"
|
||||
id="name"
|
||||
layout="horizontal"
|
||||
type="text"
|
||||
error={errors.name ? String(errors.name) : undefined}
|
||||
@@ -366,6 +361,7 @@ export const TableEditor = ({
|
||||
/>
|
||||
<Input
|
||||
label="Description"
|
||||
id="description"
|
||||
placeholder="Optional"
|
||||
layout="horizontal"
|
||||
type="text"
|
||||
|
||||
@@ -61,12 +61,67 @@ test.describe('Database', () => {
|
||||
await expect(page.getByText('users', { exact: true })).toBeVisible()
|
||||
await expect(page.getByText('sso_providers', { exact: true })).toBeVisible()
|
||||
await expect(page.getByText('saml_providers', { exact: true })).toBeVisible()
|
||||
})
|
||||
|
||||
// navigate to table editor when icon is clicked
|
||||
const samlProvidersHeader = await page.getByText('saml_providers', { exact: true })
|
||||
await samlProvidersHeader.locator('..').getByRole('link').click()
|
||||
test('table actions work as expected', async ({ page, ref }) => {
|
||||
const databaseTableName = 'pw_database_schema_table_actions'
|
||||
const databaseColumnName = 'pw_database_schema_column_table_actions'
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(databaseTableName, databaseColumnName)
|
||||
},
|
||||
async () => {
|
||||
await dropTable(databaseTableName)
|
||||
}
|
||||
)
|
||||
const wait = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'tables?include_columns=true&included_schemas=public'
|
||||
)
|
||||
await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/schemas?schema=public`))
|
||||
await wait
|
||||
|
||||
// validates table and column exists
|
||||
await expect(page.getByText(databaseTableName, { exact: true })).toBeVisible()
|
||||
// test we can edit the column
|
||||
await page.getByText(`${databaseTableName} actions`).click()
|
||||
|
||||
await page.getByText(`${databaseTableName} actions`).click()
|
||||
await expect(page.getByRole('menuitem', { name: 'Edit table' })).toBeVisible()
|
||||
await page.getByRole('menuitem', { name: 'Edit table' }).click({ force: true })
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
await expect(dialog.getByText('timestamptz')).toBeVisible()
|
||||
await page.getByLabel('Description').fill('Bazinga')
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await expect(page.getByText(`Successfully updated ${databaseTableName}!`)).toBeVisible()
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible()
|
||||
|
||||
// test the schema view has been refreshed
|
||||
await page.getByText(`${databaseTableName} actions`).click()
|
||||
await expect(page.getByRole('menuitem', { name: 'Edit table' })).toBeVisible()
|
||||
await page.getByRole('menuitem', { name: 'Edit table' }).click()
|
||||
await expect(page.getByRole('dialog')).toBeVisible()
|
||||
// FIXME: For some reason, the dialog is not stable and rerenders, sometimes preventing the description to be filled
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page.getByLabel('Description')).toHaveValue('Bazinga')
|
||||
await page.getByRole('button', { name: 'Cancel' }).click()
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible()
|
||||
|
||||
await page.getByText(`${databaseTableName} actions`).click()
|
||||
await expect(page.getByRole('menuitem', { name: 'Copy name' })).toBeVisible()
|
||||
await page.getByRole('menuitem', { name: 'Copy name' }).click()
|
||||
await page.waitForTimeout(500)
|
||||
const copiedTableResult = await page.evaluateHandle(() => navigator.clipboard.readText())
|
||||
expect(await copiedTableResult.jsonValue()).toBe(databaseTableName)
|
||||
|
||||
await page.getByText(`${databaseTableName} actions`).click()
|
||||
await expect(page.getByRole('menuitem', { name: 'View in Table Editor' })).toBeVisible()
|
||||
await page.getByRole('menuitem', { name: 'View in Table Editor' }).click()
|
||||
await page.waitForURL(/.*\/editor\/\d+/)
|
||||
await page.getByRole('button', { name: 'View saml_providers', exact: true }).click()
|
||||
await expect(page.getByRole('tab', { name: databaseTableName })).toBeVisible()
|
||||
})
|
||||
|
||||
test('columns actions work as expected', async ({ page, ref }) => {
|
||||
@@ -93,8 +148,11 @@ test.describe('Database', () => {
|
||||
await expect(page.getByText(databaseTableName, { exact: true })).toBeVisible()
|
||||
await expect(page.getByText(databaseColumnName, { exact: true })).toBeVisible()
|
||||
// test we can edit the column
|
||||
await page.getByText(databaseColumnName, { exact: true }).hover()
|
||||
await page.getByText(`Edit ${databaseTableName} ${databaseColumnName} column`).click()
|
||||
await page
|
||||
.getByText(`${databaseTableName} ${databaseColumnName} actions`)
|
||||
.click({ force: true })
|
||||
await expect(page.getByRole('menuitem', { name: 'Edit column' })).toBeVisible()
|
||||
await page.getByRole('menuitem', { name: 'Edit column' }).click()
|
||||
await page.getByLabel('Description').fill('Bazinga')
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await expect(
|
||||
@@ -103,9 +161,23 @@ test.describe('Database', () => {
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible()
|
||||
|
||||
// test the schema view has been refreshed
|
||||
await page.getByText(databaseColumnName, { exact: true }).hover()
|
||||
await page.getByText(`Edit ${databaseTableName} ${databaseColumnName} column`).click()
|
||||
await page
|
||||
.getByText(`${databaseTableName} ${databaseColumnName} actions`)
|
||||
.click({ force: true })
|
||||
await expect(page.getByRole('menuitem', { name: 'Edit column' })).toBeVisible()
|
||||
await page.getByRole('menuitem', { name: 'Edit column' }).click()
|
||||
await expect(page.getByLabel('Description')).toHaveValue('Bazinga')
|
||||
await page.getByRole('button', { name: 'Cancel' }).click()
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible()
|
||||
|
||||
await page
|
||||
.getByText(`${databaseTableName} ${databaseColumnName} actions`)
|
||||
.click({ force: true })
|
||||
await expect(page.getByRole('menuitem', { name: 'Copy name' })).toBeVisible()
|
||||
await page.getByRole('menuitem', { name: 'Copy name' }).click()
|
||||
await page.waitForTimeout(500)
|
||||
const copiedTableResult = await page.evaluateHandle(() => navigator.clipboard.readText())
|
||||
expect(await copiedTableResult.jsonValue()).toBe(databaseColumnName)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -186,7 +258,7 @@ test.describe('Database', () => {
|
||||
|
||||
// create a new table
|
||||
await page.getByRole('button', { name: 'New table' }).click()
|
||||
await page.getByTestId('table-name-input').fill(databaseTableNameNew)
|
||||
await page.getByLabel('Name').fill(databaseTableNameNew)
|
||||
const createTableWait = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
@@ -224,8 +296,8 @@ test.describe('Database', () => {
|
||||
.last()
|
||||
.click()
|
||||
await page.getByRole('menuitem', { name: 'Duplicate Table' }).click()
|
||||
await page.getByTestId('table-name-input').fill(databaseTableNameDuplicate)
|
||||
await page.getByRole('textbox', { name: 'Optional' }).fill('')
|
||||
await page.getByLabel('Name').fill(databaseTableNameDuplicate)
|
||||
await page.getByLabel('Description').fill('')
|
||||
const duplicateTableWait = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=')
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user