Browse Source

Merge pull request #173 from Tumao727/bugfix/vector-search

Fix vector search style problem and update insert uploading validation check warning
ryjiang 4 years ago
parent
commit
9189e36e4a

+ 1 - 1
client/src/components/grid/Table.tsx

@@ -268,7 +268,7 @@ const EnhancedTable: FC<TableType> = props => {
                           ) : (
                           ) : (
                             <TableCell
                             <TableCell
                               key={'cell' + row[primaryKey] + i}
                               key={'cell' + row[primaryKey] + i}
-                              padding={i === 0 ? 'none' : 'default'}
+                              // padding={i === 0 ? 'none' : 'default'}
                               align={colDef.align || 'left'}
                               align={colDef.align || 'left'}
                               className={`${classes.cell} ${classes.tableCell}`}
                               className={`${classes.cell} ${classes.tableCell}`}
                               style={cellStyle}
                               style={cellStyle}

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

@@ -19,7 +19,6 @@ import {
   InsertContentProps,
   InsertContentProps,
   InsertStatusEnum,
   InsertStatusEnum,
   InsertStepperEnum,
   InsertStepperEnum,
-  SchemaOption,
 } from './Types';
 } from './Types';
 import { Option } from '../customSelector/Types';
 import { Option } from '../customSelector/Types';
 import { parse } from 'papaparse';
 import { parse } from 'papaparse';
@@ -204,32 +203,83 @@ const InsertContainer: FC<InsertContentProps> = ({
     [collections, defaultSelectedCollection]
     [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 =
     const list =
       schema && schema.length > 0
       schema && schema.length > 0
         ? schema
         ? schema
         : collections.find(c => c._name === collectionValue)?._fields;
         : 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]);
   }, [schema, collectionValue, collections]);
 
 
