Skip to content

Commit

Permalink
feat: Move Database Import option into DB Connection modal (apache#19314
Browse files Browse the repository at this point in the history
)

* rebase

* more progress

* Fix unintended changes

* DB import goes to step 3

* debugging

* DB list refreshing properly

* import screens flowing properly

* Code cleanup

* Fixed back button on import flow

* Remove import db tooltip test

* Fix test

* Password field resets properly

* Changed import modal state dictators and removed unneeded comment

* Removed unneeded param pass and corrected modal spelling

* Fixed typos

* Changed file to fileList

* Clarified import footer comment

* Cleaned passwordNeededField and confirmOverwriteField state checks

* debugging

* Import state flow fixed

* Removed unneeded importModal check in unreachable area

* Fixed import db footer behavior when pressing back on step 2

* Import db button now at 14px

* Removed animation from import db button

* Fixed doble-loading successToast

* Fixed errored import behavior

* Updated import password check info box text

* Connect button disables while importing db is loading

* Connect button disables while overwrite confirmation is still needed

* Connect button disables while password confirmation is still needed

* Removed gray line under upload filename

* Hide trashcan icon on import filename

* Modal scroll behavior fixed for importing filename

* Changed errored to failed

* RTL testing for db import
  • Loading branch information
lyndsiWilliams authored and philipher29 committed Jun 9, 2022
1 parent 4b8ef2d commit f2da20d
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 175 deletions.
8 changes: 8 additions & 0 deletions superset-frontend/src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ export interface ButtonProps {
cta?: boolean;
loading?: boolean | { delay?: number | undefined } | undefined;
showMarginRight?: boolean;
type?:
| 'default'
| 'text'
| 'link'
| 'primary'
| 'dashed'
| 'ghost'
| undefined;
}

export default function Button(props: ButtonProps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,10 @@
import React from 'react';
import thunk from 'redux-thunk';
import * as redux from 'react-redux';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
import { Provider } from 'react-redux';
import { styledMount as mount } from 'spec/helpers/theming';
import { cleanup, render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { QueryParamProvider } from 'use-query-params';
import * as featureFlags from 'src/featureFlags';

import DatabaseList from 'src/views/CRUD/data/database/DatabaseList';
import DatabaseModal from 'src/views/CRUD/data/database/DatabaseModal';
Expand All @@ -41,17 +37,6 @@ import { act } from 'react-dom/test-utils';
const mockStore = configureStore([thunk]);
const store = mockStore({});

const mockAppState = {
common: {
config: {
CSV_EXTENSIONS: ['csv'],
EXCEL_EXTENSIONS: ['xls', 'xlsx'],
COLUMNAR_EXTENSIONS: ['parquet', 'zip'],
ALLOWED_EXTENSIONS: ['parquet', 'zip', 'xls', 'xlsx', 'csv'],
},
},
};

const databasesInfoEndpoint = 'glob:*/api/v1/database/_info*';
const databasesEndpoint = 'glob:*/api/v1/database/?*';
const databaseEndpoint = 'glob:*/api/v1/database/*';
Expand Down Expand Up @@ -208,44 +193,3 @@ describe('DatabaseList', () => {
);
});
});

describe('RTL', () => {
async function renderAndWait() {
const mounted = act(async () => {
render(
<QueryParamProvider>
<DatabaseList user={mockUser} />
</QueryParamProvider>,
{ useRedux: true },
mockAppState,
);
});

return mounted;
}

let isFeatureEnabledMock;
beforeEach(async () => {
isFeatureEnabledMock = jest
.spyOn(featureFlags, 'isFeatureEnabled')
.mockImplementation(() => true);
await renderAndWait();
});

afterEach(() => {
cleanup();
isFeatureEnabledMock.mockRestore();
});

it('renders an "Import Database" tooltip under import button', async () => {
const importButton = await screen.findByTestId('import-button');
userEvent.hover(importButton);

await screen.findByRole('tooltip');
const importTooltip = screen.getByRole('tooltip', {
name: 'Import databases',
});

expect(importTooltip).toBeInTheDocument();
});
});
57 changes: 0 additions & 57 deletions superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import { Tooltip } from 'src/components/Tooltip';
import Icons from 'src/components/Icons';
import ListView, { FilterOperator, Filters } from 'src/components/ListView';
import { commonMenuData } from 'src/views/CRUD/data/common';
import ImportModelsModal from 'src/components/ImportModal/index';
import handleResourceExport from 'src/utils/export';
import { ExtentionConfigs } from 'src/views/components/types';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
Expand All @@ -39,17 +38,6 @@ import DatabaseModal from './DatabaseModal';
import { DatabaseObject } from './types';

const PAGE_SIZE = 25;
const PASSWORDS_NEEDED_MESSAGE = t(
'The passwords for the databases below are needed in order to ' +
'import them. Please note that the "Secure Extra" and "Certificate" ' +
'sections of the database configuration are not present in export ' +
'files, and should be added manually after the import if they are needed.',
);
const CONFIRM_OVERWRITE_MESSAGE = t(
'You are importing one or more databases that already exist. ' +
'Overwriting might cause you to lose some of your work. Are you ' +
'sure you want to overwrite?',
);

