Browse Source

update insert validation check warning

tumao 4 years ago
parent
commit
17974d30ac

+ 64 - 14
client/src/components/insert/Container.tsx

@@ -19,7 +19,6 @@ import {
   InsertContentProps,
   InsertStatusEnum,
   InsertStepperEnum,
-  SchemaOption,
 } from './Types';
 import { Option } from '../customSelector/Types';
 import { parse } from 'papaparse';
@@ -204,32 +203,83 @@ const InsertContainer: FC<InsertContentProps> = ({
     [collections, defaultSelectedCollection]
   );
 
-  const schemaOptions: SchemaOption[] = useMemo(() => {
+  const {
+    schemaOptions,
+    autoIdFieldName,
+  }: { schemaOptions: Option[]; autoIdFieldName: string } = useMemo(() => {
+    /**
+     * on collection page, we get schema data from collection
+     * on partition page, we pass schema as props
+     */
     const list =
       schema && schema.length > 0
         ? schema
         : collections.find(c => c._name === collectionValue)?._fields;
 
-    return (list || []).map(s => ({
-      label: s._fieldName,
-      value: s._fieldId,
-      isPrimaryKey: s._isPrimaryKey,
-    }));
+    const autoIdFieldName =
+      list?.find(item => item._isPrimaryKey && item._isAutoId)?._fieldName ||
+      '';
+    /**
+     * if below conditions all met, this schema shouldn't be selectable as head:
+     * 1. this field is primary key
+     * 2. this field auto id is true
+     */
+    const options = (list || [])
+      .filter(s => !s._isAutoId || !s._isPrimaryKey)
+      .map(s => ({
+        label: s._fieldName,
+        value: s._fieldId,
+      }));
+    return {
+      schemaOptions: options,
+      autoIdFieldName,
+    };
   }, [schema, collectionValue, collections]);
 
-  const checkUploadFileValidation = (fieldNamesLength: number): boolean => {
+  const checkUploadFileValidation = (firstRowItems: string[]): boolean => {
+    const uploadFieldNamesLength = firstRowItems.length;
     return (
-      schemaOptions.filter(s => !s.isPrimaryKey).length === fieldNamesLength
+      checkIsAutoIdFieldValid(firstRowItems) ||
+      checkColumnLength(uploadFieldNamesLength)
     );
   };
 
+  /**
+   * when primary key field auto id is true
+   * no need to upload this field data
+   * @param firstRowItems uploaded file first row items
+   * @returns whether invalid, true means invalid
+   */
+  const checkIsAutoIdFieldValid = (firstRowItems: string[]): boolean => {
+    const isContainAutoIdField = firstRowItems.includes(autoIdFieldName);
+    isContainAutoIdField &&
+      openSnackBar(
+        insertTrans('uploadAutoIdFieldWarning', { fieldName: autoIdFieldName }),
+        'error'
+      );
+    return isContainAutoIdField;
+  };
+
+  /**
+   * uploaded file column length should be equal to schema length
+   * @param fieldNamesLength every row items length
+   * @returns whether invalid, true means invalid
+   */
+  const checkColumnLength = (fieldNamesLength: number): boolean => {
+    const isLengthEqual = schemaOptions.length === fieldNamesLength;
+    // if not equal, open warning snackbar
+    !isLengthEqual &&
+      openSnackBar(insertTrans('uploadFieldNamesLenWarning'), 'error');
+    return !isLengthEqual;
+  };
+
   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');
+    // if uploaded csv contains heads, firstRowItems is the list of all heads
+    const [firstRowItems = []] = data as string[][];
+
+    const invalid = checkUploadFileValidation(firstRowItems);
+    if (invalid) {
       // reset uploader value and filename
       setFileName('');
       setFile(null);

+ 9 - 3
client/src/components/insert/Import.tsx

@@ -4,7 +4,7 @@ import { makeStyles, Theme, Divider, Typography } from '@material-ui/core';
 import CustomSelector from '../customSelector/CustomSelector';
 import { InsertImportProps } from './Types';
 import Uploader from '../uploader/Uploader';
-import { INSERT_CSV_SAMPLE } from '../../consts/Insert';
+import { INSERT_CSV_SAMPLE, INSERT_MAX_SIZE } from '../../consts/Insert';
 import { parseByte } from '../../utils/Format';
 
 const getStyles = makeStyles((theme: Theme) => ({
@@ -159,10 +159,16 @@ const InsertImport: FC<InsertImportProps> = ({
             btnClass="uploader"
             label={insertTrans('uploaderLabel')}
             accept=".csv"
+            // selected collection will affect schema, which is required for uploaded data validation check
+            // so upload file should be disabled until user select one collection
+            disabled={!selectedCollection}
+            disableTooltip={insertTrans('uploadFileDisableTooltip')}
             setFileName={setFileName}
             handleUploadedData={handleUploadedData}
-            maxSize={parseByte('150m')}
-            overSizeWarning={insertTrans('overSizeWarning')}
+            maxSize={parseByte(`${INSERT_MAX_SIZE}m`)}
+            overSizeWarning={insertTrans('overSizeWarning', {
+              size: INSERT_MAX_SIZE,
+            })}
             handleUploadFileChange={handleUploadFileChange}
           />
           <Typography className="text">

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

@@ -75,7 +75,3 @@ export interface InsertStatusProps {
   status: InsertStatusEnum;
   failMsg: string;
 }
-
-export interface SchemaOption extends Option {
-  isPrimaryKey: boolean;
-}

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

@@ -2,6 +2,8 @@ export interface UploaderProps {
   label: string;
   accept: string;
   btnClass?: string;
+  disabled?: boolean;
+  disableTooltip?: string;
   // unit should be byte
   maxSize?: number;
   // snackbar warning when uploaded file size is over limit

+ 4 - 0
client/src/components/uploader/Uploader.tsx

@@ -12,6 +12,8 @@ const Uploader: FC<UploaderProps> = ({
   label,
   accept,
   btnClass = '',
+  disabled = false,
+  disableTooltip = '',
   maxSize,
   overSizeWarning = '',
   handleUploadedData,
@@ -69,6 +71,8 @@ const Uploader: FC<UploaderProps> = ({
         variant="contained"
         className={`${classes.btn} ${btnClass}`}
         onClick={handleUpload}
+        disabled={disabled}
+        tooltip={disabled ? disableTooltip : ''}
       >
         {label}
       </CustomButton>

+ 2 - 0
client/src/consts/Insert.ts

@@ -2,3 +2,5 @@ export const INSERT_CSV_SAMPLE = `Date, Country, Units, Revenue,\n
 1, 183, [13848...], [318998...]\n
 909,3898,[3898...], [84981...]\n
 ...`;
+
+export const INSERT_MAX_SIZE = 150;

+ 5 - 0
client/src/http/Field.ts

@@ -9,6 +9,7 @@ export class FieldHttp extends BaseModel implements FieldData {
   is_primary_key!: true;
   name!: string;
   description!: string;
+  autoID!: boolean;
 
   constructor(props: {}) {
     super(props);
@@ -34,6 +35,10 @@ export class FieldHttp extends BaseModel implements FieldData {
     return this.is_primary_key;
   }
 
+  get _isAutoId() {
+    return this.autoID;
+  }
+
   get _fieldName() {
     return this.name;
   }

+ 5 - 1
client/src/i18n/cn/insert.ts

@@ -11,10 +11,14 @@ 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',
+  overSizeWarning: 'File data size should less than {{size}}MB',
   isContainFieldNames: 'First row contains field names?',
+
+  uploadFileDisableTooltip: 'Please select collection before uploading',
   uploadFieldNamesLenWarning:
     'Uploaded data column count is not equal to schema count',
+  uploadAutoIdFieldWarning:
+    'AutoId field ({{fieldName}}) does not require data',
   previewTipData: 'Data Preview(Top 4 rows shown)',
   previewTipAction: '*Change header cell selector value to edit field name',
   requiredFieldName: 'Field Name*',

+ 6 - 1
client/src/i18n/en/insert.ts

@@ -11,10 +11,15 @@ 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',
+  overSizeWarning: 'File data size should less than {{size}}MB',
   isContainFieldNames: 'First row contains field names?',
+
+  uploadFileDisableTooltip: 'Please select collection before uploading',
   uploadFieldNamesLenWarning:
     'Uploaded data column count is not equal to schema count',
+  uploadAutoIdFieldWarning:
+    'AutoId field ({{fieldName}}) does not require data',
+
   previewTipData: 'Data Preview(Top 4 rows shown)',
   previewTipAction: '*Change header cell selector value to edit field name',
   requiredFieldName: 'Field Name*',

+ 2 - 2
client/src/pages/schema/Types.ts

@@ -27,6 +27,7 @@ export interface Field {
 export interface FieldData {
   _fieldId: string;
   _isPrimaryKey: boolean;
+  _isAutoId: boolean;
   _fieldName: string;
   _fieldNameElement?: ReactElement;
   _fieldType: DataType;
@@ -61,8 +62,7 @@ export type IndexType =
   | INDEX_TYPES_ENUM.HNSW
   | INDEX_TYPES_ENUM.ANNOY
   | INDEX_TYPES_ENUM.BIN_IVF_FLAT
-  | INDEX_TYPES_ENUM.BIN_FLAT
-
+  | INDEX_TYPES_ENUM.BIN_FLAT;
 
 export interface IndexManageParam {
   collection_name: string;

+ 2 - 1
client/src/pages/seach/SearchParams.tsx

@@ -138,7 +138,8 @@ const SearchParams: FC<SearchParamsProps> = ({
   const getSearchInputConfig = useCallback(
     (paramKey: searchKeywordsType): ITextfieldConfig => {
       const nlist = Number(
-        indexParams.find(p => p.key === 'nlist')?.value || 0
+        // nlist range is [1, 65536], if user didn't create index, we set 1024 as default nlist value
+        indexParams.find(p => p.key === 'nlist')?.value || 1024
       );
 
       const configParamMap: {

+ 7 - 1
client/src/pages/seach/Styles.ts

@@ -10,7 +10,7 @@ export const getVectorSearchStyles = makeStyles((theme: Theme) => ({
       flexDirection: 'column',
       flexBasis: '33%',
 
-      padding: theme.spacing(2, 3, 3),
+      padding: theme.spacing(2, 3, 4),
       backgroundColor: '#fff',
       borderRadius: theme.spacing(0.5),
       boxShadow: '3px 3px 10px rgba(0, 0, 0, 0.05)',
@@ -64,6 +64,12 @@ export const getVectorSearchStyles = makeStyles((theme: Theme) => ({
       margin: theme.spacing(0, 1),
     },
 
+    // Textfield component has more bottom space to show error msg when validation
+    // if still set padding-bottom, the whole form space will be stretched
+    '& .field-params': {
+      paddingBottom: 0,
+    },
+
     '& .text': {
       color: theme.palette.milvusGrey.dark,
       fontWeight: 500,

+ 1 - 1
client/src/pages/seach/VectorSearch.tsx

@@ -318,7 +318,7 @@ const VectorSearch = () => {
           />
         </fieldset>
         {/* search params selectors */}
-        <fieldset className="field">
+        <fieldset className="field field-params">
           <Typography className="text">{searchTrans('thirdTip')}</Typography>
           <SearchParams
             wrapperClass={classes.paramsWrapper}