|
@@ -1,5 +1,13 @@
|
|
|
import { makeStyles, Theme } from '@material-ui/core';
|
|
|
-import { FC, ReactElement, useContext, useMemo, useState } from 'react';
|
|
|
+import {
|
|
|
+ FC,
|
|
|
+ ReactElement,
|
|
|
+ useCallback,
|
|
|
+ useContext,
|
|
|
+ useEffect,
|
|
|
+ useMemo,
|
|
|
+ useState,
|
|
|
+} from 'react';
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
import DialogTemplate from '../customDialog/DialogTemplate';
|
|
|
import icons from '../icons/Icons';
|
|
@@ -14,6 +22,8 @@ import {
|
|
|
} from './Types';
|
|
|
import { Option } from '../customSelector/Types';
|
|
|
import { parse } from 'papaparse';
|
|
|
+import { PartitionHttp } from '../../http/Partition';
|
|
|
+import { combineHeadsAndData } from '../../utils/Insert';
|
|
|
|
|
|
const getStyles = makeStyles((theme: Theme) => ({
|
|
|
icon: {
|
|
@@ -23,33 +33,20 @@ const getStyles = makeStyles((theme: Theme) => ({
|
|
|
|
|
|
/**
|
|
|
* this component contains processes during insert
|
|
|
- * all datas and methods passed in as props, no interactions with server done in it
|
|
|
+ * including import, preview and status
|
|
|
*/
|
|
|
|
|
|
const InsertContainer: FC<InsertContentProps> = ({
|
|
|
- collections,
|
|
|
- selectedCollection,
|
|
|
- partitions,
|
|
|
- selectedPartition,
|
|
|
- schema,
|
|
|
+ collections = [],
|
|
|
+ defaultSelectedCollection,
|
|
|
+ defaultSelectedPartition,
|
|
|
+
|
|
|
+ partitions = [],
|
|
|
+ schema = [],
|
|
|
handleInsert,
|
|
|
}) => {
|
|
|
const classes = getStyles();
|
|
|
|
|
|
- // props children component needed:
|
|
|
- const collectionOptions: Option[] = collections.map(c => ({
|
|
|
- label: c._name,
|
|
|
- value: c._name,
|
|
|
- }));
|
|
|
- const partitionOptions: Option[] = partitions.map(p => ({
|
|
|
- label: p._name,
|
|
|
- value: p._name,
|
|
|
- }));
|
|
|
- const schemaOptions: Option[] = schema.map(s => ({
|
|
|
- label: s._fieldName,
|
|
|
- value: s._fieldId,
|
|
|
- }));
|
|
|
-
|
|
|
const { t: insertTrans } = useTranslation('insert');
|
|
|
const { t: btnTrans } = useTranslation('btn');
|
|
|
const { handleCloseDialog, openSnackBar } = useContext(rootContext);
|
|
@@ -59,14 +56,18 @@ const InsertContainer: FC<InsertContentProps> = ({
|
|
|
const [insertStatus, setInsertStauts] = useState<InsertStatusEnum>(
|
|
|
InsertStatusEnum.init
|
|
|
);
|
|
|
- // const [nextDisabled, setNextDisabled] = useState<boolean>(false);
|
|
|
+ const [insertFailMsg, setInsertFailMsg] = useState<string>('');
|
|
|
+
|
|
|
+ const [nextDisabled, setNextDisabled] = useState<boolean>(false);
|
|
|
|
|
|
// selected collection name
|
|
|
- const [collectionValue, setCollectionValue] =
|
|
|
- useState<string>(selectedCollection);
|
|
|
+ const [collectionValue, setCollectionValue] = useState<string>(
|
|
|
+ defaultSelectedCollection
|
|
|
+ );
|
|
|
// selected partition name
|
|
|
- const [partitionValue, setPartitionValue] =
|
|
|
- useState<string>(selectedPartition);
|
|
|
+ const [partitionValue, setPartitionValue] = useState<string>(
|
|
|
+ defaultSelectedPartition
|
|
|
+ );
|
|
|
// use contain field names yes as default
|
|
|
const [isContainFieldNames, setIsContainFieldNames] = useState<number>(1);
|
|
|
// uploaded file name
|
|
@@ -74,6 +75,77 @@ const InsertContainer: FC<InsertContentProps> = ({
|
|
|
// uploaded csv data (type: string)
|
|
|
const [csvData, setCsvData] = useState<any[]>([]);
|
|
|
|
|
|
+ // handle changed table heads
|
|
|
+ const [tableHeads, setTableHeads] = useState<string[]>([]);
|
|
|
+
|
|
|
+ const [partitionOptions, setPartitionOptions] = useState<Option[]>([]);
|
|
|
+
|
|
|
+ const previewData = useMemo(() => {
|
|
|
+ // we only show top 4 results of uploaded csv data
|
|
|
+ const end = isContainFieldNames ? 5 : 4;
|
|
|
+ return csvData.slice(0, end);
|
|
|
+ }, [csvData, isContainFieldNames]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (activeStep === InsertStepperEnum.import) {
|
|
|
+ /**
|
|
|
+ * 1. must choose collection and partition
|
|
|
+ * 2. must upload a csv file
|
|
|
+ */
|
|
|
+ const selectValid = collectionValue !== '' && partitionValue !== '';
|
|
|
+ const uploadValid = csvData.length > 0;
|
|
|
+ const condition = selectValid && uploadValid;
|
|
|
+ setNextDisabled(!condition);
|
|
|
+ }
|
|
|
+ if (activeStep === InsertStepperEnum.preview) {
|
|
|
+ /**
|
|
|
+ * table heads shouldn't be empty
|
|
|
+ */
|
|
|
+ const headsValid = tableHeads.every(h => h !== '');
|
|
|
+ setNextDisabled(!headsValid);
|
|
|
+ }
|
|
|
+ }, [activeStep, collectionValue, partitionValue, csvData, tableHeads]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ const heads = isContainFieldNames
|
|
|
+ ? previewData[0]
|
|
|
+ : new Array(previewData[0].length).fill('');
|
|
|
+
|
|
|
+ setTableHeads(heads);
|
|
|
+ }, [previewData, isContainFieldNames]);
|
|
|
+
|
|
|
+ const fetchPartition = useCallback(async () => {
|
|
|
+ if (collectionValue) {
|
|
|
+ const partitions = await PartitionHttp.getPartitions(collectionValue);
|
|
|
+ const partitionOptions: Option[] = partitions.map(p => ({
|
|
|
+ label: p._formatName,
|
|
|
+ value: p._name,
|
|
|
+ }));
|
|
|
+ setPartitionOptions(partitionOptions);
|
|
|
+ }
|
|
|
+ }, [collectionValue]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ // if not on partitions page, we need to fetch partitions according to selected collection
|
|
|
+ if (partitions.length === 0) {
|
|
|
+ fetchPartition();
|
|
|
+ } else {
|
|
|
+ const options = partitions
|
|
|
+ .map(p => ({
|
|
|
+ label: p._formatName,
|
|
|
+ value: p._name,
|
|
|
+ }))
|
|
|
+ // when there's single selected partition
|
|
|
+ // insert dialog partitions shouldn't selectable
|
|
|
+ .filter(
|
|
|
+ partition =>
|
|
|
+ partition.label === defaultSelectedPartition ||
|
|
|
+ defaultSelectedPartition === ''
|
|
|
+ );
|
|
|
+ setPartitionOptions(options);
|
|
|
+ }
|
|
|
+ }, [partitions, fetchPartition, defaultSelectedPartition]);
|
|
|
+
|
|
|
const BackIcon = icons.back;
|
|
|
|
|
|
// modal actions part, buttons label text or component
|
|
@@ -112,37 +184,78 @@ const InsertContainer: FC<InsertContentProps> = ({
|
|
|
};
|
|
|
}, [insertStatus]);
|
|
|
|
|
|
+ // props children component needed:
|
|
|
+ const collectionOptions: Option[] = useMemo(
|
|
|
+ () =>
|
|
|
+ defaultSelectedCollection === ''
|
|
|
+ ? collections.map(c => ({
|
|
|
+ label: c._name,
|
|
|
+ value: c._name,
|
|
|
+ }))
|
|
|
+ : [
|
|
|
+ {
|
|
|
+ label: defaultSelectedCollection,
|
|
|
+ value: defaultSelectedCollection,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ [collections, defaultSelectedCollection]
|
|
|
+ );
|
|
|
+
|
|
|
+ const schemaOptions: Option[] = useMemo(() => {
|
|
|
+ const list =
|
|
|
+ schema.length > 0
|
|
|
+ ? schema
|
|
|
+ : collections.find(c => c._name === collectionValue)?._fields;
|
|
|
+ return (list || []).map(s => ({
|
|
|
+ label: s._fieldName,
|
|
|
+ value: s._fieldId,
|
|
|
+ }));
|
|
|
+ }, [schema, collectionValue, collections]);
|
|
|
+
|
|
|
const checkUploadFileValidation = (fieldNamesLength: number): boolean => {
|
|
|
return schemaOptions.length === fieldNamesLength;
|
|
|
};
|
|
|
|
|
|
- const previewData = useMemo(() => {
|
|
|
- // we only show top 4 results of uploaded csv data
|
|
|
- const end = isContainFieldNames ? 5 : 4;
|
|
|
- return csvData.slice(0, end);
|
|
|
- }, [csvData, isContainFieldNames]);
|
|
|
-
|
|
|
- const handleUploadedData = (csv: string) => {
|
|
|
+ const handleUploadedData = (csv: string, uploader: HTMLFormElement) => {
|
|
|
const { data } = parse(csv);
|
|
|
const uploadFieldNamesLength = (data as string[])[0].length;
|
|
|
const validation = checkUploadFileValidation(uploadFieldNamesLength);
|
|
|
if (!validation) {
|
|
|
// open snackbar
|
|
|
openSnackBar(insertTrans('uploadFieldNamesLenWarning'), 'error');
|
|
|
- // reset filename
|
|
|
+ // reset uploader value and filename
|
|
|
setFileName('');
|
|
|
+ uploader.value = null;
|
|
|
return;
|
|
|
}
|
|
|
setCsvData(data);
|
|
|
};
|
|
|
|
|
|
const handleInsertData = async () => {
|
|
|
+ // combine table heads and data
|
|
|
+ const tableData = isContainFieldNames ? csvData.slice(1) : csvData;
|
|
|
+ const data = combineHeadsAndData(tableHeads, tableData);
|
|
|
+
|
|
|
setInsertStauts(InsertStatusEnum.loading);
|
|
|
- const res = await handleInsert();
|
|
|
- const status = res ? InsertStatusEnum.success : InsertStatusEnum.error;
|
|
|
+ const { result, msg } = await handleInsert(
|
|
|
+ collectionValue,
|
|
|
+ partitionValue,
|
|
|
+ data
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!result) {
|
|
|
+ setInsertFailMsg(msg);
|
|
|
+ }
|
|
|
+ const status = result ? InsertStatusEnum.success : InsertStatusEnum.error;
|
|
|
setInsertStauts(status);
|
|
|
};
|
|
|
|
|
|
+ const handleCollectionChange = (name: string) => {
|
|
|
+ setCollectionValue(name);
|
|
|
+ // reset partition
|
|
|
+ setPartitionValue('');
|
|
|
+ };
|
|
|
+
|
|
|
const handleNext = () => {
|
|
|
switch (activeStep) {
|
|
|
case InsertStepperEnum.import:
|
|
@@ -183,7 +296,7 @@ const InsertContainer: FC<InsertContentProps> = ({
|
|
|
partitionOptions={partitionOptions}
|
|
|
selectedCollection={collectionValue}
|
|
|
selectedPartition={partitionValue}
|
|
|
- handleCollectionChange={setCollectionValue}
|
|
|
+ handleCollectionChange={handleCollectionChange}
|
|
|
handlePartitionChange={setPartitionValue}
|
|
|
handleUploadedData={handleUploadedData}
|
|
|
fileName={fileName}
|
|
@@ -195,13 +308,15 @@ const InsertContainer: FC<InsertContentProps> = ({
|
|
|
<InsertPreview
|
|
|
schemaOptions={schemaOptions}
|
|
|
data={previewData}
|
|
|
+ tableHeads={tableHeads}
|
|
|
+ setTableHeads={setTableHeads}
|
|
|
isContainFieldNames={isContainFieldNames}
|
|
|
handleIsContainedChange={setIsContainFieldNames}
|
|
|
/>
|
|
|
);
|
|
|
// default represents InsertStepperEnum.status
|
|
|
default:
|
|
|
- return <InsertStatus status={insertStatus} />;
|
|
|
+ return <InsertStatus status={insertStatus} failMsg={insertFailMsg} />;
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -213,7 +328,7 @@ const InsertContainer: FC<InsertContentProps> = ({
|
|
|
cancelLabel={cancel}
|
|
|
handleCancel={handleBack}
|
|
|
handleConfirm={handleNext}
|
|
|
- confirmDisabled={false}
|
|
|
+ confirmDisabled={nextDisabled}
|
|
|
showActions={showActions}
|
|
|
showCancel={showCancel}
|
|
|
// don't show close icon when insert not finish
|