interface DatabaseDeleteObject extends DatabaseObject {
chart_count: number;
Expand Down Expand Up @@ -104,8 +92,6 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
const [currentDatabase, setCurrentDatabase] = useState<DatabaseObject | null>(
null,
);
const [importingDatabase, showImportModal] = useState<boolean>(false);
const [passwordFields, setPasswordFields] = useState<string[]>([]);
const [preparingExport, setPreparingExport] = useState<boolean>(false);
const { roles } = useSelector<any, UserWithPermissionsAndRoles>(
state => state.user,
Expand All @@ -117,20 +103,6 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
ALLOWED_EXTENSIONS,
} = useSelector<any, ExtentionConfigs>(state => state.common.conf);

const openDatabaseImportModal = () => {
showImportModal(true);
};

const closeDatabaseImportModal = () => {
showImportModal(false);
};

const handleDatabaseImport = () => {
showImportModal(false);
refreshData();
addSuccessToast(t('Database imported'));
};

const openDatabaseDeleteModal = (database: DatabaseObject) =>
SupersetClient.get({
endpoint: `${process.env.APP_PREFIX}/api/v1/database/${database.id}/related_objects/`,
Expand Down Expand Up @@ -246,22 +218,6 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
},
},
];

if (isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT)) {
menuData.buttons.push({
name: (
<Tooltip
id="import-tooltip"
title={t('Import databases')}
placement="bottomRight"
>
<Icons.Import data-test="import-button" />
</Tooltip>
),
buttonStyle: 'link',
onClick: openDatabaseImportModal,
});
}
}

function handleDatabaseExport(database: DatabaseObject) {
Expand Down Expand Up @@ -527,19 +483,6 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
pageSize={PAGE_SIZE}
/>

<ImportModelsModal
resourceName="database"
resourceLabel={t('database')}
passwordsNeededMessage={PASSWORDS_NEEDED_MESSAGE}
confirmOverwriteMessage={CONFIRM_OVERWRITE_MESSAGE}
addDangerToast={addDangerToast}
addSuccessToast={addSuccessToast}
onModelImport={handleDatabaseImport}
show={importingDatabase}
onHide={closeDatabaseImportModal}
passwordFields={passwordFields}
setPasswordFields={setPasswordFields}
/>
{preparingExport && <Loading />}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import React from 'react';
import { getDatabaseDocumentationLinks } from 'src/views/CRUD/hooks';
import { UploadFile } from 'antd/lib/upload/interface';
import {
EditHeaderSubtitle,
EditHeaderTitle,
Expand Down Expand Up @@ -52,6 +53,7 @@ const documentationLink = (engine: string | undefined) => {
}
return irregularDocumentationLinks[engine];
};

const ModalHeader = ({
isLoading,
isEditMode,
Expand All @@ -61,6 +63,7 @@ const ModalHeader = ({
dbName,
dbModel,
editNewDb,
fileList,
}: {
isLoading: boolean;
isEditMode: boolean;
Expand All @@ -70,13 +73,19 @@ const ModalHeader = ({
dbName: string;
dbModel: DatabaseForm;
editNewDb?: boolean;
fileList?: UploadFile[];
passwordFields?: string[];
needsOverwriteConfirm?: boolean;
}) => {
const fileCheck = fileList && fileList?.length > 0;

const isEditHeader = (
<StyledFormHeader>
<EditHeaderTitle>{db?.backend}</EditHeaderTitle>
<EditHeaderSubtitle>{dbName}</EditHeaderSubtitle>
</StyledFormHeader>
);

const useSqlAlchemyFormHeader = (
<StyledFormHeader>
<p className="helper-top"> STEP 2 OF 2 </p>
Expand All @@ -94,6 +103,7 @@ const ModalHeader = ({
</p>
</StyledFormHeader>
);

const hasConnectedDbHeader = (
<StyledStickyHeader>
<StyledFormHeader>
Expand All @@ -115,6 +125,7 @@ const ModalHeader = ({
</StyledFormHeader>
</StyledStickyHeader>
);

const hasDbHeader = (
<StyledStickyHeader>
<StyledFormHeader>
Expand All @@ -133,6 +144,7 @@ const ModalHeader = ({
</StyledFormHeader>
</StyledStickyHeader>
);

const noDbHeader = (
<StyledFormHeader>
<div className="select-db">
Expand All @@ -142,19 +154,23 @@ const ModalHeader = ({
</StyledFormHeader>
);

const importDbHeader = (
<StyledStickyHeader>
<StyledFormHeader>
<p className="helper-top"> STEP 2 OF 2 </p>
<h4>Enter the required {dbModel.name} credentials</h4>
<p className="helper-bottom">{fileCheck ? fileList[0].name : ''}</p>
</StyledFormHeader>
</StyledStickyHeader>
);

if (fileCheck) return importDbHeader;
if (isLoading) return <></>;
if (isEditMode) {
return isEditHeader;
}
if (useSqlAlchemyForm) {
return useSqlAlchemyFormHeader;
}
if (hasConnectedDb && !editNewDb) {
return hasConnectedDbHeader;
}
if (db || editNewDb) {
return hasDbHeader;
}
if (isEditMode) return isEditHeader;
if (useSqlAlchemyForm) return useSqlAlchemyFormHeader;
if (hasConnectedDb && !editNewDb) return hasConnectedDbHeader;
if (db || editNewDb) return hasDbHeader;

return noDbHeader;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1028,7 +1028,24 @@ describe('DatabaseModal', () => {
*/
});
});

describe('Import database flow', () => {
it('imports a file', () => {
const importDbButton = screen.getByTestId('import-database-btn');
expect(importDbButton).toBeVisible();

const testFile = new File([new ArrayBuffer(1)], 'model_export.zip');

userEvent.click(importDbButton);
userEvent.upload(importDbButton, testFile);

expect(importDbButton.files[0]).toStrictEqual(testFile);
expect(importDbButton.files.item(0)).toStrictEqual(testFile);
expect(importDbButton.files).toHaveLength(1);
});
});
});

describe('DatabaseModal w/ Deeplinking Engine', () => {
const renderAndWait = async () => {
const mounted = act(async () => {
Expand Down

0 comments on commit f2da20d

Please sign in to comment.