Browse Source

Support boolean (#129)

* fix creation dialog

* adjust size

* fix tooltip

* support boolean in preview and query

* fix style

* fix typo
ryjiang 2 years ago
parent
commit
60db41d6a5

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

@@ -54,6 +54,7 @@ const DialogTemplate: FC<DialogContainerProps> = ({
   // needed for code mode
   showCode = false,
   codeBlocksData = [],
+  dialogClass = '',
 }) => {
   const { t } = useTranslation('btn');
   const cancel = cancelLabel || t('cancel');
@@ -77,7 +78,10 @@ const DialogTemplate: FC<DialogContainerProps> = ({
 
   return (
     <section className={classes.wrapper}>
-      <div ref={dialogRef} className={`${classes.dialog} ${classes.block}`}>
+      <div
+        ref={dialogRef}
+        className={`${classes.dialog} ${classes.block} ${dialogClass}`}
+      >
         <CustomDialogTitle onClose={handleClose} showCloseIcon={showCloseIcon}>
           {title}
         </CustomDialogTitle>

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

@@ -36,4 +36,6 @@ export type DialogContainerProps = {
   // code mode requirement
   showCode?: boolean;
   codeBlocksData?: CodeViewData[];
+  children: ReactElement;
+  dialogClass?: string;
 };

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

@@ -1,4 +1,5 @@
 import { ReactElement } from 'react';
+import { InputLabelProps } from '@material-ui/core';
 import { IValidationItem } from '../../hooks/Form';
 import { IExtraParam, ValidType } from '../../utils/Validation';
 
@@ -78,6 +79,7 @@ export interface ITextfieldConfig {
   type?: string;
   onBlur?: (event: any) => void;
   onChange?: (event: any) => void;
+  InputLabelProps?: Partial<InputLabelProps>;
 }
 
 export interface IAdornmentConfig {

+ 2 - 1
client/src/components/customSelector/CustomSelector.tsx

@@ -16,12 +16,13 @@ const CustomSelector: FC<CustomSelectorType> = props => {
     variant,
     wrapperClass = '',
     labelClass = '',
+    size = 'medium',
     ...others
   } = props;
   const id = generateId('selector');
 
   return (
-    <FormControl variant={variant} className={wrapperClass}>
+    <FormControl variant={variant} className={wrapperClass} size={size}>
       {label && (
         <InputLabel classes={{ root: labelClass }} htmlFor={id}>
           {label}

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

@@ -20,6 +20,8 @@ export type CustomSelectorType = SelectProps & {
   variant?: 'filled' | 'outlined' | 'standard';
   labelClass?: string;
   wrapperClass?: string;
+  hiddenLabel?: boolean;
+  size?: 'small' | 'medium' | undefined;
 };
 
 export interface ICustomGroupSelect {

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

@@ -2,6 +2,7 @@ import React from 'react';
 import { IconsType } from './Types';
 import SearchIcon from '@material-ui/icons/Search';
 import AddIcon from '@material-ui/icons/Add';
+import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutlineOutlined';
 import DeleteIcon from '@material-ui/icons/Delete';
 import FileCopyIcon from '@material-ui/icons/FileCopy';
 import Visibility from '@material-ui/icons/Visibility';
@@ -44,6 +45,7 @@ import { ReactComponent as SystemIcon } from '../../assets/icons/system.svg';
 const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   search: (props = {}) => <SearchIcon {...props} />,
   add: (props = {}) => <AddIcon {...props} />,
+  addOutline:  (props = {}) => <AddCircleOutlineIcon {...props} />,
   delete: (props = {}) => <DeleteIcon {...props} />,
   list: (props = {}) => <ReorderIcon {...props} />,
   copy: (props = {}) => <FileCopyIcon {...props} />,

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

@@ -1,6 +1,7 @@
 export type IconsType =
   | 'search'
   | 'add'
+  | 'addOutline'
   | 'delete'
   | 'list'
   | 'copy'

+ 1 - 1
client/src/i18n/cn/collection.ts

@@ -3,7 +3,7 @@ const collectionTrans = {
   noData: 'No Collection',
 
   rowCount: 'Entity Count',
-  tooltip: 'data in one entity',
+  tooltip: 'Approximately entity count.',
 
   create: 'Create Collection',
   delete: 'delete',

+ 1 - 1
client/src/i18n/cn/partition.ts

@@ -9,7 +9,7 @@ const partitionTrans = {
   createdTime: 'Created Time',
   status: 'Status',
   rowCount: 'Entity Count',
-  tooltip: 'data in one entity',
+  tooltip: 'Approximately entity count.',
 
   createTitle: 'Create Partition',
   nameWarning: '_default is reserved, cannot be used as name',

+ 11 - 6
client/src/i18n/en/collection.ts

@@ -3,7 +3,7 @@ const collectionTrans = {
   noData: 'No Collection',
 
   rowCount: 'Entity Count',
-  tooltip: 'data in one entity',
+  tooltip: 'Approximately entity count.',
 
   create: 'Create Collection',
   delete: 'delete',
@@ -19,6 +19,7 @@ const collectionTrans = {
   // table
   id: 'ID',
   name: 'Name',
+  nameTip: 'Collection Name',
   status: 'Status',
   desc: 'Description',
   createdTime: 'Created Time',
@@ -26,19 +27,23 @@ const collectionTrans = {
 
   // create dialog
   createTitle: 'Create Collection',
-  general: '1. General Info',
-  schema: '2. Define Schema',
-  consistency: '3. Consistency Level',
+  general: 'General information',
+  schema: 'Schema',
+  consistency: 'Consistency Level',
   consistencyLevel: 'Consistency Level',
-  description: 'Description (Optional)',
+  description: 'Description',
   fieldType: 'Field Type',
   vectorFieldType: 'Vector Field Type',
   fieldName: 'Field Name',
+  idFieldName: 'ID Name',
+  vectorFieldName: 'Vector Name',
   autoId: 'Auto ID',
+  vectorType: 'Vector Type',
+  idType: 'ID Type',
   dimension: 'Dimension',
   dimensionTooltip: 'Only vector type has dimension',
   dimensionMutipleWarning: 'Dimension should be 8 multiple',
-  dimensionPositiveWarning: 'Dimension should be positive number',
+  dimensionPositiveWarning: 'Positive number only',
   newBtn: 'add new field',
   nameLengthWarning: 'Name length should be less than 256',
   nameContentWarning: 'Name can only contain numbers, letters, and underscores',

+ 1 - 1
client/src/i18n/en/partition.ts

@@ -9,7 +9,7 @@ const partitionTrans = {
   createdTime: 'Created Time',
   status: 'Status',
   rowCount: 'Entity Count',
-  tooltip: 'data in one entity',
+  tooltip: 'Approximately entity count.',
 
   createTitle: 'Create Partition',
   nameWarning: '_default is reserved, cannot be used as name',

+ 2 - 1
client/src/i18n/en/warning.ts

@@ -1,8 +1,9 @@
 const warningTrans = {
   required: '{{name}} is required',
+  requiredOnly: 'Required',
   positive: '{{name}} should be positive',
   integer: '{{name}} should be integers',
-  range: 'range is {{min}} ~ {{max}}',
+  range: 'Range is {{min}} ~ {{max}}',
   specValueOrRange:
     '{{name}} should be {{specValue}}, or in range {{min}} ~ {{max}}',
   noSupportIndexType:

+ 22 - 10
client/src/pages/collections/Create.tsx

@@ -23,32 +23,34 @@ const useStyles = makeStyles((theme: Theme) => ({
   fieldset: {
     width: '100%',
     display: 'flex',
-    justifyContent: 'space-between',
     alignItems: 'center',
-
+    marginBottom: '16px',
+    gap: '8px',
     '&:nth-last-child(2)': {
       flexDirection: 'column',
       alignItems: 'flex-start',
     },
 
     '& legend': {
-      marginBottom: theme.spacing(2),
+      marginBottom: theme.spacing(1),
       color: `#82838e`,
       lineHeight: '20px',
       fontSize: '14px',
     },
   },
   input: {
-    width: '48%',
+    width: '100%',
   },
   select: {
     width: '160px',
-    marginBottom: '22px',
 
     '&:first-child': {
       marginLeft: 0,
     },
   },
+  dialog: {
+    minWidth: '100%',
+  },
 }));
 
 const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
@@ -130,9 +132,7 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
         // cannot be empty
         {
           rule: 'require',
-          errorText: warningTrans('required', {
-            name: collectionTrans('name'),
-          }),
+          errorText: warningTrans('requiredOnly'),
         },
         // length <= 255
         {
@@ -157,6 +157,10 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
           errorText: collectionTrans('nameFirstLetterWarning'),
         },
       ],
+      InputLabelProps: {
+        shrink: true,
+      },
+      size: 'small',
       className: classes.input,
     },
     {
@@ -166,7 +170,11 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
       onChange: (value: string) => handleInputChange('description', value),
       variant: 'filled',
       validations: [],
+      size: 'small',
       className: classes.input,
+      InputLabelProps: {
+        shrink: true,
+      },
     },
   ];
 
@@ -196,10 +204,13 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
   return (
     <DialogTemplate
       title={collectionTrans('createTitle')}
-      handleClose={handleCloseDialog}
+      handleClose={() => {
+        handleCloseDialog();
+      }}
       confirmLabel={btnTrans('create')}
       handleConfirm={handleCreateCollection}
       confirmDisabled={disabled || !allFieldsValid}
+      dialogClass={classes.dialog}
     >
       <form>
         <fieldset className={classes.fieldset}>
@@ -230,13 +241,14 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
           <legend>{collectionTrans('consistency')}</legend>
           <CustomSelector
             wrapperClass={classes.select}
+            size="small"
             options={CONSISTENCY_LEVEL_OPTIONS}
             onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
               setConsistencyLevel(e.target.value as ConsistencyLevelEnum);
             }}
+            hiddenLabel={true}
             value={consistencyLevel}
             variant="filled"
-            label={'Consistency'}
           />
         </fieldset>
       </form>

+ 103 - 77
client/src/pages/collections/CreateFields.tsx

@@ -1,7 +1,6 @@
 import { makeStyles, Theme, TextField, IconButton } from '@material-ui/core';
 import { FC, Fragment, ReactElement, useMemo } from 'react';
 import { useTranslation } from 'react-i18next';
-import CustomButton from '../../components/customButton/CustomButton';
 import CustomSelector from '../../components/customSelector/CustomSelector';
 import icons from '../../components/icons/Icons';
 import { generateId } from '../../utils/Common';
@@ -35,57 +34,50 @@ const useStyles = makeStyles((theme: Theme) => ({
     display: 'flex',
     flexWrap: 'nowrap',
     alignItems: 'center',
-    // only Safari 14.1+ support flexbox gap
-    // gap: '10px',
-    width: '100%',
-
-    '& > *': {
-      marginLeft: '10px',
-    },
-
-    '& .dimension': {
-      maxWidth: '160px',
-    },
+    gap: '8px',
+    flex: '1 0 auto',
   },
   input: {
     fontSize: '14px',
   },
-  primaryInput: {
-    maxWidth: '160px',
-
-    '&:first-child': {
-      marginLeft: 0,
-    },
+  fieldInput: {
+    width: '160px',
   },
   select: {
-    width: '160px',
-    marginBottom: '22px',
+    width: '140px',
+    marginTop: '-20px',
 
     '&:first-child': {
       marginLeft: 0,
     },
   },
+  autoIdSelect: {
+    width: '120px',
+    marginTop: '-20px',
+  },
+  numberBox: {
+    width: '97px',
+  },
+  maxLength: {
+    maxWidth: '80px',
+  },
   descInput: {
-    minWidth: '100px',
-    flexGrow: 1,
+    width: '120px',
   },
   btnTxt: {
     textTransform: 'uppercase',
   },
   iconBtn: {
     marginLeft: 0,
-
     padding: 0,
-    width: '20px',
-    height: '20px',
-  },
-  mb2: {
-    marginBottom: theme.spacing(2),
+    width: '16px',
+    height: '16px',
   },
   helperText: {
     lineHeight: '20px',
-    margin: theme.spacing(0.25, 0),
-    marginLeft: '12px',
+    fontSize: '10px',
+    margin: theme.spacing(0),
+    marginLeft: '11px',
   },
 }));
 
@@ -112,7 +104,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
 
   const classes = useStyles();
 
-  const AddIcon = icons.add;
+  const AddIcon = icons.addOutline;
   const RemoveIcon = icons.remove;
 
   const { requiredFields, optionalFields } = useMemo(
@@ -161,6 +153,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
             ? ALL_OPTIONS
             : VECTOR_FIELDS_OPTIONS
         }
+        size="small"
         onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
           onChange(e.target.value as DataTypeEnum);
         }}
@@ -196,6 +189,10 @@ const CreateFields: FC<CreateFieldsProps> = ({
             input: inputClassName,
           },
         }}
+        InputLabelProps={{
+          shrink: true,
+        }}
+        size="small"
         disabled={isReadOnly}
         error={validate(value) !== ' '}
         helperText={validate(value)}
@@ -208,10 +205,15 @@ const CreateFields: FC<CreateFieldsProps> = ({
     );
   };
 
-  const generateFieldName = (field: Field) => {
+  const generateFieldName = (
+    field: Field,
+    label?: string,
+    className?: string
+  ) => {
     return getInput({
-      label: collectionTrans('fieldName'),
+      label: label || collectionTrans('fieldName'),
       value: field.name,
+      className: className || classes.fieldInput,
       handleChange: (value: string) => {
         const isValid = checkEmptyValid(value);
         setFieldsValidation(v =>
@@ -226,9 +228,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
         if (value === null) return ' ';
         const isValid = checkEmptyValid(value);
 
-        return isValid
-          ? ' '
-          : warningTrans('required', { name: collectionTrans('fieldName') });
+        return isValid ? ' ' : warningTrans('requiredOnly');
       },
     });
   };
@@ -239,7 +239,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
       value: field.description,
       handleChange: (value: string) =>
         changeFields(field.id!, 'description', value),
-      className: classes.descInput,
+      inputClassName: classes.descInput,
     });
   };
 
@@ -269,6 +269,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
     return getInput({
       label: collectionTrans('dimension'),
       value: field.dimension as number,
+      inputClassName: classes.numberBox,
       handleChange: (value: string) => {
         const { isPositive, isMutiple } = validateDimension(value);
         const isValid =
@@ -301,6 +302,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
       label: 'Max Length',
       value: field.max_length!,
       type: 'number',
+      inputClassName: classes.maxLength,
       handleChange: (value: string) =>
         changeFields(field.id!, 'max_length', value),
       validate: (value: any) => {
@@ -313,9 +315,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
           type: 'number',
         });
         return !isEmptyValid
-          ? warningTrans('required', {
-              name: collectionTrans('fieldName'),
-            })
+          ? warningTrans('requiredOnly')
           : !isRangeValid
           ? warningTrans('range', {
               min: 1,
@@ -339,7 +339,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
     setFields(newFields);
   };
 
-  const handleAddNewField = () => {
+  const handleAddNewField = (index: number) => {
     const id = generateId();
     const newDefaultItem: Field = {
       name: null,
@@ -356,14 +356,16 @@ const CreateFields: FC<CreateFieldsProps> = ({
       name: false,
       dimension: true,
     };
-    setFields([...fields, newDefaultItem]);
+
+    fields.splice(index + 1, 0, newDefaultItem);
+    setFields([...fields]);
     setFieldsValidation(v => [...v, newValidation]);
   };
 
-  const handleRemoveField = (field: Field) => {
-    const newFields = fields.filter(f => f.id !== field.id);
+  const handleRemoveField = (id: string) => {
+    const newFields = fields.filter(f => f.id !== id);
     setFields(newFields);
-    setFieldsValidation(v => v.filter(item => item.id !== field.id));
+    setFieldsValidation(v => v.filter(item => item.id !== id));
   };
 
   const generatePrimaryKeyRow = (
@@ -374,9 +376,10 @@ const CreateFields: FC<CreateFieldsProps> = ({
     const autoIdOff = isVarChar ? 'false' : autoID ? 'true' : 'false';
     return (
       <div className={`${classes.rowWrapper}`}>
+        {generateFieldName(field, collectionTrans('idFieldName'))}
         {getSelector(
           'vector',
-          `Primary ${collectionTrans('fieldType')} `,
+          `${collectionTrans('idType')} `,
           field.data_type,
           (value: DataTypeEnum) => {
             changeFields(field.id!, 'data_type', value);
@@ -386,8 +389,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
           },
           PRIMARY_FIELDS_OPTIONS
         )}
-
-        {generateFieldName(field)}
+        {generateDesc(field)}
 
         <CustomSelector
           label={collectionTrans('autoId')}
@@ -398,53 +400,51 @@ const CreateFields: FC<CreateFieldsProps> = ({
             setAutoID(autoId);
           }}
           variant="filled"
-          wrapperClass={classes.select}
+          wrapperClass={classes.autoIdSelect}
           disabled={isVarChar}
+          size="small"
         />
-        {isVarChar && generateMaxLength(field)}
 
-        {generateDesc(field)}
+        {isVarChar && generateMaxLength(field)}
       </div>
     );
   };
 
-  const generateDefaultVectorRow = (field: Field): ReactElement => {
+  const generateDefaultVectorRow = (
+    field: Field,
+    index: number
+  ): ReactElement => {
     return (
       <>
         <div className={`${classes.rowWrapper}`}>
+          {generateFieldName(field, collectionTrans('vectorFieldName'))}
+
           {getSelector(
             'vector',
-            collectionTrans('fieldType'),
+            `${collectionTrans('vectorType')} `,
             field.data_type,
             (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value)
           )}
-
-          {generateFieldName(field)}
+          {generateDesc(field)}
 
           {generateDimension(field)}
 
-          {generateDesc(field)}
+          <IconButton
+            onClick={() => handleAddNewField(index)}
+            classes={{ root: classes.iconBtn }}
+            aria-label="add"
+          >
+            <AddIcon />
+          </IconButton>
         </div>
-
-        <CustomButton onClick={handleAddNewField} className={classes.mb2}>
-          <AddIcon />
-          <span className={classes.btnTxt}>{collectionTrans('newBtn')}</span>
-        </CustomButton>
       </>
     );
   };
 
-  const generateNumberRow = (field: Field): ReactElement => {
+  const generateNumberRow = (field: Field, index: number): ReactElement => {
     const isVarChar = field.data_type === DataTypeEnum.VarChar;
     return (
       <div className={`${classes.rowWrapper}`}>
-        <IconButton
-          onClick={() => handleRemoveField(field)}
-          classes={{ root: classes.iconBtn }}
-          aria-label="delete"
-        >
-          <RemoveIcon />
-        </IconButton>
         {generateFieldName(field)}
         {getSelector(
           'all',
@@ -452,8 +452,28 @@ const CreateFields: FC<CreateFieldsProps> = ({
           field.data_type,
           (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value)
         )}
-        {isVarChar && generateMaxLength(field)}
         {generateDesc(field)}
+
+        {isVarChar && generateMaxLength(field)}
+        <IconButton
+          onClick={() => {
+            handleAddNewField(index);
+          }}
+          classes={{ root: classes.iconBtn }}
+          aria-label="add"
+        >
+          <AddIcon />
+        </IconButton>
+        <IconButton
+          onClick={() => {
+            const id = field.id || '';
+            handleRemoveField(id);
+          }}
+          classes={{ root: classes.iconBtn }}
+          aria-label="delete"
+        >
+          <RemoveIcon />
+        </IconButton>
       </div>
     );
   };
@@ -478,35 +498,41 @@ const CreateFields: FC<CreateFieldsProps> = ({
     );
   };
 
-  const generateRequiredFieldRow = (field: Field, autoID: boolean) => {
+  const generateRequiredFieldRow = (
+    field: Field,
+    autoID: boolean,
+    index: number
+  ) => {
     // required type is primaryKey or defaultVector
     if (field.createType === 'primaryKey') {
       return generatePrimaryKeyRow(field, autoID);
     }
     // use defaultVector as default return type
-    return generateDefaultVectorRow(field);
+    return generateDefaultVectorRow(field, index);
   };
 
-  const generateOptionalFieldRow = (field: Field) => {
+  const generateOptionalFieldRow = (field: Field, index: number) => {
     // optional type is vector or number
     if (field.createType === 'vector') {
       return generateVectorRow(field);
     }
 
     // use number as default createType
-    return generateNumberRow(field);
+    return generateNumberRow(field, index);
   };
 
   return (
     <>
       {requiredFields.map((field, index) => (
-        <Fragment key={index}>
-          {generateRequiredFieldRow(field, autoID)}
+        <Fragment key={field.id}>
+          {generateRequiredFieldRow(field, autoID, index)}
         </Fragment>
       ))}
       <div className={classes.optionalWrapper}>
         {optionalFields.map((field, index) => (
-          <Fragment key={index}>{generateOptionalFieldRow(field)}</Fragment>
+          <Fragment key={field.id}>
+            {generateOptionalFieldRow(field, index + requiredFields.length)}
+          </Fragment>
         ))}
       </div>
     </>

+ 2 - 6
client/src/pages/preview/Preview.tsx

@@ -106,13 +106,9 @@ const Preview: FC<{
     const searchParam = { [searchParamKey]: searchParamValue };
     const params = `${JSON.stringify(searchParam)}`;
     setPrimaryKey(primaryKey);
-    // Temporarily hide bool field due to incorrect return from SDK.
-    const fieldWithoutBool = nameList.filter(
-      i => i.type !== DataTypeStringEnum.Bool
-    );
 
     // set fields
-    setFields(fieldWithoutBool);
+    setFields(nameList);
 
     // set loading
     setTableLoading(true);
@@ -140,7 +136,7 @@ const Preview: FC<{
       // query by random id
       const res = await CollectionHttp.queryData(collectionName, {
         expr: expr,
-        output_fields: fieldWithoutBool.map(i => i.name),
+        output_fields: nameList.map(i => i.name),
       });
 
       const result = res.data;

+ 1 - 5
client/src/pages/query/Query.tsx

@@ -115,11 +115,7 @@ const Query: FC<{
     const primaryKey =
       schemaList.find(v => v._isPrimaryKey === true)?._fieldName || '';
     setPrimaryKey(primaryKey);
-    // Temporarily hide bool field due to incorrect return from SDK.
-    const fieldWithoutBool = nameList.filter(
-      i => i.type !== DataTypeStringEnum.Bool
-    );
-    setFields(fieldWithoutBool);
+    setFields(nameList);
   };
 
   // Get fields at first or collection name changed.