From 9811bf09a02ab6ca5fdad92e874f5f90cc46fc0a Mon Sep 17 00:00:00 2001 From: Ashish Gupta Date: Tue, 12 Aug 2025 16:33:25 +0530 Subject: [PATCH] disbable the upload dragger when file in process in BulkImport (#22812) --- .../UploadFile/UploadFile.interface.ts | 1 + .../components/UploadFile/UploadFile.test.tsx | 214 ++++++++++++++++++ .../src/components/UploadFile/UploadFile.tsx | 9 +- .../components/UploadFile/upload-file.less | 35 +++ .../BulkEntityImportPage.tsx | 4 + 5 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/UploadFile/upload-file.less diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.interface.ts index 811a36b951c..835d27fb077 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.interface.ts @@ -15,6 +15,7 @@ import { RcFile } from 'antd/lib/upload'; declare type BeforeUploadValueType = void | boolean | string | Blob | File; export interface UploadFileProps { + disabled?: boolean; fileType: string; beforeUpload?: ( file: RcFile, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.test.tsx new file mode 100644 index 00000000000..d2e5117f1a3 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.test.tsx @@ -0,0 +1,214 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { showErrorToast } from '../../utils/ToastUtils'; +import UploadFile from './UploadFile'; +import { UploadFileProps } from './UploadFile.interface'; + +jest.mock('../../assets/svg/ic-drag-drop.svg', () => ({ + ReactComponent: () =>
ImportIcon
, +})); + +jest.mock('../common/Loader/Loader', () => { + return jest.fn(() =>
Loading...
); +}); + +jest.mock('../../utils/ToastUtils', () => ({ + showErrorToast: jest.fn(), +})); + +jest.mock('../../utils/CommonUtils', () => ({ + Transi18next: jest + .fn() + .mockReturnValue('message.drag-and-drop-or-browse-csv-files-here'), +})); + +describe('UploadFile Component', () => { + const defaultProps: UploadFileProps = { + fileType: '.csv', + onCSVUploaded: jest.fn(), + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render the upload component with correct props', () => { + render(); + + expect(screen.getByTestId('upload-file-widget')).toBeInTheDocument(); + expect(screen.getByTestId('import-icon')).toBeInTheDocument(); + expect( + screen.getByText('message.drag-and-drop-or-browse-csv-files-here') + ).toBeInTheDocument(); + }); + + it('should render with disabled state', () => { + render(); + + const uploadWidget = screen.getByTestId('upload-file-widget'); + + expect(uploadWidget).toHaveAttribute('disabled'); + }); + + it('should not render with disabled state', () => { + render(); + + const uploadWidget = screen.getByTestId('upload-file-widget'); + + expect(uploadWidget).not.toHaveAttribute('disabled'); + }); + + it('should render with custom file type', () => { + render(); + + const uploadWidget = screen.getByTestId('upload-file-widget'); + + expect(uploadWidget).toHaveAttribute('accept', '.json,.xml'); + }); + + it('should call onCSVUploaded when file is uploaded successfully', async () => { + const mockOnCSVUploaded = jest.fn(); + + render(); + + const uploadWidget = screen.getByTestId('upload-file-widget'); + const file = new File(['test,csv,content'], 'test.csv', { + type: 'text/csv', + }); + + fireEvent.drop(uploadWidget, { + dataTransfer: { + files: [file], + }, + }); + + await waitFor(() => { + expect(mockOnCSVUploaded).toHaveBeenCalled(); + }); + }); + + it('should handle file upload error', async () => { + const mockOnCSVUploaded = jest.fn(); + + render(); + + const uploadWidget = screen.getByTestId('upload-file-widget'); + + // Create a file that will cause an error when read + const file = new File(['test content'], 'test.csv', { + type: 'text/csv', + }); + + // Mock FileReader to throw an error + const originalFileReader = global.FileReader; + global.FileReader = jest.fn().mockImplementation(() => ({ + readAsText: jest.fn(() => { + throw new Error('File read error'); + }), + onerror: null, + })) as any; + + fireEvent.drop(uploadWidget, { + dataTransfer: { + files: [file], + }, + }); + + await waitFor(() => { + expect(showErrorToast).toHaveBeenCalled(); + }); + + // Restore original FileReader + global.FileReader = originalFileReader; + }); + + it('should call beforeUpload when provided', () => { + const mockBeforeUpload = jest.fn(() => true); + render(); + + const uploadWidget = screen.getByTestId('upload-file-widget'); + const file = new File(['test content'], 'test.csv', { type: 'text/csv' }); + + fireEvent.drop(uploadWidget, { + dataTransfer: { + files: [file], + }, + }); + + expect(mockBeforeUpload).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Array) + ); + }); + + it('should not upload file when beforeUpload returns false', () => { + const mockBeforeUpload = jest.fn(() => false); + const mockOnCSVUploaded = jest.fn(); + + render( + + ); + + const uploadWidget = screen.getByTestId('upload-file-widget'); + const file = new File(['test content'], 'test.csv', { type: 'text/csv' }); + + fireEvent.drop(uploadWidget, { + dataTransfer: { + files: [file], + }, + }); + + expect(mockBeforeUpload).toHaveBeenCalled(); + expect(mockOnCSVUploaded).not.toHaveBeenCalled(); + }); + + it('should handle async beforeUpload function', async () => { + const mockBeforeUpload = jest.fn().mockResolvedValue(true); + const mockOnCSVUploaded = jest.fn(); + + render( + + ); + + const uploadWidget = screen.getByTestId('upload-file-widget'); + const file = new File(['test content'], 'test.csv', { type: 'text/csv' }); + + fireEvent.drop(uploadWidget, { + dataTransfer: { + files: [file], + }, + }); + + await waitFor(() => { + expect(mockBeforeUpload).toHaveBeenCalled(); + }); + }); + + it('should render browse text correctly', () => { + render(); + + expect( + screen.getByText('message.drag-and-drop-or-browse-csv-files-here') + ).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.tsx b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.tsx index 69d10408e01..1d675a554d0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.tsx @@ -20,9 +20,11 @@ import { ReactComponent as ImportIcon } from '../../assets/svg/ic-drag-drop.svg' import { Transi18next } from '../../utils/CommonUtils'; import { showErrorToast } from '../../utils/ToastUtils'; import Loader from '../common/Loader/Loader'; +import './upload-file.less'; import { UploadFileProps } from './UploadFile.interface'; const UploadFile: FC = ({ + disabled, fileType, beforeUpload, onCSVUploaded, @@ -55,9 +57,10 @@ const UploadFile: FC = ({ = ({ direction="vertical" size={42}> - + } + renderElement={} values={{ text: t('label.browse'), }} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/upload-file.less b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/upload-file.less new file mode 100644 index 00000000000..4231ee9608f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/upload-file.less @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import (reference) '../../styles/variables.less'; + +.file-dragger-wrapper { + padding: @padding-lg; + background: @white; + .ant-typography { + font-weight: 500; + font-size: 16px; + + .browse-text { + color: @primary-color; + } + } + .ant-upload-disabled { + .ant-typography { + color: @grey-300; + + .browse-text { + color: @grey-300; + } + } + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/EntityImport/BulkEntityImportPage/BulkEntityImportPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/EntityImport/BulkEntityImportPage/BulkEntityImportPage.tsx index a892e3834b4..eba168a7b65 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/EntityImport/BulkEntityImportPage/BulkEntityImportPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/EntityImport/BulkEntityImportPage/BulkEntityImportPage.tsx @@ -552,6 +552,10 @@ const BulkEntityImportPage = () => { ) : (