Browse Source

update import ui

tumao 4 years ago
parent
commit
195d52d9bd

+ 8 - 4
client/src/components/customSelector/CustomSelector.tsx

@@ -14,17 +14,21 @@ const CustomSelector: FC<CustomSelectorType> = props => {
     options,
     classes,
     variant,
+    wrapperClass = '',
     labelClass = '',
     ...others
   } = props;
   const id = generateId('selector');
 
   return (
-    <FormControl variant={variant} classes={classes}>
-      <InputLabel classes={{ root: labelClass }} htmlFor={id}>
-        {label}
-      </InputLabel>
+    <FormControl variant={variant} className={wrapperClass}>
+      {label && (
+        <InputLabel classes={{ root: labelClass }} htmlFor={id}>
+          {label}
+        </InputLabel>
+      )}
       <Select
+        classes={classes}
         {...others}
         value={value}
         onChange={onChange}

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

@@ -12,13 +12,14 @@ export interface GroupOption {
 }
 
 export type CustomSelectorType = SelectProps & {
-  label: string;
+  label?: string;
   value: string | number;
   options: Option[];
   onChange: (e: React.ChangeEvent<{ value: unknown }>) => void;
   classes?: Partial<ClassNameMap<FormControlClassKey>>;
   variant?: 'filled' | 'outlined' | 'standard';
   labelClass?: string;
+  wrapperClass?: string;
 };
 
 export interface ICustomGroupSelect {

+ 25 - 2
client/src/components/insert/Container.tsx

@@ -48,6 +48,13 @@ const InsertContainer: FC<InsertContentProps> = ({
     label: s._fieldName,
     value: s._fieldId,
   }));
+  const isContainFieldNamesOptions: Option[] = [
+    {
+      label: 'Yes',
+      value: 1,
+    },
+    { label: 'No', value: 0 },
+  ];
 
   const { t: insertTrans } = useTranslation('insert');
   const { t: btnTrans } = useTranslation('btn');
@@ -59,6 +66,12 @@ const InsertContainer: FC<InsertContentProps> = ({
     InsertStatusEnum.init
   );
   // const [nextDisabled, setNextDisabled] = useState<boolean>(false);
+  const [collectionValue, setCollectionValue] =
+    useState<string>(selectedCollection);
+  const [partitionValue, setPartitionValue] =
+    useState<string>(selectedPartition);
+  // use contain field names yes as default
+  const [isContainFieldNames, setIsContainFieldNames] = useState<number>(1);
 
   const BackIcon = icons.back;
 
@@ -98,6 +111,10 @@ const InsertContainer: FC<InsertContentProps> = ({
     };
   }, [insertStatus]);
 
+  const handleUploadedData = (data: string) => {
+    console.log('----- data 102', data);
+  };
+
   const handleInsertData = () => {
     // mock status change
     setInsertStauts(InsertStatusEnum.loading);
@@ -144,9 +161,15 @@ const InsertContainer: FC<InsertContentProps> = ({
         return (
           <InsertImport
             collectionOptions={collectionOptions}
-            selectedCollection={selectedCollection}
             partitionOptions={partitionOptions}
-            selectedPartition={selectedPartition}
+            isContainedOptions={isContainFieldNamesOptions}
+            selectedCollection={collectionValue}
+            selectedPartition={partitionValue}
+            isContainFieldNames={isContainFieldNames}
+            handleCollectionChange={setCollectionValue}
+            handlePartitionChange={setPartitionValue}
+            handleIsContainedChange={setIsContainFieldNames}
+            handleUploadedData={handleUploadedData}
           />
         );
       case InsertStepperEnum.preview:

+ 84 - 38
client/src/components/insert/Import.tsx

@@ -2,38 +2,49 @@ import { useTranslation } from 'react-i18next';
 import Typography from '@material-ui/core/Typography';
 import { makeStyles, Theme, Divider } from '@material-ui/core';
 import CustomSelector from '../customSelector/CustomSelector';
-import { FC } from 'react';
+import { FC, useState } from 'react';
 import { InsertImportProps } from './Types';
 import Uploader from '../uploader/Uploader';
 import { INSERT_CSV_SAMPLE } from '../../consts/Insert';
+import { parseByte } from '../../utils/Format';
 
 const getStyles = makeStyles((theme: Theme) => ({
   tip: {
     color: theme.palette.milvusGrey.dark,
+    fontWeight: 500,
     marginBottom: theme.spacing(1),
   },
-  selectorWrapper: {
-    display: 'flex',
-    justifyContent: 'space-between',
-    alignItems: 'center',
+  selectors: {
+    '& .selectorWrapper': {
+      display: 'flex',
+      justifyContent: 'space-between',
+      alignItems: 'center',
+
+      marginBottom: theme.spacing(3),
+
+      '& .selectLabel': {
+        fontSize: '14px',
+        lineHeight: '20px',
+        color: '#010e29',
+      },
+
+      '& .divider': {
+        width: '20px',
+        margin: theme.spacing(0, 4),
+        backgroundColor: theme.palette.milvusGrey.dark,
+      },
+    },
 
     '& .selector': {
       flexBasis: '40%',
       minWidth: '256px',
     },
 
-    '& .selectLabel': {
-      fontSize: '14px',
-      lineHeight: '20px',
-      color: '#010e29',
-    },
-
-    '& .divider': {
-      width: '20px',
-      margin: theme.spacing(0, 4),
-      backgroundColor: theme.palette.milvusGrey.dark,
+    '& .isContainSelect': {
+      paddingTop: theme.spacing(2),
     },
   },
+
   uploadWrapper: {
     marginTop: theme.spacing(3),
     padding: theme.spacing(1),
@@ -88,18 +99,23 @@ const getStyles = makeStyles((theme: Theme) => ({
 const InsertImport: FC<InsertImportProps> = ({
   collectionOptions,
   partitionOptions,
+  isContainedOptions,
+
   selectedCollection,
   selectedPartition,
+  isContainFieldNames,
+
+  handleCollectionChange,
+  handlePartitionChange,
+  handleIsContainedChange,
+
+  handleUploadedData,
 }) => {
   const { t: insertTrans } = useTranslation('insert');
   const { t: collectionTrans } = useTranslation('collection');
   const { t: partitionTrans } = useTranslation('partition');
   const classes = getStyles();
-
-  const handleCollectionChange = () => {};
-  const handlePartitionChange = () => {};
-
-  const fileName = '';
+  const [fileName, setFileName] = useState<string>('');
 
   return (
     <section>
@@ -107,25 +123,51 @@ const InsertImport: FC<InsertImportProps> = ({
         {insertTrans('targetTip')}
       </Typography>
 
-      <form className={classes.selectorWrapper}>
-        <CustomSelector
-          options={collectionOptions}
-          classes={{ root: 'selector' }}
-          labelClass="selectLabel"
-          value={selectedCollection}
-          variant="filled"
-          label={collectionTrans('collection')}
-          onChange={handleCollectionChange}
-        />
-        <Divider classes={{ root: 'divider' }} />
+      <form className={classes.selectors}>
+        <div className="selectorWrapper">
+          <CustomSelector
+            options={collectionOptions}
+            wrapperClass="selector"
+            labelClass="selectLabel"
+            value={selectedCollection}
+            variant="filled"
+            label={collectionTrans('collection')}
+            onChange={(e: { target: { value: unknown } }) => {
+              const collection = e.target.value;
+              handleCollectionChange(collection as string);
+            }}
+          />
+          <Divider classes={{ root: 'divider' }} />
+          <CustomSelector
+            options={partitionOptions}
+            wrapperClass="selector"
+            labelClass="selectLabel"
+            value={selectedPartition}
+            variant="filled"
+            label={partitionTrans('partition')}
+            onChange={(e: { target: { value: unknown } }) => {
+              const partition = e.target.value;
+              handlePartitionChange(partition as string);
+            }}
+          />
+        </div>
+
+        <label>
+          <Typography className={classes.tip}>
+            {insertTrans('isContainFieldNames')}
+          </Typography>
+        </label>
         <CustomSelector
-          options={partitionOptions}
-          classes={{ root: 'selector' }}
-          labelClass="selectLabel"
-          value={selectedPartition}
+          options={isContainedOptions}
+          wrapperClass="selector"
+          classes={{ filled: 'isContainSelect' }}
+          value={isContainFieldNames}
           variant="filled"
-          label={partitionTrans('partition')}
-          onChange={handlePartitionChange}
+          onChange={(e: { target: { value: unknown } }) => {
+            const isContainedValue = e.target.value;
+            console.log('isContained value', isContainedValue);
+            handleIsContainedChange(isContainedValue as number);
+          }}
         />
       </form>
 
@@ -138,6 +180,10 @@ const InsertImport: FC<InsertImportProps> = ({
             btnClass="uploader"
             label={insertTrans('uploaderLabel')}
             accept=".csv"
+            setFileName={setFileName}
+            handleUploadedData={handleUploadedData}
+            maxSize={parseByte('5m')}
+            overSizeWarning={insertTrans('overSizeWarning')}
           />
           <Typography className="text">
             {fileName || insertTrans('fileNamePlaceHolder')}
@@ -156,7 +202,7 @@ const InsertImport: FC<InsertImportProps> = ({
         </Typography>
         <ul className="noteList">
           {insertTrans('notes', { returnObjects: true }).map(note => (
-            <li className="text noteItem">
+            <li key={note} className="text noteItem">
               <Typography>{note}</Typography>
             </li>
           ))}

+ 10 - 0
client/src/components/insert/Types.ts

@@ -27,10 +27,20 @@ export enum InsertStatusEnum {
 }
 
 export interface InsertImportProps {
+  // selectors options
   collectionOptions: Option[];
   partitionOptions: Option[];
+  isContainedOptions: Option[];
+  // 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
+  handleUploadedData: (data: string) => void;
 }
 
 export interface InsertPreviewProps {

+ 8 - 0
client/src/components/uploader/Types.ts

@@ -2,4 +2,12 @@ export interface UploaderProps {
   label: string;
   accept: string;
   btnClass?: string;
+  // unit should be byte
+  maxSize?: number;
+  // snackbar warning when uploaded file size is over limit
+  overSizeWarning?: string;
+  setFileName: (fileName: string) => void;
+  handleUploadedData: (data: string) => void;
+  handleUploadFileChange?: (file: File) => void;
+  handleUploadError?: () => void;
 }

+ 53 - 2
client/src/components/uploader/Uploader.tsx

@@ -1,5 +1,6 @@
 import { makeStyles, Theme } from '@material-ui/core';
-import { FC, useRef } from 'react';
+import { FC, useContext, useRef } from 'react';
+import { rootContext } from '../../context/Root';
 import CustomButton from '../customButton/CustomButton';
 import { UploaderProps } from './Types';
 
@@ -7,15 +8,65 @@ const getStyles = makeStyles((theme: Theme) => ({
   btn: {},
 }));
 
-const Uploader: FC<UploaderProps> = ({ label, accept, btnClass = '' }) => {
+const Uploader: FC<UploaderProps> = ({
+  label,
+  accept,
+  btnClass = '',
+  maxSize,
+  overSizeWarning = '',
+  handleUploadedData,
+  handleUploadFileChange,
+  handleUploadError,
+  setFileName,
+}) => {
   const inputRef = useRef(null);
   const classes = getStyles();
 
+  const { openSnackBar } = useContext(rootContext);
+
+  const handleUpload = () => {
+    const uploader = inputRef.current! as HTMLFormElement;
+    const reader = new FileReader();
+    // handle uploaded data
+    reader.onload = async e => {
+      const data = reader.result;
+      if (data) {
+        handleUploadedData(data as string);
+      }
+    };
+    // handle upload error
+    reader.onerror = e => {
+      if (handleUploadError) {
+        handleUploadError();
+      }
+      console.error(e);
+    };
+    uploader!.onchange = (e: Event) => {
+      const target = e.target as HTMLInputElement;
+      const file: File = (target.files as FileList)[0];
+      const isSizeOverLimit = maxSize && maxSize < file.size;
+
+      if (!file) {
+        return;
+      }
+      if (isSizeOverLimit) {
+        openSnackBar(overSizeWarning, 'error');
+        return;
+      }
+
+      setFileName(file.name || 'file');
+      handleUploadFileChange && handleUploadFileChange(file);
+      reader.readAsText(file, 'utf8');
+    };
+    uploader.click();
+  };
+
   return (
     <form>
       <CustomButton
         variant="contained"
         className={`${classes.btn} ${btnClass}`}
+        onClick={handleUpload}
       >
         {label}
       </CustomButton>

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

@@ -11,6 +11,8 @@ const insertTrans = {
     `Data size should be less than 5MB and the number of rows should be less than 100000, for the data to be imported properly.`,
     `The "Import Data" option will only append new records. You cannot update existing records using this option.`,
   ],
+  overSizeWarning: 'File data size should less than 5MB',
+  isContainFieldNames: 'First row contains field names?',
 };
 
 export default insertTrans;

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

@@ -11,6 +11,8 @@ const insertTrans = {
     `Data size should be less than 5MB and the number of rows should be less than 100000, for the data to be imported properly.`,
     `The "Import Data" option will only append new records. You cannot update existing records using this option.`,
   ],
+  overSizeWarning: 'File data size should less than 5MB',
+  isContainFieldNames: 'First row contains field names?',
 };
 
 export default insertTrans;

+ 2 - 2
client/src/pages/collections/CreateFields.tsx

@@ -148,6 +148,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
   ) => {
     return (
       <CustomSelector
+        wrapperClass={classes.select}
         options={type === 'all' ? ALL_OPTIONS : VECTOR_FIELDS_OPTIONS}
         onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
           onChange(e.target.value as DataTypeEnum);
@@ -155,7 +156,6 @@ const CreateFields: FC<CreateFieldsProps> = ({
         value={value}
         variant="filled"
         label={label}
-        classes={{ root: classes.select }}
       />
     );
   };
@@ -349,7 +349,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
             setAutoID(autoId);
           }}
           variant="filled"
-          classes={{ root: classes.select }}
+          wrapperClass={classes.select}
         />
 
         {generateDesc(field)}

+ 3 - 3
client/src/pages/schema/CreateForm.tsx

@@ -159,7 +159,7 @@ const CreateForm = (
           indexTypeChange && indexTypeChange(type as string);
         }}
         variant="filled"
-        classes={{ root: classes.select }}
+        wrapperClass={classes.select}
       />
 
       <Typography className={classes.paramTitle}>
@@ -174,7 +174,7 @@ const CreateForm = (
           updateForm('metric_type', type as string);
         }}
         variant="filled"
-        classes={{ root: classes.select }}
+        wrapperClass={classes.select}
       />
 
       {indexParams.includes('m') && (
@@ -186,7 +186,7 @@ const CreateForm = (
             updateForm('m', e.target.value as string)
           }
           variant="filled"
-          classes={{ root: classes.select }}
+          wrapperClass={classes.select}
         />
       )}