mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
feat: style import button (#15057)
* feat: style import button * Update tests * Update test
This commit is contained in:
parent
0c470feaef
commit
256e1452fe
@ -54,6 +54,7 @@ export {
|
||||
Tag,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
Upload,
|
||||
Input as AntdInput,
|
||||
} from 'antd';
|
||||
export { Card as AntdCard } from 'antd';
|
||||
|
@ -17,10 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { Upload } from 'src/common/components';
|
||||
import Button from 'src/components/Button';
|
||||
import { ImportResourceName } from 'src/views/CRUD/types';
|
||||
import ImportModelsModal from 'src/components/ImportModal';
|
||||
@ -66,29 +68,37 @@ describe('ImportModelsModal', () => {
|
||||
expect(wrapper.find('h4').text()).toEqual('Import database');
|
||||
});
|
||||
|
||||
it('renders a label and a file input field', () => {
|
||||
it('renders a file input field', () => {
|
||||
expect(wrapper.find('input[type="file"]')).toExist();
|
||||
expect(wrapper.find('label')).toExist();
|
||||
});
|
||||
|
||||
it('should attach the label to the input field', () => {
|
||||
const id = 'modelFile';
|
||||
expect(wrapper.find('label').prop('htmlFor')).toBe(id);
|
||||
expect(wrapper.find('input').prop('id')).toBe(id);
|
||||
});
|
||||
|
||||
it('should render the close, import and cancel buttons', () => {
|
||||
expect(wrapper.find('button')).toHaveLength(3);
|
||||
it('should render the close, file, import and cancel buttons', () => {
|
||||
expect(wrapper.find('button')).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('should render the import button initially disabled', () => {
|
||||
expect(wrapper.find(Button).at(1).prop('disabled')).toBe(true);
|
||||
expect(wrapper.find(Button).at(2).prop('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('should render the import button enabled when a file is selected', () => {
|
||||
const file = new File([new ArrayBuffer(1)], 'model_export.zip');
|
||||
wrapper.find('input').simulate('change', { target: { files: [file] } });
|
||||
expect(wrapper.find(Button).at(1).prop('disabled')).toBe(false);
|
||||
act(() => {
|
||||
const handler = wrapper.find(Upload).prop('onChange');
|
||||
if (handler) {
|
||||
handler({
|
||||
fileList: [],
|
||||
file: {
|
||||
name: 'model_export.zip',
|
||||
originFileObj: file,
|
||||
uid: '-1',
|
||||
size: 0,
|
||||
type: 'zip',
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find(Button).at(2).prop('disabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('should render password fields when needed for import', () => {
|
||||
|
@ -16,11 +16,14 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
import { UploadChangeParam, UploadFile } from 'antd/lib/upload/interface';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
|
||||
import Icon from 'src//components/Icon';
|
||||
import Button from 'src/components/Button';
|
||||
import Icon from 'src/components/Icon';
|
||||
import Modal from 'src/components/Modal';
|
||||
import { Upload } from 'src/common/components';
|
||||
import { useImportResource } from 'src/views/CRUD/hooks';
|
||||
import { ImportResourceName } from 'src/views/CRUD/types';
|
||||
|
||||
@ -127,24 +130,19 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({
|
||||
setPasswordFields = () => {},
|
||||
}) => {
|
||||
const [isHidden, setIsHidden] = useState<boolean>(true);
|
||||
const [uploadFile, setUploadFile] = useState<File | null>(null);
|
||||
const [passwords, setPasswords] = useState<Record<string, string>>({});
|
||||
const [needsOverwriteConfirm, setNeedsOverwriteConfirm] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const [confirmedOverwrite, setConfirmedOverwrite] = useState<boolean>(false);
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
|
||||
const clearModal = () => {
|
||||
setUploadFile(null);
|
||||
setFileList([]);
|
||||
setPasswordFields([]);
|
||||
setPasswords({});
|
||||
setNeedsOverwriteConfirm(false);
|
||||
setConfirmedOverwrite(false);
|
||||
if (fileInputRef && fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleErrorMsg = (msg: string) => {
|
||||
@ -173,11 +171,15 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({
|
||||
};
|
||||
|
||||
const onUpload = () => {
|
||||
if (uploadFile === null) {
|
||||
if (!(fileList[0]?.originFileObj instanceof File)) {
|
||||
return;
|
||||
}
|
||||
|
||||
importResource(uploadFile, passwords, confirmedOverwrite).then(result => {
|
||||
importResource(
|
||||
fileList[0].originFileObj,
|
||||
passwords,
|
||||
confirmedOverwrite,
|
||||
).then(result => {
|
||||
if (result) {
|
||||
addSuccessToast(t('The import was successful'));
|
||||
clearModal();
|
||||
@ -186,9 +188,18 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const changeFile = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { files } = event.target as HTMLInputElement;
|
||||
setUploadFile((files && files[0]) || null);
|
||||
const changeFile = (info: UploadChangeParam) => {
|
||||
setFileList([
|
||||
{
|
||||
...info.file,
|
||||
status: 'done',
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const removeFile = (removedFile: UploadFile) => {
|
||||
setFileList(fileList.filter(file => file.uid !== removedFile.uid));
|
||||
return false;
|
||||
};
|
||||
|
||||
const confirmOverwrite = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@ -259,7 +270,7 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({
|
||||
name="model"
|
||||
className="import-model-modal"
|
||||
disablePrimaryButton={
|
||||
uploadFile === null || (needsOverwriteConfirm && !confirmedOverwrite)
|
||||
fileList.length === 0 || (needsOverwriteConfirm && !confirmedOverwrite)
|
||||
}
|
||||
onHandledPrimaryAction={onUpload}
|
||||
onHide={hide}
|
||||
@ -270,21 +281,19 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({
|
||||
title={<h4>{t('Import %s', resourceLabel)}</h4>}
|
||||
>
|
||||
<StyledInputContainer>
|
||||
<div className="control-label">
|
||||
<label htmlFor="modelFile">
|
||||
{t('File')}
|
||||
<span className="required">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
data-test="model-file-input"
|
||||
<Upload
|
||||
name="modelFile"
|
||||
id="modelFile"
|
||||
type="file"
|
||||
data-test="model-file-input"
|
||||
accept=".yaml,.json,.yml,.zip"
|
||||
fileList={fileList}
|
||||
onChange={changeFile}
|
||||
/>
|
||||
onRemove={removeFile}
|
||||
// upload is handled by hook
|
||||
customRequest={() => {}}
|
||||
>
|
||||
<Button>Select file</Button>
|
||||
</Upload>
|
||||
</StyledInputContainer>
|
||||
{renderPasswordFields()}
|
||||
{renderOverwriteConfirmation()}
|
||||
|
@ -330,7 +330,7 @@ describe('RTL', () => {
|
||||
userEvent.click(importButton);
|
||||
|
||||
// Grab "Choose File" input from import modal
|
||||
const chooseFileInput = screen.getByLabelText(/file\*/i);
|
||||
const chooseFileInput = screen.getByTestId('model-file-input');
|
||||
// Upload mocked import file
|
||||
userEvent.upload(chooseFileInput, mockImportFile);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user