Skip to content

Commit

Permalink
feat: Dynamic Form for edit DB Modal (#14845)
Browse files Browse the repository at this point in the history
* split db modal file

* split db modal file

* hook up available databases

* add comment

* split db modal file

* hook up available databases

* use new validation component

* first draft

* use new validation component

* get tests passing

* split db modal file

* hook up available databases

* use new validation component

* feat(db-connection-ui): Allow users to pick engine (#14884)

* poc picker for db selection

* working select

* setup is loading for available dbs and step1 view

* fix on close

* update on fetch

* remove unneeded code

* add some styls

* revisions

* fix package-lock.json

* # This is a combination of 6 commits.
# This is the 1st commit message:

feat: validation db modal (#14832)

* split db modal file

* hook up available databases

* use new validation component
# This is the commit message #2:

feat: Icon Button (#14818)

* Creating IconButton

* Changed naming: logo is now icon

* Hard-coded inset values for ellipses

* Removed default SVG

* Fixed test

* Removed logo from test
# This is the commit message #3:

chore: Improves the native filters UI/UX - iteration 6 (#14932)


# This is the commit message #4:

fix: is_temporal should overwrite is_dttm (#14894)

* fix: is_temporal should overwrite is_dttm

* move up
# This is the commit message #5:

fix: time parser truncate to first day of year/month (#14945)


# This is the commit message #6:

hook up available databases

* fix test for db modal

* feat(db-connection-ui): Allow users to pick engine (#14884)

* poc picker for db selection

* working select

* setup is loading for available dbs and step1 view

* fix on close

* update on fetch

* remove unneeded code

* add some styls

* more revisions

* used db.backend

* added engine to model

* elizabeth revisions

* elizabeth revisions

Co-authored-by: Elizabeth Thompson <[email protected]>
Co-authored-by: hughhhh <[email protected]>
  • Loading branch information
3 people authored Jun 3, 2021
1 parent d5c5167 commit b8e6687
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
* under the License.
*/
import { DATABASE_LIST } from './helper';

// TODO: Add new tests with the modal
describe('Add database', () => {
beforeEach(() => {
cy.login();
});

it('should keep create modal open when error', () => {
xit('should keep create modal open when error', () => {
cy.visit(DATABASE_LIST);

// open modal
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('Add database', () => {
cy.get('[data-test="database-modal"]').should('not.be.visible');
});

it('should keep update modal open when error', () => {
xit('should keep update modal open when error', () => {
// open modal
cy.get('[data-test="database-edit"]:last').click();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import { SupersetTheme, JsonObject } from '@superset-ui/core';
import { InputProps } from 'antd/lib/input';
import ValidatedInput from 'src/components/Form/LabeledErrorBoundInput';
import {
StyledFormHeader,
formScrollableStyles,
validatedFormStyles,
StyledFormHeader,
} from './styles';
import { DatabaseForm } from '../types';
import { DatabaseForm, DatabaseObject } from '../types';

export const FormFieldOrder = [
'host',
Expand All @@ -43,17 +43,20 @@ interface FieldPropTypes {
};
validationErrors: JsonObject | null;
getValidation: () => void;
db?: DatabaseObject;
}

const hostField = ({
required,
changeMethods,
getValidation,
validationErrors,
db,
}: FieldPropTypes) => (
<ValidatedInput
id="host"
name="host"
value={db?.parameters?.host || ''}
required={required}
validationMethods={{ onBlur: getValidation }}
errorMessage={validationErrors?.host}
Expand All @@ -68,11 +71,13 @@ const portField = ({
changeMethods,
getValidation,
validationErrors,
db,
}: FieldPropTypes) => (
<ValidatedInput
id="port"
name="port"
required={required}
value={db?.parameters?.port || ''}
validationMethods={{ onBlur: getValidation }}
errorMessage={validationErrors?.port}
placeholder="e.g. 5432"
Expand All @@ -86,29 +91,33 @@ const databaseField = ({
changeMethods,
getValidation,
validationErrors,
db,
}: FieldPropTypes) => (
<ValidatedInput
id="database"
name="database"
required={required}
value={db?.parameters?.database || ''}
validationMethods={{ onBlur: getValidation }}
errorMessage={validationErrors?.database}
placeholder="e.g. world_population"
label="Database name"
onChange={changeMethods.onParametersChange}
helpText="Copy the name of the PostgreSQL database you are trying to connect to."
helpText="Copy the name of the database you are trying to connect to."
/>
);
const usernameField = ({
required,
changeMethods,
getValidation,
validationErrors,
db,
}: FieldPropTypes) => (
<ValidatedInput
id="username"
name="username"
required={required}
value={db?.parameters?.username || ''}
validationMethods={{ onBlur: getValidation }}
errorMessage={validationErrors?.username}
placeholder="e.g. Analytics"
Expand All @@ -121,11 +130,14 @@ const passwordField = ({
changeMethods,
getValidation,
validationErrors,
db,
}: FieldPropTypes) => (
<ValidatedInput
id="password"
name="password"
required={required}
type="password"
value={db?.parameters?.password || ''}
validationMethods={{ onBlur: getValidation }}
errorMessage={validationErrors?.password}
placeholder="e.g. ********"
Expand All @@ -138,11 +150,13 @@ const displayField = ({
changeMethods,
getValidation,
validationErrors,
db,
}: FieldPropTypes) => (
<ValidatedInput
id="database_name"
name="database_name"
required={required}
value={db?.database_name || ''}
validationMethods={{ onBlur: getValidation }}
errorMessage={validationErrors?.database_name}
placeholder=""
Expand All @@ -167,8 +181,12 @@ const DatabaseConnectionForm = ({
onChange,
validationErrors,
getValidation,
db,
isEditMode = false,
}: {
isEditMode?: boolean;
dbModel: DatabaseForm;
db: Partial<DatabaseObject> | null;
onParametersChange: (
event: FormEvent<InputProps> | { target: HTMLInputElement },
) => void;
Expand Down Expand Up @@ -203,6 +221,7 @@ const DatabaseConnectionForm = ({
changeMethods: { onParametersChange, onChange },
validationErrors,
getValidation,
db,
key: field,
}),
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import React from 'react';
import fetchMock from 'fetch-mock';
import userEvent from '@testing-library/user-event';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import { render, screen } from 'spec/helpers/testing-library';
import DatabaseModal from './index';

const dbProps = {
Expand All @@ -30,7 +30,7 @@ const dbProps = {
};

const DATABASE_FETCH_ENDPOINT = 'glob:*/api/v1/database/10';
const DATABASE_POST_ENDPOINT = 'glob:*/api/v1/database/';
// const DATABASE_POST_ENDPOINT = 'glob:*/api/v1/database/';
const AVAILABLE_DB_ENDPOINT = 'glob:*/api/v1/database/available*';
fetchMock.config.overwriteRoutes = true;
fetchMock.get(DATABASE_FETCH_ENDPOINT, {
Expand Down Expand Up @@ -203,68 +203,68 @@ describe('DatabaseModal', () => {
// Both checkboxes go unchecked, so the field should no longer render
expect(schemaField).not.toHaveClass('open');
});

describe('create database', () => {
beforeEach(() => {
fetchMock.post(DATABASE_POST_ENDPOINT, {
id: 10,
});
fetchMock.mock(AVAILABLE_DB_ENDPOINT, {
databases: [
{
engine: 'mysql',
name: 'MySQL',
preferred: false,
},
],
});
});
const props = {
...dbProps,
databaseId: null,
database_name: null,
sqlalchemy_uri: null,
};
it('should show a form when dynamic_form is selected', async () => {
render(<DatabaseModal {...props} />, { useRedux: true });
// it should have the correct header text
const headerText = screen.getByText(/connect a database/i);
expect(headerText).toBeVisible();

await screen.findByText(/display name/i);

// it does not fetch any databases if no id is passed in
expect(fetchMock.calls(DATABASE_FETCH_ENDPOINT).length).toEqual(0);

// todo we haven't hooked this up to load dynamically yet so
// we can't currently test it
});
it('should close the modal on save if using the sqlalchemy form', async () => {
const onHideMock = jest.fn();
render(<DatabaseModal {...props} onHide={onHideMock} />, {
useRedux: true,
});
// button should be disabled by default
const submitButton = screen.getByTestId('modal-confirm-button');
expect(submitButton).toBeDisabled();

const displayName = screen.getByTestId('database-name-input');
userEvent.type(displayName, 'MyTestDB');
expect(displayName.value).toBe('MyTestDB');
const sqlalchemyInput = screen.getByTestId('sqlalchemy-uri-input');
userEvent.type(sqlalchemyInput, 'some_url');
expect(sqlalchemyInput.value).toBe('some_url');

// button should not be disabled now
expect(submitButton).toBeEnabled();

await waitFor(() => {
userEvent.click(submitButton);
});
expect(fetchMock.calls(DATABASE_POST_ENDPOINT)).toHaveLength(1);
expect(onHideMock).toHaveBeenCalled();
});
});
// TODO: rewrite when Modal is complete
// describe('create database', () => {
// beforeEach(() => {
// fetchMock.post(DATABASE_POST_ENDPOINT, {
// id: 10,
// });
// fetchMock.mock(AVAILABLE_DB_ENDPOINT, {
// databases: [
// {
// engine: 'mysql',
// name: 'MySQL',
// preferred: false,
// },
// ],
// });
// });
// const props = {
// ...dbProps,
// databaseId: null,
// database_name: null,
// sqlalchemy_uri: null,
// };
// it('should show a form when dynamic_form is selected', async () => {
// render(<DatabaseModal {...props} />, { useRedux: true });
// // it should have the correct header text
// const headerText = screen.getByText(/connect a database/i);
// expect(headerText).toBeVisible();

// await screen.findByText(/display name/i);

// // it does not fetch any databases if no id is passed in
// expect(fetchMock.calls(DATABASE_FETCH_ENDPOINT).length).toEqual(0);

// // todo we haven't hooked this up to load dynamically yet so
// // we can't currently test it
// });
// it('should close the modal on save if using the sqlalchemy form', async () => {
// const onHideMock = jest.fn();
// render(<DatabaseModal {...props} onHide={onHideMock} />, {
// useRedux: true,
// });
// // button should be disabled by default
// const submitButton = screen.getByTestId('modal-confirm-button');
// expect(submitButton).toBeDisabled();

// const displayName = screen.getByTestId('database-name-input');
// userEvent.type(displayName, 'MyTestDB');
// expect(displayName.value).toBe('MyTestDB');
// const sqlalchemyInput = screen.getByTestId('sqlalchemy-uri-input');
// userEvent.type(sqlalchemyInput, 'some_url');
// expect(sqlalchemyInput.value).toBe('some_url');

// // button should not be disabled now
// expect(submitButton).toBeEnabled();

// await waitFor(() => {
// userEvent.click(submitButton);
// });
// expect(fetchMock.calls(DATABASE_POST_ENDPOINT)).toHaveLength(1);
// expect(onHideMock).toHaveBeenCalled();
// });
// });

describe('edit database', () => {
beforeEach(() => {
Expand All @@ -290,8 +290,6 @@ describe('DatabaseModal', () => {
// it should have the correct header text
const headerText = screen.getByText(/edit database/i);
expect(headerText).toBeVisible();

// todo add more when this form is built out
});
it('renders the dynamic form when the dynamic_form configuration method is set', async () => {
fetchMock.get(DATABASE_FETCH_ENDPOINT, {
Expand All @@ -309,15 +307,15 @@ describe('DatabaseModal', () => {
});
render(<DatabaseModal {...dbProps} />, { useRedux: true });

await screen.findByText(/todo/i);
await screen.findByText(/edit database/i);

// // it should have tabs
const tabs = screen.getAllByRole('tab');
expect(tabs.length).toEqual(2);

// it should show a TODO for now
const todoText = screen.getAllByText(/todo/i);
expect(todoText[0]).toBeVisible();
const headerText = screen.getByText(/edit database/i);
expect(headerText).toBeVisible();
});
});
});
Loading

0 comments on commit b8e6687

Please sign in to comment.