Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Move Database Import option into DB Connection modal #19314

Merged
merged 35 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2820627
rebase
lyndsiWilliams Mar 22, 2022
da40e8d
more progress
lyndsiWilliams Mar 22, 2022
bc7ef74
Fix unintended changes
lyndsiWilliams Mar 22, 2022
1a53bdb
DB import goes to step 3
lyndsiWilliams Mar 24, 2022
84dd7f5
debugging
lyndsiWilliams Mar 24, 2022
3a86dea
DB list refreshing properly
lyndsiWilliams Mar 28, 2022
c4715da
import screens flowing properly
lyndsiWilliams Mar 28, 2022
14ec320
Code cleanup
lyndsiWilliams Mar 29, 2022
bcc9a32
Fixed back button on import flow
lyndsiWilliams Mar 29, 2022
c61e0b1
Remove import db tooltip test
lyndsiWilliams Mar 29, 2022
415011e
Fix test
lyndsiWilliams Mar 29, 2022
c0da671
Password field resets properly
lyndsiWilliams Mar 29, 2022
a5d4761
Changed import modal state dictators and removed unneeded comment
lyndsiWilliams Mar 30, 2022
71ebaf3
Removed unneeded param pass and corrected modal spelling
lyndsiWilliams Mar 30, 2022
cb6ef83
Fixed typos
lyndsiWilliams Mar 30, 2022
ba13443
Changed file to fileList
lyndsiWilliams Mar 30, 2022
f1a7e8b
Clarified import footer comment
lyndsiWilliams Mar 30, 2022
aa5b7ea
Cleaned passwordNeededField and confirmOverwriteField state checks
lyndsiWilliams Mar 30, 2022
f983649
debugging
lyndsiWilliams Mar 31, 2022
4d1c95a
Import state flow fixed
lyndsiWilliams Mar 31, 2022
1420929
Removed unneeded importModal check in unreachable area
lyndsiWilliams Mar 31, 2022
7c32001
Fixed import db footer behavior when pressing back on step 2
lyndsiWilliams Apr 4, 2022
a50e3dd
Import db button now at 14px
lyndsiWilliams Apr 4, 2022
677c546
Removed animation from import db button
lyndsiWilliams Apr 4, 2022
a16f98b
Fixed doble-loading successToast
lyndsiWilliams Apr 5, 2022
09ff22a
Fixed errored import behavior
lyndsiWilliams Apr 5, 2022
0bbf890
Updated import password check info box text
lyndsiWilliams Apr 6, 2022
e5fc92d
Connect button disables while importing db is loading
lyndsiWilliams Apr 7, 2022
55eedff
Connect button disables while overwrite confirmation is still needed
lyndsiWilliams Apr 7, 2022
48749e4
Connect button disables while password confirmation is still needed
lyndsiWilliams Apr 7, 2022
ca6377c
Removed gray line under upload filename
lyndsiWilliams Apr 7, 2022
5a4ee8b
Hide trashcan icon on import filename
lyndsiWilliams Apr 7, 2022
87f4cac
Modal scroll behavior fixed for importing filename
lyndsiWilliams Apr 7, 2022
b61afd4
Changed errored to failed
lyndsiWilliams Apr 7, 2022
b4c6b6a
RTL testing for db import
lyndsiWilliams Apr 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -23,10 +23,6 @@ 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 { render, screen, cleanup } 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 = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure we don't need this for another test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible, but it wasn't being used for any other tests currently so I wasn't sure if I should keep it. Should I put it back just in case?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion is, if we don't need it let's remove it.

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 @@ -103,8 +91,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 @@ -116,20 +102,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: `/api/v1/database/${database.id}/related_objects/`,
Expand Down Expand Up @@ -245,22 +217,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 @@ -526,19 +482,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 {
EditHeaderTitle,
EditHeaderSubtitle,
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 <></>;
betodealmeida marked this conversation as resolved.
Show resolved Hide resolved
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;
betodealmeida marked this conversation as resolved.
Show resolved Hide resolved

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