-  const checkUploadFileValidation = (fieldNamesLength: number): boolean => {
+  const checkUploadFileValidation = (firstRowItems: string[]): boolean => {
+    const uploadFieldNamesLength = firstRowItems.length;
     return (
     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 handleUploadedData = (csv: string, uploader: HTMLFormElement) => {
     const { data } = parse(csv);
     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
       // reset uploader value and filename
       setFileName('');
       setFileName('');
       setFile(null);
       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 CustomSelector from '../customSelector/CustomSelector';
 import { InsertImportProps } from './Types';
 import { InsertImportProps } from './Types';
 import Uploader from '../uploader/Uploader';
 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';
 import { parseByte } from '../../utils/Format';
 
 
 const getStyles = makeStyles((theme: Theme) => ({
 const getStyles = makeStyles((theme: Theme) => ({
@@ -159,10 +159,16 @@ const InsertImport: FC<InsertImportProps> = ({
             btnClass="uploader"
             btnClass="uploader"
             label={insertTrans('uploaderLabel')}
             label={insertTrans('uploaderLabel')}
             accept=".csv"
             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}
             setFileName={setFileName}
             handleUploadedData={handleUploadedData}
             handleUploadedData={handleUploadedData}
-            maxSize={parseByte('150m')}
-            overSizeWarning={insertTrans('overSizeWarning')}
+            maxSize={parseByte(`${INSERT_MAX_SIZE}m`)}
+            overSizeWarning={insertTrans('overSizeWarning', {
+              size: INSERT_MAX_SIZE,
+            })}
             handleUploadFileChange={handleUploadFileChange}
             handleUploadFileChange={handleUploadFileChange}
           />
           />
           <Typography className="text">
           <Typography className="text">

+ 1 - 1
client/src/components/insert/Preview.tsx

@@ -155,7 +155,7 @@ const InsertPreview: FC<InsertPreviewProps> = ({
     .map(key => ({
     .map(key => ({
       id: key,
       id: key,
       align: 'left',
       align: 'left',
-      disablePadding: true,
+      disablePadding: false,
       label: '',
       label: '',
     }));
     }));
 
 

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

@@ -75,7 +75,3 @@ export interface InsertStatusProps {
   status: InsertStatusEnum;
   status: InsertStatusEnum;
   failMsg: string;
   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;
   label: string;
   accept: string;
   accept: string;
   btnClass?: string;
   btnClass?: string;
+  disabled?: boolean;
+  disableTooltip?: string;
   // unit should be byte
   // unit should be byte
   maxSize?: number;
   maxSize?: number;
   // snackbar warning when uploaded file size is over limit
   // 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,
   label,
   accept,
   accept,
   btnClass = '',
   btnClass = '',
+  disabled = false,
+  disableTooltip = '',
   maxSize,
   maxSize,
   overSizeWarning = '',
   overSizeWarning = '',
   handleUploadedData,
   handleUploadedData,
@@ -69,6 +71,8 @@ const Uploader: FC<UploaderProps> = ({
         variant="contained"
         variant="contained"
         className={`${classes.btn} ${btnClass}`}
         className={`${classes.btn} ${btnClass}`}
         onClick={handleUpload}
         onClick={handleUpload}
+        disabled={disabled}
+        tooltip={disabled ? disableTooltip : ''}
       >
       >
         {label}
         {label}
       </CustomButton>
       </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
 1, 183, [13848...], [318998...]\n
 909,3898,[3898...], [84981...]\n
 909,3898,[3898...], [84981...]\n
 ...`;
 ...`;
+
+export const INSERT_MAX_SIZE = 150;

+ 2 - 0
client/src/consts/Milvus.tsx

@@ -192,3 +192,5 @@ export const DEFAULT_SEARCH_PARAM_VALUE_MAP: {
   // range: [10, 300]
   // range: [10, 300]
   search_length: 10,
   search_length: 10,
 };
 };
+
+export const DEFAULT_NLIST_VALUE = 1024;

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

@@ -9,6 +9,7 @@ export class FieldHttp extends BaseModel implements FieldData {
   is_primary_key!: true;
   is_primary_key!: true;
   name!: string;
   name!: string;
   description!: string;
   description!: string;
+  autoID!: boolean;
 
 
   constructor(props: {}) {
   constructor(props: {}) {
     super(props);
     super(props);
@@ -34,6 +35,10 @@ export class FieldHttp extends BaseModel implements FieldData {
     return this.is_primary_key;
     return this.is_primary_key;
   }
   }
 
 
+  get _isAutoId() {
+    return this.autoID;
+  }
+
   get _fieldName() {
   get _fieldName() {
     return this.name;
     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.`,
     `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.`,
     `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?',
   isContainFieldNames: 'First row contains field names?',
+
+  uploadFileDisableTooltip: 'Please select collection before uploading',
   uploadFieldNamesLenWarning:
   uploadFieldNamesLenWarning:
     'Uploaded data column count is not equal to schema count',
     '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)',
   previewTipData: 'Data Preview(Top 4 rows shown)',
   previewTipAction: '*Change header cell selector value to edit field name',
   previewTipAction: '*Change header cell selector value to edit field name',
   requiredFieldName: 'Field Name*',
   requiredFieldName: 'Field Name*',

+ 1 - 0
client/src/i18n/cn/search.ts

@@ -4,6 +4,7 @@ const searchTrans = {
   thirdTip: '3. Set search parameters',
   thirdTip: '3. Set search parameters',
   vectorPlaceholder: 'Please input your vector value here, e.g. [1, 2, 3, 4]',
   vectorPlaceholder: 'Please input your vector value here, e.g. [1, 2, 3, 4]',
   collection: 'Choose Collection',
   collection: 'Choose Collection',
+  noCollection: 'No collection',
   field: 'Choose Field',
   field: 'Choose Field',
   startTip: 'Start your vector search',
   startTip: 'Start your vector search',
   empty: 'No result',
   empty: 'No result',

+ 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.`,
     `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.`,
     `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?',
   isContainFieldNames: 'First row contains field names?',
+
+  uploadFileDisableTooltip: 'Please select collection before uploading',
   uploadFieldNamesLenWarning:
   uploadFieldNamesLenWarning:
     'Uploaded data column count is not equal to schema count',
     '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)',
   previewTipData: 'Data Preview(Top 4 rows shown)',
   previewTipAction: '*Change header cell selector value to edit field name',
   previewTipAction: '*Change header cell selector value to edit field name',
   requiredFieldName: 'Field Name*',
   requiredFieldName: 'Field Name*',

+ 1 - 0
client/src/i18n/en/search.ts

@@ -4,6 +4,7 @@ const searchTrans = {
   thirdTip: '3. Set search parameters',
   thirdTip: '3. Set search parameters',
   vectorPlaceholder: 'Please input your vector value here, e.g. [1, 2, 3, 4]',
   vectorPlaceholder: 'Please input your vector value here, e.g. [1, 2, 3, 4]',
   collection: 'Choose Collection',
   collection: 'Choose Collection',
+  noCollection: 'No collection',
   field: 'Choose Field',
   field: 'Choose Field',
   startTip: 'Start your vector search',
   startTip: 'Start your vector search',
   empty: 'No result',
   empty: 'No result',

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

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

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

@@ -6,6 +6,7 @@ import { ITextfieldConfig } from '../../components/customInput/Types';
 import CustomSelector from '../../components/customSelector/CustomSelector';
 import CustomSelector from '../../components/customSelector/CustomSelector';
 import { Option } from '../../components/customSelector/Types';
 import { Option } from '../../components/customSelector/Types';
 import {
 import {
+  DEFAULT_NLIST_VALUE,
   DEFAULT_SEARCH_PARAM_VALUE_MAP,
   DEFAULT_SEARCH_PARAM_VALUE_MAP,
   INDEX_CONFIG,
   INDEX_CONFIG,
   METRIC_OPTIONS_MAP,
   METRIC_OPTIONS_MAP,
@@ -138,7 +139,8 @@ const SearchParams: FC<SearchParamsProps> = ({
   const getSearchInputConfig = useCallback(
   const getSearchInputConfig = useCallback(
     (paramKey: searchKeywordsType): ITextfieldConfig => {
     (paramKey: searchKeywordsType): ITextfieldConfig => {
       const nlist = Number(
       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 || DEFAULT_NLIST_VALUE
       );
       );
 
 
       const configParamMap: {
       const configParamMap: {
@@ -239,7 +241,8 @@ const SearchParams: FC<SearchParamsProps> = ({
           // not selectable now, so not set onChange event
           // not selectable now, so not set onChange event
         }}
         }}
         // not selectable now
         // not selectable now
-        readOnly={true}
+        // readOnly can't avoid all events, so we use disabled instead
+        disabled={true}
       />
       />
 
 
       {/* dynamic params, now every type only has one param except metric type */}
       {/* dynamic params, now every type only has one param except metric type */}

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

@@ -10,7 +10,7 @@ export const getVectorSearchStyles = makeStyles((theme: Theme) => ({
       flexDirection: 'column',
       flexDirection: 'column',
       flexBasis: '33%',
       flexBasis: '33%',
 
 
-      padding: theme.spacing(2, 3, 3),
+      padding: theme.spacing(2, 3, 4),
       backgroundColor: '#fff',
       backgroundColor: '#fff',
       borderRadius: theme.spacing(0.5),
       borderRadius: theme.spacing(0.5),
       boxShadow: '3px 3px 10px rgba(0, 0, 0, 0.05)',
       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),
       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': {
     '& .text': {
       color: theme.palette.milvusGrey.dark,
       color: theme.palette.milvusGrey.dark,
       fontWeight: 500,
       fontWeight: 500,

+ 7 - 3
client/src/pages/seach/VectorSearch.tsx

@@ -295,7 +295,10 @@ const VectorSearch = () => {
             options={collectionOptions}
             options={collectionOptions}
             wrapperClass={classes.selector}
             wrapperClass={classes.selector}
             variant="filled"
             variant="filled"
-            label={searchTrans('collection')}
+            label={searchTrans(
+              collectionOptions.length === 0 ? 'noCollection' : 'collection'
+            )}
+            disabled={collectionOptions.length === 0}
             value={selectedCollection}
             value={selectedCollection}
             onChange={(e: { target: { value: unknown } }) => {
             onChange={(e: { target: { value: unknown } }) => {
               const collection = e.target.value;
               const collection = e.target.value;
@@ -306,7 +309,8 @@ const VectorSearch = () => {
           />
           />
           <CustomSelector
           <CustomSelector
             options={fieldOptions}
             options={fieldOptions}
-            readOnly={selectedCollection === ''}
+            // readOnly can't avoid all events, so we use disabled instead
+            disabled={selectedCollection === ''}
             wrapperClass={classes.selector}
             wrapperClass={classes.selector}
             variant="filled"
             variant="filled"
             label={searchTrans('field')}
             label={searchTrans('field')}
@@ -318,7 +322,7 @@ const VectorSearch = () => {
           />
           />
         </fieldset>
         </fieldset>
         {/* search params selectors */}
         {/* search params selectors */}
-        <fieldset className="field">
+        <fieldset className="field field-params">
           <Typography className="text">{searchTrans('thirdTip')}</Typography>
           <Typography className="text">{searchTrans('thirdTip')}</Typography>
           <SearchParams
           <SearchParams
             wrapperClass={classes.paramsWrapper}
             wrapperClass={classes.paramsWrapper}