diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx index 6021d099399..e08fc2c0299 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx @@ -5,11 +5,10 @@ // with timeouts and a lot of unnecessary defensive guards - but these can go away when we port // the component over to the UI library +import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { noop } from 'lodash' import { List } from 'lucide-react' import { useEffect, useRef, useState } from 'react' - -import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { DropdownMenu, DropdownMenuContent, @@ -19,6 +18,7 @@ import { DropdownMenuTrigger, Input, } from 'ui' + import type { Suggestion } from './ColumnEditor.types' const MAX_SUGGESTIONS = 3 @@ -38,6 +38,7 @@ interface InputWithSuggestionsProps { onChange: (event: any) => void onSelectSuggestion: (suggestion: Suggestion) => void 'data-testid'?: string + 'aria-label'?: string } const InputWithSuggestions = ({ @@ -55,6 +56,7 @@ const InputWithSuggestions = ({ onChange = noop, onSelectSuggestion = noop, 'data-testid': dataTestId, + 'aria-label': ariaLabel, }: InputWithSuggestionsProps) => { const ref = useRef(null) const [filteredSuggestions, setFilteredSuggestions] = useState(suggestions) @@ -83,6 +85,7 @@ const InputWithSuggestions = ({
{ // create a new table await page.getByRole('button', { name: 'New table' }).click() - await page.getByLabel('Name').fill(databaseTableNameNew) + await page.getByLabel('Name', { exact: true }).fill(databaseTableNameNew) const createTableWait = createApiResponseWaiter( page, 'pg-meta', diff --git a/e2e/studio/features/table-editor.spec.ts b/e2e/studio/features/table-editor.spec.ts index 4785c135a26..f1d5065965f 100644 --- a/e2e/studio/features/table-editor.spec.ts +++ b/e2e/studio/features/table-editor.spec.ts @@ -90,17 +90,16 @@ testRunner('table editor', () => { .getByRole('button') .nth(2) .click() - const schemaPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-definition-') await page.getByRole('menuitem', { name: 'Copy table schema' }).click() - await schemaPromise // wait for endpoint to generate schema - await page.waitForTimeout(500) - const copiedSchemaResult = await page.evaluate(() => navigator.clipboard.readText()) - expect(copiedSchemaResult).toBe(`create table public.pw_table_actions ( + await expect(async () => { + const copiedSchemaResult = await page.evaluate(() => navigator.clipboard.readText()) + expect(copiedSchemaResult).toBe(`create table public.pw_table_actions ( id bigint generated by default as identity not null, created_at timestamp with time zone null default now(), pw_column text null, constraint pw_table_actions_pkey primary key (id) ) TABLESPACE pg_default;`) + }).toPass({ timeout: 2000 }) // duplicates table await page @@ -213,7 +212,7 @@ testRunner('table editor', () => { await page.getByText('Is Nullable').click() await page.getByTestId('created_at-extra-options').click() await page.getByRole('button', { name: 'Add column' }).click() - await page.getByRole('textbox', { name: 'column_name' }).fill(columnNameEnum) + await page.getByLabel('Column name').nth(2).fill(columnNameEnum) await page.getByRole('combobox').filter({ hasText: 'Choose a column type...' }).click() await page.getByPlaceholder('Search types...').fill(enum_name) // wait for response, then click @@ -302,7 +301,7 @@ testRunner('table editor', () => { .click() await page.getByRole('menuitem', { name: 'Edit table' }).click() await page.getByTestId('table-name-input').fill(tableNameUpdated) - await page.getByRole('textbox', { name: 'pw_column' }).fill(columnNameUpdated) + await page.getByLabel('Column name').nth(2).fill(columnNameUpdated) const updateTablePromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=column-update', { method: 'POST', }) @@ -814,7 +813,7 @@ testRunner('table editor', () => { // Add boolean column await page.getByRole('button', { name: 'Add column' }).click() - await page.getByRole('textbox', { name: 'column_name' }).fill(boolColName) + await page.getByLabel('Column name').nth(3).fill(boolColName) await page.getByText('Choose a column type...').click() await page.getByPlaceholder('Search types...').fill('bool') await page.getByRole('option', { name: 'bool' }).first().click() @@ -928,7 +927,7 @@ testRunner('table editor', () => { // Add nullable boolean column await page.getByRole('button', { name: 'Add column' }).click() - await page.getByRole('textbox', { name: 'column_name' }).fill(boolColName) + await page.getByLabel('Column name').nth(3).fill(boolColName) await page.getByText('Choose a column type...').click() await page.getByPlaceholder('Search types...').fill('bool') await page.getByRole('option', { name: 'bool' }).first().click() @@ -1214,4 +1213,49 @@ testRunner('table editor', () => { ).toBeVisible() await expect(page.getByRole('gridcell', { name: 'drag drop value 1' })).toBeVisible() }) + + test('create a table in a single transaction', async ({ page, ref }) => { + const tableName = 'pw_table_create_transaction' + + await using _ = await withSetupCleanup( + async () => { + // Nothing + }, + async () => { + await dropTable(tableName) + } + ) + await page.goto(toUrl(`/project/${ref}/editor?schema=public`)) + await page.getByRole('button', { name: 'New table' }).click() + await page.getByLabel('Name', { exact: true }).fill(tableName) + await page.getByRole('button', { name: 'Add column' }).click() + await page.getByLabel('Column name').nth(2).fill('pw_column') + await page.getByRole('combobox').filter({ hasText: 'Choose a column type...' }).click() + await page.getByRole('option').filter({ hasText: 'int8' }).click() + await page.getByLabel('Column default value').nth(2).fill('invalid') + + await page.getByRole('button', { name: 'Save' }).click() + await expect(page.getByText('invalid input syntax')).toBeVisible() + await page.getByLabel('Column default value').nth(2).fill('10') + await page.getByRole('button', { name: 'Save' }).click() + await expect(page.getByText(`Table ${tableName} is good to go!`)).toBeVisible() + await expect(page.getByRole('button', { name: `View ${tableName}`, exact: true })).toBeVisible() + + // copies table schema to clipboard when copy schema option is clicked + await page + .getByRole('button', { name: `View ${tableName}`, exact: true }) + .getByRole('button') + .nth(2) + .click() + await page.getByRole('menuitem', { name: 'Copy table schema' }).click() + await expect(async () => { + const copiedSchemaResult = await page.evaluate(() => navigator.clipboard.readText()) + expect(copiedSchemaResult).toBe(`create table public.${tableName} ( + id bigint generated by default as identity not null, + created_at timestamp with time zone not null default now(), + pw_column bigint null default '10'::bigint, + constraint ${tableName}_pkey primary key (id) +) TABLESPACE pg_default;`) + }).toPass({ timeout: 2000 }) + }) }) diff --git a/packages/pg-meta/src/pg-meta-columns.ts b/packages/pg-meta/src/pg-meta-columns.ts index e0f646abb42..34d43ed4c2a 100644 --- a/packages/pg-meta/src/pg-meta-columns.ts +++ b/packages/pg-meta/src/pg-meta-columns.ts @@ -120,6 +120,7 @@ function create({ is_unique = false, comment, check, + no_transaction = false, }: { schema: string table: string @@ -134,6 +135,7 @@ function create({ is_unique?: boolean comment?: string check?: string + no_transaction?: boolean }): { sql: string } { let defaultValueClause = '' if (is_identity) { @@ -165,17 +167,24 @@ function create({ : `COMMENT ON COLUMN ${ident(schema)}.${ident(table)}.${ident(name)} IS ${literal(comment)}` const sql = ` -BEGIN; ALTER TABLE ${ident(schema)}.${ident(table)} ADD COLUMN ${ident(name)} ${typeIdent(type)} ${defaultValueClause} ${isNullableClause} ${isPrimaryKeyClause} ${isUniqueClause} ${checkSql}; - ${commentSql}; -COMMIT;` + ${commentSql};` - return { sql } + if (no_transaction) { + return { sql } + } + + return { + sql: ` + BEGIN; + ${sql}; + COMMIT;`, + } } // TODO: make this more robust - use type_id or type_schema + type_name instead of just type. diff --git a/packages/pg-meta/src/pg-meta-tables.ts b/packages/pg-meta/src/pg-meta-tables.ts index 88782ce1a4f..8ca124bac33 100644 --- a/packages/pg-meta/src/pg-meta-tables.ts +++ b/packages/pg-meta/src/pg-meta-tables.ts @@ -141,14 +141,22 @@ type TableCreateParams = { name: string schema?: string comment?: string | null + no_transaction?: boolean } -function create({ name, schema = 'public', comment }: TableCreateParams): { sql: string } { +function create({ name, schema = 'public', comment, no_transaction = false }: TableCreateParams): { + sql: string +} { const tableSql = `CREATE TABLE ${ident(schema)}.${ident(name)} ();` const commentSql = comment != undefined ? `COMMENT ON TABLE ${ident(schema)}.${ident(name)} IS ${literal(comment)};` : '' + + if (no_transaction) { + const sql = `${tableSql} ${commentSql}` + return { sql } + } const sql = `BEGIN; ${tableSql} ${commentSql} COMMIT;` return { sql } }