feat: FE: Import for Queries II (#14091)

* Copied changes over

* Tests passing

* Import testing complete
This commit is contained in:
Lyndsi Kay Williams 2021-04-14 18:02:35 -05:00 committed by GitHub
parent 21f973f0bd
commit 36bd6d8303
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 3 deletions

View File

@ -75,6 +75,42 @@ const mockqueries = [...new Array(3)].map((_, i) => ({
],
}));
// ---------- For import testing ----------
// Create an one more mocked query than the original mocked query array
const mockOneMoreQuery = [...new Array(mockqueries.length + 1)].map((_, i) => ({
created_by: {
id: i,
first_name: `user`,
last_name: `${i}`,
},
created_on: `${i}-2020`,
database: {
database_name: `db ${i}`,
id: i,
},
changed_on_delta_humanized: '1 day ago',
db_id: i,
description: `SQL for ${i}`,
id: i,
label: `query ${i}`,
schema: 'public',
sql: `SELECT ${i} FROM table`,
sql_tables: [
{
catalog: null,
schema: null,
table: `${i}`,
},
],
}));
// Grab the last mocked query, to mock import
const mockNewImportQuery = mockOneMoreQuery.pop();
// Create a new file out of mocked import query to mock upload
const mockImportFile = new File(
[mockNewImportQuery],
'saved_query_import_mock.json',
);
fetchMock.get(queriesInfoEndpoint, {
permissions: ['can_write', 'can_read'],
});
@ -237,7 +273,7 @@ describe('RTL', () => {
it('renders an export button in the actions bar', async () => {
// Grab Export action button and mock mouse hovering over it
const exportActionButton = screen.getAllByRole('button')[17];
const exportActionButton = screen.getAllByRole('button')[18];
userEvent.hover(exportActionButton);
// Wait for the tooltip to pop up
@ -252,9 +288,42 @@ describe('RTL', () => {
it('runs handleBulkSavedQueryExport when export is clicked', () => {
// Grab Export action button and mock mouse clicking it
const exportActionButton = screen.getAllByRole('button')[17];
const exportActionButton = screen.getAllByRole('button')[18];
userEvent.click(exportActionButton);
expect(handleBulkSavedQueryExport).toHaveBeenCalled();
});
it('renders an import button in the submenu', () => {
// Grab and assert that import saved query button is visible
const importSavedQueryButton = screen.getAllByRole('button')[2];
expect(importSavedQueryButton).toBeVisible();
});
it('renders an import model when import button is clicked', async () => {
// Grab and click import saved query button to reveal modal
const importSavedQueryButton = screen.getAllByRole('button')[2];
userEvent.click(importSavedQueryButton);
// Grab and assert that saved query import modal's heading is visible
const importSavedQueryModalHeading = screen.getByRole('heading', {
name: /import saved query/i,
});
expect(importSavedQueryModalHeading).toBeVisible();
});
it('imports a saved query', () => {
// Grab and click import saved query button to reveal modal
const importSavedQueryButton = screen.getAllByRole('button')[2];
userEvent.click(importSavedQueryButton);
// Grab "Choose File" input from import modal
const chooseFileInput = screen.getByLabelText(/file\*/i);
// Upload mocked import file
userEvent.upload(chooseFileInput, mockImportFile);
expect(chooseFileInput.files[0]).toStrictEqual(mockImportFile);
expect(chooseFileInput.files.item(0)).toStrictEqual(mockImportFile);
expect(chooseFileInput.files).toHaveLength(1);
});
});

View File

@ -42,9 +42,23 @@ import { commonMenuData } from 'src/views/CRUD/data/common';
import { SavedQueryObject } from 'src/views/CRUD/types';
import copyTextToClipboard from 'src/utils/copy';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import ImportModelsModal from 'src/components/ImportModal/index';
import Icons from 'src/components/Icons';
import SavedQueryPreviewModal from './SavedQueryPreviewModal';
const PAGE_SIZE = 25;
const PASSWORDS_NEEDED_MESSAGE = t(
'The passwords for the databases below are needed in order to ' +
'import them together with the saved queries. 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 saved queries that already exist. ' +
'Overwriting might cause you to lose some of your work. Are you ' +
'sure you want to overwrite?',
);
interface SavedQueryListProps {
addDangerToast: (msg: string) => void;
@ -96,6 +110,21 @@ function SavedQueryList({
savedQueryCurrentlyPreviewing,
setSavedQueryCurrentlyPreviewing,
] = useState<SavedQueryObject | null>(null);
const [importingSavedQuery, showImportModal] = useState<boolean>(false);
const [passwordFields, setPasswordFields] = useState<string[]>([]);
const openSavedQueryImportModal = () => {
showImportModal(true);
};
const closeSavedQueryImportModal = () => {
showImportModal(false);
};
const handleSavedQueryImport = () => {
showImportModal(false);
refreshData();
};
const canEdit = hasPerm('can_write');
const canDelete = hasPerm('can_write');
@ -149,6 +178,15 @@ function SavedQueryList({
buttonStyle: 'primary',
});
if (isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT)) {
subMenuButtons.push({
name: <Icons.Import />,
buttonStyle: 'link',
onClick: openSavedQueryImportModal,
'data-test': 'import-button',
});
}
menuData.buttons = subMenuButtons;
// Action methods
@ -476,6 +514,20 @@ function SavedQueryList({
);
}}
</ConfirmStatusChange>
<ImportModelsModal
resourceName="saved_query"
resourceLabel={t('saved query')}
passwordsNeededMessage={PASSWORDS_NEEDED_MESSAGE}
confirmOverwriteMessage={CONFIRM_OVERWRITE_MESSAGE}
addDangerToast={addDangerToast}
addSuccessToast={addSuccessToast}
onModelImport={handleSavedQueryImport}
show={importingSavedQuery}
onHide={closeSavedQueryImportModal}
passwordFields={passwordFields}
setPasswordFields={setPasswordFields}
/>
</>
);
}

View File

@ -123,7 +123,12 @@ export enum QueryObjectColumns {
tracking_url = 'tracking_url',
}
export type ImportResourceName = 'chart' | 'dashboard' | 'database' | 'dataset';
export type ImportResourceName =
| 'chart'
| 'dashboard'
| 'database'
| 'dataset'
| 'saved_query';
export type DatabaseObject = {
allow_run_async?: boolean;

View File

@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import {
t,
SupersetClient,