import React, { useCallback, useEffect, useRef } from 'react';
import { connect } from 'react-redux';
import {
Button,
ErrorSummary,
Modal,
ModalBody,
ModalFooter,
ModalHeader,
css,
cx,
spacing,
FormFieldContainer,
Body,
Link,
palette,
useDarkMode,
} from '@mongodb-js/compass-components';
import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
import { FINISHED_STATUSES, STARTED } from '../constants/process-status';
import type { ProcessStatus } from '../constants/process-status';
import { ImportPreview } from './import-preview';
import ImportPreviewLoader from './import-preview-loader';
import { ImportOptions } from './import-options';
import type { AcceptedFileType } from '../constants/file-types';
import {
startImport,
cancelImport,
skipCSVAnalyze,
selectImportFileName,
setDelimiter,
setStopOnErrors,
setIgnoreBlanks,
closeImport,
toggleIncludeField,
setFieldType,
} from '../modules/import';
import type { RootImportState } from '../stores/import-store';
import type { FieldFromCSV } from '../modules/import';
import { ImportFileInput } from './import-file-input';
import type { Delimiter, CSVParsableFieldType } from '../csv/csv-types';
const closeButtonStyles = css({
marginRight: spacing[2],
});
const fieldsHeadingStyles = css({
fontWeight: 'bold',
paddingBottom: spacing[2],
});
const fieldsHeadingStylesDark = css({
borderBottom: `2px solid ${palette.gray.dark2}`,
});
const fieldsHeadingStylesLight = css({
borderBottom: `2px solid ${palette.gray.light2}`,
});
const analyzeContainerStyles = css({
// Remove double spacing between the analyze container and the form action
// buttons caused by analyze always being the last item when visible.
marginBottom: 0,
});
const dataTypesLinkStyles = css({
marginLeft: spacing[3],
});
type ImportModalProps = {
isOpen: boolean;
ns: string;
startImport: () => void;
cancelImport: () => void;
skipCSVAnalyze: () => void;
closeImport: () => void;
errors: Error[];
status: ProcessStatus;
/**
* See ``
*/
selectImportFileName: (fileName: string) => void;
setDelimiter: (delimiter: Delimiter) => void;
delimiter: Delimiter;
fileType: AcceptedFileType | '';
fileName: string;
stopOnErrors: boolean;
setStopOnErrors: (stopOnErrors: boolean) => void;
ignoreBlanks: boolean;
setIgnoreBlanks: (ignoreBlanks: boolean) => void;
/**
* See ``
*/
fields: {
isArray?: boolean;
path: string;
checked?: boolean; // CSV placeholder fields don't have checked
type?: CSVParsableFieldType;
}[];
values: string[][];
toggleIncludeField: (path: string) => void;
setFieldType: (path: string, bsonType: string) => void;
previewLoaded: boolean;
csvAnalyzed: boolean;
analyzeError?: Error;
};
function ImportModal({
isOpen,
ns,
startImport,
cancelImport,
closeImport,
errors,
status,
selectImportFileName,
setDelimiter,
delimiter,
fileType,
fileName,
stopOnErrors,
setStopOnErrors,
ignoreBlanks,
setIgnoreBlanks,
fields,
values,
toggleIncludeField,
setFieldType,
previewLoaded,
csvAnalyzed,
analyzeError,
}: ImportModalProps) {
const darkMode = useDarkMode();
const modalBodyRef = useRef(null);
const handleClose = useCallback(() => {
cancelImport();
closeImport();
}, [closeImport, cancelImport]);
useEffect(() => {
// When the errors change and there are new errors, we auto scroll
// to the end of the modal body to ensure folks see the new errors.
if (isOpen && errors && modalBodyRef.current) {
const contentDiv = modalBodyRef.current;
contentDiv.scrollTop = contentDiv.scrollHeight;
}
}, [errors, isOpen]);
useTrackOnChange(
'COMPASS-IMPORT-EXPORT-UI',
(track) => {
if (isOpen) {
track('Screen', { name: 'import_modal' });
}
},
[isOpen],
undefined
);
if (isOpen && !fileName && errors.length === 0) {
// Show the file input when we don't have a file to import yet.
return (
// Don't actually show it on the screen, just render it to trigger
// autoOpen
);
}
return (
{fileType === 'csv' && !analyzeError && (
Specify Fields and Types
Learn more about data types
{csvAnalyzed ? (
) : (
)}
)}
{errors.length > 0 && (
error.message)} />
)}
{analyzeError && (
)}
);
}
/**
* Map the state of the store to component properties.
*/
const mapStateToProps = (state: RootImportState) => ({
ns: state.import.namespace,
isOpen: state.import.isOpen,
errors: state.import.errors,
fileType: state.import.fileType,
fileName: state.import.fileName,
status: state.import.status,
delimiter: state.import.delimiter,
stopOnErrors: state.import.stopOnErrors,
ignoreBlanks: state.import.ignoreBlanks,
fields: state.import.fields,
values: state.import.values,
previewLoaded: state.import.previewLoaded,
csvAnalyzed: state.import.analyzeStatus === 'COMPLETED',
analyzeError: state.import.analyzeError,
});
/**
* Export the connected component as the default.
*/
export default connect(mapStateToProps, {
startImport,
cancelImport,
skipCSVAnalyze,
selectImportFileName,
setDelimiter,
setStopOnErrors,
setIgnoreBlanks,
closeImport,
toggleIncludeField,
setFieldType,
})(ImportModal);