Browse Source

update insert ui

tumao 4 years ago
parent
commit
c81d5f054b

BIN
client/src/assets/imgs/insert/fail.png


BIN
client/src/assets/imgs/insert/success.png


+ 1 - 1
client/src/components/customButton/CustomButton.tsx

@@ -19,7 +19,7 @@ const buttonStyle = makeStyles(theme => ({
     backgroundColor: theme.palette.primary.main,
     boxShadow: 'initial',
     fontWeight: 'bold',
-    lineHeight: '16px',
+    lineHeight: '24px',
     '&:hover': {
       backgroundColor: theme.palette.primary.dark,
       boxShadow: 'initial',

+ 15 - 3
client/src/components/customDialog/CustomDialogTitle.tsx

@@ -17,6 +17,9 @@ const getStyles = makeStyles((theme: Theme) => ({
   // closeButton: {
   //   padding: theme.spacing(1),
   // },
+  title: {
+    fontWeight: 500,
+  },
   icon: {
     fontSize: '24px',
     color: '#010e29',
@@ -27,10 +30,17 @@ const getStyles = makeStyles((theme: Theme) => ({
 
 interface IProps extends DialogTitleProps {
   onClose?: () => void;
+  showCloseIcon?: boolean;
 }
 
 const CustomDialogTitle = (props: IProps) => {
-  const { children, classes = { root: '' }, onClose, ...other } = props;
+  const {
+    children,
+    classes = { root: '' },
+    onClose,
+    showCloseIcon = true,
+    ...other
+  } = props;
   const innerClass = getStyles();
 
   const ClearIcon = icons.clear;
@@ -41,8 +51,10 @@ const CustomDialogTitle = (props: IProps) => {
       className={`${innerClass.root} ${classes.root}`}
       {...other}
     >
-      <Typography variant="h5">{children}</Typography>
-      {onClose ? (
+      <Typography variant="h4" className={innerClass.title}>
+        {children}
+      </Typography>
+      {showCloseIcon && onClose ? (
         <ClearIcon
           data-testid="clear-icon"
           classes={{ root: innerClass.icon }}

+ 4 - 1
client/src/components/customDialog/DialogTemplate.tsx

@@ -27,6 +27,7 @@ const DialogTemplate: FC<DialogContainerProps> = ({
   children,
   showActions = true,
   showCancel = true,
+  showCloseIcon = true,
 }) => {
   const { t } = useTranslation('btn');
   const cancel = cancelLabel || t('cancel');
@@ -36,7 +37,9 @@ const DialogTemplate: FC<DialogContainerProps> = ({
 
   return (
     <>
-      <CustomDialogTitle onClose={handleClose}>{title}</CustomDialogTitle>
+      <CustomDialogTitle onClose={handleClose} showCloseIcon={showCloseIcon}>
+        {title}
+      </CustomDialogTitle>
       <DialogContent>{children}</DialogContent>
       {showActions && (
         <DialogActions className={classes.actions}>

+ 1 - 0
client/src/components/customDialog/Types.ts

@@ -24,6 +24,7 @@ export type DialogContainerProps = {
   title: string;
   cancelLabel?: string | ReactElement;
   confirmLabel?: string | ReactElement;
+  showCloseIcon?: boolean;
   handleClose: () => void;
   handleCancel?: () => void;
   handleConfirm: (param: any) => void;

+ 4 - 0
client/src/components/grid/Grid.tsx

@@ -100,6 +100,8 @@ const MilvusGrid: FC<MilvusGridType> = props => {
     disableSelect = false,
     noData = gridTrans.noData,
     showHoverStyle = true,
+    headEditable = false,
+    editHeads = [],
     selected = [],
     setSelected = () => {},
     setRowsPerPage = () => {},
@@ -208,6 +210,8 @@ const MilvusGrid: FC<MilvusGridType> = props => {
           showHoverStyle={showHoverStyle}
           isLoading={isLoading}
           setPageSize={setRowsPerPage}
+          headEditable={headEditable}
+          editHeads={editHeads}
         ></Table>
         {rowCount ? (
           <TablePagination

+ 18 - 11
client/src/components/grid/Table.tsx

@@ -10,6 +10,7 @@ import Checkbox from '@material-ui/core/Checkbox';
 import { TableType } from './Types';
 import { Box, Button, Typography } from '@material-ui/core';
 import EnhancedTableHead from './TableHead';
+import EditableTableHead from './TableEditableHead';
 import { stableSort, getComparator } from './Utils';
 import Copy from '../../components/copy/Copy';
 import ActionBar from './ActionBar';
@@ -111,9 +112,11 @@ const EnhancedTable: FC<TableType> = props => {
     openCheckBox = true,
     disableSelect,
     noData,
-    showHoverStyle,
+    showHoverStyle = true,
     isLoading,
     setPageSize,
+    headEditable = false,
+    editHeads = [],
   } = props;
   const classes = useStyles();
   const [order, setOrder] = React.useState('asc');
@@ -165,16 +168,20 @@ const EnhancedTable: FC<TableType> = props => {
           size="medium"
           aria-label="enhanced table"
         >
-          <EnhancedTableHead
-            colDefinitions={colDefinitions}
-            numSelected={selected.length}
-            order={order}
-            orderBy={orderBy}
-            onSelectAllClick={onSelectedAll}
-            onRequestSort={handleRequestSort}
-            rowCount={rows.length}
-            openCheckBox={openCheckBox}
-          />
+          {!headEditable ? (
+            <EnhancedTableHead
+              colDefinitions={colDefinitions}
+              numSelected={selected.length}
+              order={order}
+              orderBy={orderBy}
+              onSelectAllClick={onSelectedAll}
+              onRequestSort={handleRequestSort}
+              rowCount={rows.length}
+              openCheckBox={openCheckBox}
+            />
+          ) : (
+            <EditableTableHead editHeads={editHeads} />
+          )}
           {!isLoading && (
             <TableBody>
               {rows && rows.length ? (

+ 49 - 0
client/src/components/grid/TableEditableHead.tsx

@@ -0,0 +1,49 @@
+import { FC } from 'react';
+import { TableEditableHeadType } from './Types';
+import { TableHead, TableRow, TableCell, makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles(theme => ({
+  visuallyHidden: {
+    border: 0,
+    clip: 'rect(0 0 0 0)',
+    height: 1,
+    margin: -1,
+    overflow: 'hidden',
+    padding: 0,
+    position: 'absolute',
+    top: 20,
+    width: 1,
+  },
+  tableCell: {
+    // background: theme.palette.common.t,
+    paddingLeft: theme.spacing(2),
+    // borderBottom: 'none',
+  },
+  tableHeader: {
+    textTransform: 'capitalize',
+    color: 'rgba(0, 0, 0, 0.6)',
+    fontSize: '12.8px',
+  },
+  tableRow: {
+    // borderBottom: '1px solid rgba(0, 0, 0, 0.6);',
+  },
+}));
+
+const EditableTableHead: FC<TableEditableHeadType> = props => {
+  const { editHeads } = props;
+  const classes = useStyles();
+
+  return (
+    <TableHead>
+      <TableRow className={classes.tableRow}>
+        {editHeads.map((headCell, index) => (
+          <TableCell key={index} className={classes.tableCell}>
+            {headCell.component}
+          </TableCell>
+        ))}
+      </TableRow>
+    </TableHead>
+  );
+};
+
+export default EditableTableHead;

+ 13 - 0
client/src/components/grid/Types.ts

@@ -53,6 +53,15 @@ export type TableHeadType = {
   openCheckBox?: boolean;
 };
 
+export type TableEditableHeadType = {
+  editHeads: EditableHeads[];
+};
+
+export type EditableHeads = {
+  component: ReactElement;
+  value: string;
+};
+
 export type TableType = {
   selected: any[];
   onSelected: (e: React.MouseEvent, row: any) => void;
@@ -67,6 +76,8 @@ export type TableType = {
   showHoverStyle?: boolean;
   isLoading?: boolean;
   setPageSize?: (size: number) => void;
+  headEditable?: boolean;
+  editHeads: EditableHeads[];
 };
 
 export type ColDefinitionsType = {
@@ -114,6 +125,8 @@ export type MilvusGridType = ToolBarType & {
   disableSelect?: boolean;
   noData?: string;
   showHoverStyle?: boolean;
+  headEditable?: boolean;
+  editHeads?: EditableHeads[];
 };
 
 export type ActionBarType = {

+ 2 - 0
client/src/components/icons/Icons.tsx

@@ -18,6 +18,7 @@ import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
 import ExitToAppIcon from '@material-ui/icons/ExitToApp';
 import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
 import RemoveCircleOutlineIcon from '@material-ui/icons/RemoveCircleOutline';
+import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
 import { SvgIcon } from '@material-ui/core';
 import { ReactComponent as MilvusIcon } from '../../assets/icons/milvus.svg';
 import { ReactComponent as OverviewIcon } from '../../assets/icons/overview.svg';
@@ -48,6 +49,7 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   logout: (props = {}) => <ExitToAppIcon {...props} />,
   rightArrow: (props = {}) => <ArrowForwardIosIcon {...props} />,
   remove: (props = {}) => <RemoveCircleOutlineIcon {...props} />,
+  dropdown: (props = {}) => <ArrowDropDownIcon {...props} />,
 
   milvus: (props = {}) => (
     <SvgIcon viewBox="0 0 44 31" component={MilvusIcon} {...props} />

+ 2 - 1
client/src/components/icons/Types.ts

@@ -25,4 +25,5 @@ export type IconsType =
   | 'load'
   | 'remove'
   | 'key'
-  | 'upload';
+  | 'upload'
+  | 'dropdown';

+ 33 - 24
client/src/components/insert/Container.tsx

@@ -1,5 +1,5 @@
 import { makeStyles, Theme } from '@material-ui/core';
-import { FC, useContext, useMemo, useState } from 'react';
+import { FC, ReactElement, useContext, useMemo, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import DialogTemplate from '../customDialog/DialogTemplate';
 import icons from '../icons/Icons';
@@ -67,22 +67,22 @@ const InsertContainer: FC<InsertContentProps> = ({
   // use contain field names yes as default
   const [isContainFieldNames, setIsContainFieldNames] = useState<number>(1);
   const [fileName, setFileName] = useState<string>('');
+  const [csvData, setCsvData] = useState<any[]>([]);
 
   const BackIcon = icons.back;
 
   const { confirm, cancel } = useMemo(() => {
-    /**
-     * activeStep type is InsertStepperEnum
-     * so index 0 represents import,
-     * index 1 represents preview,
-     * index 2 represents status
-     */
-    const labelList = [
-      {
+    const labelMap: {
+      [key in InsertStepperEnum]: {
+        confirm: string;
+        cancel: string | ReactElement;
+      };
+    } = {
+      [InsertStepperEnum.import]: {
         confirm: btnTrans('next'),
         cancel: btnTrans('cancel'),
       },
-      {
+      [InsertStepperEnum.preview]: {
         confirm: btnTrans('insert'),
         cancel: (
           <>
@@ -91,12 +91,12 @@ const InsertContainer: FC<InsertContentProps> = ({
           </>
         ),
       },
-      {
+      [InsertStepperEnum.status]: {
         confirm: btnTrans('done'),
         cancel: '',
       },
-    ];
-    return labelList[activeStep];
+    };
+    return labelMap[activeStep];
   }, [activeStep, btnTrans, BackIcon, classes.icon]);
 
   const { showActions, showCancel } = useMemo(() => {
@@ -107,16 +107,17 @@ const InsertContainer: FC<InsertContentProps> = ({
   }, [insertStatus]);
 
   const checkUploadFileValidation = (fieldNamesLength: number): boolean => {
-    // return schemaOptions.length === fieldNamesLength;
-    return true;
+    return schemaOptions.length === fieldNamesLength;
   };
 
+  const previewData = useMemo(() => {
+    const end = isContainFieldNames ? 5 : 4;
+    return csvData.slice(0, end);
+  }, [csvData, isContainFieldNames]);
+
   const handleUploadedData = (csv: string) => {
-    // use !! to convert number(0 or 1) to boolean
-    const { data } = parse(csv, { header: !!isContainFieldNames });
-    const uploadFieldNamesLength = !!isContainFieldNames
-      ? data.length
-      : (data as string[])[0].length;
+    const { data } = parse(csv);
+    const uploadFieldNamesLength = (data as string[])[0].length;
     const validation = checkUploadFileValidation(uploadFieldNamesLength);
     if (!validation) {
       // open snackbar
@@ -125,6 +126,7 @@ const InsertContainer: FC<InsertContentProps> = ({
       setFileName('');
       return;
     }
+    setCsvData(data);
   };
 
   const handleInsertData = () => {
@@ -133,7 +135,7 @@ const InsertContainer: FC<InsertContentProps> = ({
     handleInsert();
     setTimeout(() => {
       setInsertStauts(InsertStatusEnum.success);
-    }, 500);
+    }, 1000);
   };
 
   const handleNext = () => {
@@ -176,17 +178,22 @@ const InsertContainer: FC<InsertContentProps> = ({
             partitionOptions={partitionOptions}
             selectedCollection={collectionValue}
             selectedPartition={partitionValue}
-            isContainFieldNames={isContainFieldNames}
             handleCollectionChange={setCollectionValue}
             handlePartitionChange={setPartitionValue}
-            handleIsContainedChange={setIsContainFieldNames}
             handleUploadedData={handleUploadedData}
             fileName={fileName}
             setFileName={setFileName}
           />
         );
       case InsertStepperEnum.preview:
-        return <InsertPreview schemaOptions={schemaOptions} />;
+        return (
+          <InsertPreview
+            schemaOptions={schemaOptions}
+            data={previewData}
+            isContainFieldNames={isContainFieldNames}
+            handleIsContainedChange={setIsContainFieldNames}
+          />
+        );
       // default represents InsertStepperEnum.status
       default:
         return <InsertStatus status={insertStatus} />;
@@ -204,6 +211,8 @@ const InsertContainer: FC<InsertContentProps> = ({
       confirmDisabled={false}
       showActions={showActions}
       showCancel={showCancel}
+      // don't show close icon when insert not finish
+      showCloseIcon={insertStatus !== InsertStatusEnum.loading}
     >
       {generateContent(activeStep)}
     </DialogTemplate>

+ 2 - 35
client/src/components/insert/Import.tsx

@@ -1,13 +1,11 @@
+import { FC } from 'react';
 import { useTranslation } from 'react-i18next';
-import Typography from '@material-ui/core/Typography';
-import { makeStyles, Theme, Divider } from '@material-ui/core';
+import { makeStyles, Theme, Divider, Typography } from '@material-ui/core';
 import CustomSelector from '../customSelector/CustomSelector';
-import { FC } from 'react';
 import { InsertImportProps } from './Types';
 import Uploader from '../uploader/Uploader';
 import { INSERT_CSV_SAMPLE } from '../../consts/Insert';
 import { parseByte } from '../../utils/Format';
-import { Option } from '../customSelector/Types';
 
 const getStyles = makeStyles((theme: Theme) => ({
   tip: {
@@ -40,10 +38,6 @@ const getStyles = makeStyles((theme: Theme) => ({
       flexBasis: '40%',
       minWidth: '256px',
     },
-
-    '& .isContainSelect': {
-      paddingTop: theme.spacing(2),
-    },
   },
 
   uploadWrapper: {
@@ -103,11 +97,9 @@ const InsertImport: FC<InsertImportProps> = ({
 
   selectedCollection,
   selectedPartition,
-  isContainFieldNames,
 
   handleCollectionChange,
   handlePartitionChange,
-  handleIsContainedChange,
 
   handleUploadedData,
   fileName,
@@ -117,13 +109,6 @@ const InsertImport: FC<InsertImportProps> = ({
   const { t: collectionTrans } = useTranslation('collection');
   const { t: partitionTrans } = useTranslation('partition');
   const classes = getStyles();
-  const isContainedOptions: Option[] = [
-    {
-      label: 'Yes',
-      value: 1,
-    },
-    { label: 'No', value: 0 },
-  ];
 
   return (
     <section>
@@ -159,24 +144,6 @@ const InsertImport: FC<InsertImportProps> = ({
             }}
           />
         </div>
-
-        <label>
-          <Typography className={classes.tip}>
-            {insertTrans('isContainFieldNames')}
-          </Typography>
-        </label>
-        <CustomSelector
-          options={isContainedOptions}
-          wrapperClass="selector"
-          classes={{ filled: 'isContainSelect' }}
-          value={isContainFieldNames}
-          variant="filled"
-          onChange={(e: { target: { value: unknown } }) => {
-            const isContainedValue = e.target.value;
-            console.log('isContained value', isContainedValue);
-            handleIsContainedChange(isContainedValue as number);
-          }}
-        />
       </form>
 
       <div className={classes.uploadWrapper}>

+ 221 - 3
client/src/components/insert/Preview.tsx

@@ -1,8 +1,226 @@
-import { FC } from 'react';
+import { FC, useCallback, useEffect, useMemo, useState } from 'react';
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
 import { InsertPreviewProps } from './Types';
+import { Option } from '../customSelector/Types';
+import CustomSelector from '../customSelector/CustomSelector';
+import MilvusGrid from '../grid/Grid';
+import { transferCsvArrayToTableData } from '../../utils/Insert';
+import { ColDefinitionsType } from '../grid/Types';
+import SimpleMenu from '../menu/SimpleMenu';
+import icons from '../icons/Icons';
 
-const InsertPreview: FC<InsertPreviewProps> = ({ schemaOptions }) => {
-  return <div>preview</div>;
+const getStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    width: '75vw',
+  },
+  selectorTip: {
+    color: theme.palette.milvusGrey.dark,
+    fontWeight: 500,
+    marginBottom: theme.spacing(1),
+  },
+  selectorWrapper: {
+    '& .selector': {
+      flexBasis: '40%',
+      minWidth: '256px',
+    },
+
+    '& .isContainSelect': {
+      paddingTop: theme.spacing(2),
+      paddingBottom: theme.spacing(2),
+    },
+  },
+  gridWrapper: {
+    height: '320px',
+  },
+  tableTip: {
+    display: 'flex',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+
+    marginTop: theme.spacing(3),
+    marginBottom: theme.spacing(1),
+
+    '& .text': {
+      color: theme.palette.milvusGrey.dark,
+      fontWeight: 500,
+    },
+  },
+  menuLabel: {
+    display: 'flex',
+    justifyContent: 'space-between',
+    minWidth: '160px',
+
+    color: theme.palette.milvusGrey.dark,
+    backgroundColor: '#fff',
+
+    '&:hover': {
+      backgroundColor: '#fff',
+    },
+  },
+
+  active: {
+    color: theme.palette.primary.main,
+  },
+  menuIcon: {
+    color: theme.palette.milvusGrey.dark,
+  },
+  menuItem: {
+    fontWeight: 500,
+    fontSize: '12px',
+    lineHeight: '16px',
+    color: theme.palette.milvusGrey.dark,
+  },
+  menuActive: {
+    color: theme.palette.primary.main,
+  },
+}));
+
+const getTableData = (
+  data: any[],
+  isContainFieldNames: number
+): { [key in string]: any }[] => {
+  const csvData = isContainFieldNames ? data.slice(1) : data;
+
+  return transferCsvArrayToTableData(csvData);
+};
+
+const getDefaultHeads = (
+  data: any[],
+  isContainFieldNames: number
+): string[] => {
+  return isContainFieldNames ? data[0] : new Array(data[0].length).fill('');
+};
+
+const InsertPreview: FC<InsertPreviewProps> = ({
+  schemaOptions,
+  data,
+  isContainFieldNames,
+  handleIsContainedChange,
+}) => {
+  const classes = getStyles();
+  const { t: insertTrans } = useTranslation('insert');
+
+  const ArrowIcon = icons.dropdown;
+  const tableData = getTableData(data, isContainFieldNames);
+
+  const [tableHeads, setTableHeads] = useState<string[]>(
+    getDefaultHeads(data, isContainFieldNames)
+  );
+
+  const handleTableHeadChange = useCallback(
+    (index: number, label: string) => {
+      const newHeads = [...tableHeads];
+      newHeads[index] = label;
+      setTableHeads(newHeads);
+    },
+    [tableHeads]
+  );
+
+  useEffect(() => {
+    const newHeads = getDefaultHeads(data, isContainFieldNames);
+    setTableHeads(newHeads);
+  }, [data, isContainFieldNames]);
+
+  const editHeads = useMemo(
+    () =>
+      tableHeads.map((head: string, index: number) => ({
+        value: head,
+        component: (
+          <SimpleMenu
+            label={head || insertTrans('requiredFieldName')}
+            menuItems={schemaOptions.map(schema => ({
+              label: schema.label,
+              callback: () => handleTableHeadChange(index, schema.label),
+              wrapperClass: `${classes.menuItem} ${
+                head === schema.label ? classes.menuActive : ''
+              }`,
+            }))}
+            buttonProps={{
+              className: classes.menuLabel,
+              endIcon: <ArrowIcon classes={{ root: classes.menuIcon }} />,
+            }}
+          ></SimpleMenu>
+        ),
+      })),
+    [
+      tableHeads,
+      classes.menuLabel,
+      classes.menuIcon,
+      classes.menuItem,
+      classes.menuActive,
+      ArrowIcon,
+      schemaOptions,
+      insertTrans,
+      handleTableHeadChange,
+    ]
+  );
+
+  const isContainedOptions: Option[] = [
+    {
+      label: 'Yes',
+      value: 1,
+    },
+    { label: 'No', value: 0 },
+  ];
+
+  // use table row first item to get value
+  const colDefinitions: ColDefinitionsType[] = Object.keys(tableData[0])
+    // filter id since we don't want to show it in the table
+    .filter(item => item !== 'id')
+    .map(key => ({
+      id: key,
+      align: 'left',
+      disablePadding: true,
+      label: '',
+    }));
+
+  return (
+    <section className={classes.wrapper}>
+      <form className={classes.selectorWrapper}>
+        <label>
+          <Typography className={classes.selectorTip}>
+            {insertTrans('isContainFieldNames')}
+          </Typography>
+        </label>
+        <CustomSelector
+          options={isContainedOptions}
+          wrapperClass="selector"
+          classes={{ filled: 'isContainSelect' }}
+          value={isContainFieldNames}
+          variant="filled"
+          onChange={(e: { target: { value: unknown } }) => {
+            const isContainedValue = e.target.value;
+            console.log('isContained value', isContainedValue);
+            handleIsContainedChange(isContainedValue as number);
+          }}
+        />
+      </form>
+      <div className={classes.tableTip}>
+        <Typography className="text">
+          {insertTrans('previewTipData')}
+        </Typography>
+        <Typography className="text">
+          {insertTrans('previewTipAction')}
+        </Typography>
+      </div>
+      {tableData.length > 0 && (
+        <div className={classes.gridWrapper}>
+          <MilvusGrid
+            toolbarConfigs={[]}
+            colDefinitions={colDefinitions}
+            rows={tableData}
+            rowCount={0}
+            primaryKey="id"
+            openCheckBox={false}
+            showHoverStyle={false}
+            headEditable={true}
+            editHeads={editHeads}
+          />
+        </div>
+      )}
+    </section>
+  );
 };
 
 export default InsertPreview;

+ 86 - 2
client/src/components/insert/Status.tsx

@@ -1,8 +1,92 @@
 import { FC } from 'react';
-import { InsertStatusProps } from './Types';
+import {
+  makeStyles,
+  Theme,
+  Typography,
+  CircularProgress,
+} from '@material-ui/core';
+import { InsertStatusEnum, InsertStatusProps } from './Types';
+import successPath from '../../assets/imgs/insert/success.png';
+import failPath from '../../assets/imgs/insert/fail.png';
+import { useTranslation } from 'react-i18next';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    width: '75vw',
+    height: (props: { status: InsertStatusEnum }) =>
+      props.status === InsertStatusEnum.loading ? '288px' : '200px',
+
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+    justifyContent: 'center',
+  },
+  loadingTip: {
+    marginBottom: theme.spacing(6),
+  },
+  loadingSvg: {
+    color: theme.palette.primary.main,
+  },
+  text: {
+    marginTop: theme.spacing(3),
+  },
+}));
 
 const InsertStatus: FC<InsertStatusProps> = ({ status }) => {
-  return <div>status</div>;
+  const { t: insertTrans } = useTranslation('insert');
+  const classes = getStyles({ status });
+
+  const InsertSuccess = () => (
+    <>
+      <img src={successPath} alt="insert success" />
+      <Typography variant="h4" className={classes.text}>
+        {insertTrans('statusSuccess')}
+      </Typography>
+    </>
+  );
+
+  const InsertLoading = () => (
+    <>
+      <CircularProgress
+        size={64}
+        thickness={5}
+        classes={{ svg: classes.loadingSvg }}
+      />
+      <Typography variant="h4" className={classes.text}>
+        {insertTrans('statusLoading')}
+      </Typography>
+      <Typography
+        variant="h5"
+        className={`${classes.text} ${classes.loadingTip}`}
+      >
+        {insertTrans('statusLoadingTip')}
+      </Typography>
+    </>
+  );
+  const InsertError = () => (
+    <>
+      <img src={failPath} alt="insert error" />
+      <Typography variant="h4" className={classes.text}>
+        {insertTrans('statusError')}
+      </Typography>
+    </>
+  );
+
+  const generateStatus = (status: InsertStatusEnum) => {
+    switch (status) {
+      case InsertStatusEnum.loading:
+        return <InsertLoading />;
+      case InsertStatusEnum.success:
+        return <InsertSuccess />;
+      // status error or init as default
+      default:
+        return <InsertError />;
+    }
+  };
+
+  return (
+    <section className={classes.wrapper}>{generateStatus(status)}</section>
+  );
 };
 
 export default InsertStatus;

+ 5 - 2
client/src/components/insert/Types.ts

@@ -33,9 +33,8 @@ export interface InsertImportProps {
   // selectors value
   selectedCollection: string;
   selectedPartition: string;
-  isContainFieldNames: number;
+
   // selectors change methods
-  handleIsContainedChange: (isContained: number) => void;
   handleCollectionChange: (collectionName: string) => void;
   handlePartitionChange: (partitionName: string) => void;
   // handle uploaded data
@@ -46,6 +45,10 @@ export interface InsertImportProps {
 
 export interface InsertPreviewProps {
   schemaOptions: Option[];
+  data: any[];
+
+  isContainFieldNames: number;
+  handleIsContainedChange: (isContained: number) => void;
 }
 
 export interface InsertStatusProps {

+ 10 - 1
client/src/components/menu/SimpleMenu.tsx

@@ -10,6 +10,11 @@ import { makeStyles, Theme } from '@material-ui/core';
 const getStyles = makeStyles((theme: Theme) => ({
   menuItem: {
     minWidth: '160px',
+    padding: theme.spacing(1),
+
+    '&:hover': {
+      backgroundColor: '#f9f9f9',
+    },
   },
 }));
 
@@ -59,7 +64,11 @@ const SimpleMenu: FC<SimpleMenuType> = props => {
                 }}
                 key={v.label + i}
               >
-                {v.label}
+                {v.wrapperClass ? (
+                  <span className={v.wrapperClass}>{v.label}</span>
+                ) : (
+                  v.label
+                )}
               </MenuItem>
             ) : (
               <span key={i}>{v.label}</span>

+ 5 - 1
client/src/components/menu/Types.ts

@@ -3,7 +3,11 @@ import { ReactElement } from 'react';
 
 export type SimpleMenuType = {
   label: string;
-  menuItems: { label: string | ReactElement; callback?: () => void }[];
+  menuItems: {
+    label: string | ReactElement;
+    callback?: () => void;
+    wrapperClass?: string;
+  }[];
   buttonProps?: ButtonProps;
   className?: string;
 };

+ 1 - 1
client/src/components/uploader/Uploader.tsx

@@ -44,7 +44,7 @@ const Uploader: FC<UploaderProps> = ({
     uploader!.onchange = (e: Event) => {
       const target = e.target as HTMLInputElement;
       const file: File = (target.files as FileList)[0];
-      const isSizeOverLimit = maxSize && maxSize < file.size;
+      const isSizeOverLimit = file && maxSize && maxSize < file.size;
 
       if (!file) {
         return;

+ 8 - 0
client/src/i18n/cn/insert.ts

@@ -15,6 +15,14 @@ const insertTrans = {
   isContainFieldNames: 'First row contains field names?',
   uploadFieldNamesLenWarning:
     'Uploaded data column count is not equal to schema count',
+  previewTipData: 'Data Preview(Top 4 rows shown)',
+  previewTipAction: '*Change header cell selector value to edit field name',
+  requiredFieldName: 'Field Name*',
+
+  statusLoading: 'Your data is importing now...It may take few minutes',
+  statusLoadingTip: 'Please wait patiently, thank you',
+  statusSuccess: 'Import Data Successfully!',
+  statusError: 'Import Data Failed!',
 };
 
 export default insertTrans;

+ 8 - 0
client/src/i18n/en/insert.ts

@@ -15,6 +15,14 @@ const insertTrans = {
   isContainFieldNames: 'First row contains field names?',
   uploadFieldNamesLenWarning:
     'Uploaded data column count is not equal to schema count',
+  previewTipData: 'Data Preview(Top 4 rows shown)',
+  previewTipAction: '*Change header cell selector value to edit field name',
+  requiredFieldName: 'Field Name*',
+
+  statusLoading: 'Your data is importing now...It may take few minutes',
+  statusLoadingTip: 'Please wait patiently, thank you',
+  statusSuccess: 'Import Data Successfully!',
+  statusError: 'Import Data Failed!',
 };
 
 export default insertTrans;

+ 0 - 2
client/src/pages/collections/Collections.tsx

@@ -379,8 +379,6 @@ const Collections = () => {
           rows={collectionList}
           rowCount={total}
           primaryKey="_name"
-          openCheckBox={true}
-          showHoverStyle={true}
           selected={selectedCollections}
           setSelected={handleSelectChange}
           page={currentPage}

+ 0 - 2
client/src/pages/partitions/Partitions.tsx

@@ -319,8 +319,6 @@ const Partitions: FC<{
         rows={partitionList}
         rowCount={total}
         primaryKey="id"
-        openCheckBox={true}
-        showHoverStyle={true}
         selected={selectedPartitions}
         setSelected={handleSelectChange}
         page={currentPage}

+ 0 - 1
client/src/pages/schema/Schema.tsx

@@ -210,7 +210,6 @@ const Schema: FC<{
         rows={schemaList}
         rowCount={total}
         primaryKey="_fieldId"
-        openCheckBox={false}
         showHoverStyle={false}
         page={currentPage}
         onChangePage={handlePageChange}

+ 13 - 0
client/src/utils/Insert.ts

@@ -0,0 +1,13 @@
+import { generateId } from './Common';
+
+/**
+ * function to convert uploaded csv to MilvusGrid component accepted data type
+ * @param data uploaded csv data, e.g. [['name1', 12], ['name2', 14]]
+ * @returns key value pair object array, use index as key, e.g. [{0: 'name1', 1: 12}, {0: 'name2', 1: 14}]
+ */
+export const transferCsvArrayToTableData = (data: any[][]) => {
+  return data.reduce(
+    (result, arr) => [...result, { ...arr, id: generateId() }],
+    []
+  );
+};