diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.test.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.test.tsx index f605e6e328f1..88d472f74a15 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/index.test.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/index.test.tsx @@ -1879,6 +1879,63 @@ describe('DatabaseModal', () => { expect(screen.getByTestId('sqlalchemy-uri-input')).toBeInTheDocument(); }); + test('keeps dynamic form values when toggling to the URI form and back', async () => { + setup(); + await selectPostgres(); + + const values: Record = { + host: 'db.example.com', + port: '5432', + database: 'example_db', + username: 'example_user', + password: 'example_password', + }; + Object.entries(values).forEach(([name, value]) => { + fireEvent.change(fieldByName(name), { target: { value } }); + expect(fieldByName(name)).toHaveDisplayValue(value); + }); + + await userEvent.click(screen.getByTestId('sqla-connect-btn')); + expect( + await screen.findByTestId('sqlalchemy-uri-input'), + ).toBeInTheDocument(); + await userEvent.click( + screen.getByRole('button', { + name: /connect this database using the dynamic form instead/i, + }), + ); + + expect(await screen.findByText(/step 2 of 3/i)).toBeInTheDocument(); + Object.entries(values).forEach(([name, value]) => { + expect(fieldByName(name)).toHaveDisplayValue(value); + }); + expect(fieldByName('database_name')).toHaveDisplayValue('PostgreSQL'); + }); + + test('keeps a typed SQLAlchemy URI when toggling to the dynamic form and back', async () => { + setup(); + await selectPostgres(); + + await userEvent.click(screen.getByTestId('sqla-connect-btn')); + const typedUri = + 'postgresql://example_user:example_password@db.example.com:5432/example_db'; + const uriInput = await screen.findByTestId('sqlalchemy-uri-input'); + await userEvent.type(uriInput, typedUri); + expect(uriInput).toHaveValue(typedUri); + + await userEvent.click( + screen.getByRole('button', { + name: /connect this database using the dynamic form instead/i, + }), + ); + expect(await screen.findByText(/step 2 of 3/i)).toBeInTheDocument(); + await userEvent.click(screen.getByTestId('sqla-connect-btn')); + + expect(await screen.findByTestId('sqlalchemy-uri-input')).toHaveValue( + typedUri, + ); + }); + test.each([ { field: 'host', @@ -2687,6 +2744,62 @@ describe('dbReducer', () => { }); }); + test('ConfigMethodChange preserves previous fields and applies the new configuration method', () => { + const initialState: Partial = { + database_name: 'Example DB', + engine: 'postgresql', + driver: 'psycopg2', + configuration_method: ConfigurationMethod.DynamicForm, + parameters: { + host: 'db.example.com', + port: 5432, + database: 'example_db', + username: 'example_user', + }, + sqlalchemy_uri: + 'postgresql://example_user@db.example.com:5432/example_db', + masked_encrypted_extra: '{}', + extra: '{"allows_virtual_table_explore":true}', + expose_in_sqllab: true, + engine_information: { supports_file_upload: true }, + }; + const action: DBReducerActionType = { + type: ActionType.ConfigMethodChange, + payload: { + database_name: initialState.database_name, + engine: initialState.engine, + configuration_method: ConfigurationMethod.SqlalchemyUri, + }, + }; + + expect(dbReducer(initialState, action)).toEqual({ + ...initialState, + configuration_method: ConfigurationMethod.SqlalchemyUri, + }); + }); + + test('ConfigMethodChange falls back to the previous database_name and engine', () => { + const initialState: Partial = { + database_name: 'Example DB', + engine: 'postgresql', + configuration_method: ConfigurationMethod.DynamicForm, + }; + const action: DBReducerActionType = { + type: ActionType.ConfigMethodChange, + payload: { + database_name: undefined, + engine: undefined, + configuration_method: ConfigurationMethod.SqlalchemyUri, + }, + }; + + expect(dbReducer(initialState, action)).toEqual({ + database_name: 'Example DB', + engine: 'postgresql', + configuration_method: ConfigurationMethod.SqlalchemyUri, + }); + }); + // Regression test for https://github.com/apache/superset/issues/30504 // When creating a database, the POST response doesn't include engine_information, // but it should be preserved from the state populated when the user selects a diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.tsx index c8e41e957d09..96435a7fec95 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx @@ -634,8 +634,14 @@ export function dbReducer( expose_in_sqllab: true, }; case ActionType.ConfigMethodChange: + // Switching connection mode keeps everything already entered so that + // toggling back and forth does not discard the user's input. return { + ...trimmedState, ...action.payload, + database_name: + action.payload.database_name ?? trimmedState.database_name, + engine: action.payload.engine ?? trimmedState.engine, }; case ActionType.Reset: