浏览代码

support load after create (#730)

* load after creating UI

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* backup update

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update tooltip

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

---------

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 4 月之前
父节点
当前提交
1cfb9dd9a6

+ 20 - 7
client/src/components/customDialog/DialogTemplate.tsx

@@ -1,6 +1,11 @@
-import { FC, useRef } from 'react';
+import { FC, useRef, useState } from 'react';
 import { useTranslation } from 'react-i18next';
-import { DialogContent, DialogActions, Theme } from '@mui/material';
+import {
+  DialogContent,
+  DialogActions,
+  Theme,
+  CircularProgress,
+} from '@mui/material';
 import { DialogContainerProps } from './Types';
 import CustomDialogTitle from './CustomDialogTitle';
 import CustomButton from '../customButton/CustomButton';
@@ -39,7 +44,7 @@ const useStyles = makeStyles((theme: Theme) => ({
     paddingBottom: theme.spacing(2),
     justifyContent: 'space-between',
     '& .btn': {
-      margin: theme.spacing(.5),
+      margin: theme.spacing(0.5),
     },
   },
 }));
@@ -62,6 +67,7 @@ const DialogTemplate: FC<DialogContainerProps> = ({
   codeBlocksData = [],
   dialogClass = '',
 }) => {
+  const [confirming, setConfirming] = useState(false);
   const { t } = useTranslation('btn');
   const cancel = cancelLabel || t('cancel');
   const confirm = confirmLabel || t('confirm');
@@ -70,9 +76,15 @@ const DialogTemplate: FC<DialogContainerProps> = ({
 
   const dialogRef = useRef(null);
 
-  const _handleConfirm = (event: React.FormEvent<HTMLFormElement>) => {
-    handleConfirm();
+  const _handleConfirm = async (event: React.FormEvent<HTMLFormElement>) => {
+    setConfirming(true);
     event.preventDefault();
+    try {
+      await handleConfirm();
+    } catch (error) {
+    } finally {
+      setConfirming(false);
+    }
   };
 
   return (
@@ -98,6 +110,7 @@ const DialogTemplate: FC<DialogContainerProps> = ({
                     onClick={onCancel}
                     name="cancel"
                     className="btn"
+                    disabled={confirming}
                   >
                     {cancel}
                   </CustomButton>
@@ -106,11 +119,11 @@ const DialogTemplate: FC<DialogContainerProps> = ({
                   type="submit"
                   variant="contained"
                   color="primary"
-                  disabled={confirmDisabled}
+                  disabled={confirming || confirmDisabled}
                   name="confirm"
                   className="btn"
                 >
-                  {confirm}
+                  {confirming ? <CircularProgress size={16} /> : confirm}
                 </CustomButton>
               </div>
             </DialogActions>

+ 4 - 0
client/src/i18n/cn/collection.ts

@@ -64,6 +64,7 @@ const collectionTrans = {
   partitionKey: '分区键',
   partitionKeyTooltip:
     'Milvus将根据分区键字段中的值在分区中存储entities。只支持一个Int64或VarChar字段。',
+  paritionKeyDisabledTooltip: '只允许一个分区字段,同时分区键字段不能用作主键字段。',
   enableDynamicSchema: '启用动态Schema',
   analyzer: '分词器',
   enableMatch: '启用匹配',
@@ -72,6 +73,9 @@ const collectionTrans = {
   nullableTooltip: '字段是否可以为 null,nullable 字段不能用作分区键。',
   defaultValue: '默认值',
   defaultValueTooltip: '字段的默认值, 不支持JSON 和 Array。',
+  loadCollectionAfterCreate: '创建后加载collection',
+  loadCollectionAfterCreateTooltip:
+    'Attu 将使用 AUTOINDEX 为所有字段创建索引,然后加载Collection。',
 
   // load dialog
   loadTitle: '加载Collection',

+ 5 - 0
client/src/i18n/en/collection.ts

@@ -67,6 +67,8 @@ const collectionTrans = {
   partitionKey: 'Partition Key',
   partitionKeyTooltip:
     ' Milvus will store entities in a partition according to the values in the partition key field. Only one Int64 or VarChar field is supported.',
+  paritionKeyDisabledTooltip:
+    'Only one field can be marked as paritition key and field marked as nullable cannot be used as partition key.',
   enableDynamicSchema: 'Dynamic Schema',
   analyzer: 'Analyzer',
   enableMatch: 'Enable Match',
@@ -78,6 +80,9 @@ const collectionTrans = {
   defaultValue: 'Default',
   defaultValueTooltip:
     'Default value of the field, JSON and Array types are not supported.',
+  loadCollectionAfterCreate: 'Load collection immediately after creation',
+  loadCollectionAfterCreateTip:
+    'Attu will create indexes for fields using AUTOINDEX and then load the collection.',
 
   // load dialog
   loadTitle: 'Load Collection',

+ 54 - 15
client/src/pages/dialogs/CreateCollectionDialog.tsx

@@ -26,7 +26,7 @@ import { makeStyles } from '@mui/styles';
 
 const useStyles = makeStyles((theme: Theme) => ({
   dialog: {
-    minWidth: 880,
+    minWidth: 720,
   },
   container: {
     display: 'flex',
@@ -62,12 +62,18 @@ const useStyles = makeStyles((theme: Theme) => ({
   input: {
     width: '100%',
   },
-  dynamicField: {
+  chexBoxArea: {
+    paddingTop: 8,
     fontSize: 14,
     marginLeft: -8,
+    '& label': {
+      display: 'inline-block',
+    },
+    borderTop: `1px solid ${theme.palette.divider}`,
   },
   consistencySelect: {
     width: '50%',
+    marginBottom: 16,
   },
 }));
 
@@ -85,6 +91,7 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
     description: '',
     autoID: true,
     enableDynamicField: false,
+    loadAfterCreate: true,
   });
 
   const [consistencyLevel, setConsistencyLevel] =
@@ -137,13 +144,14 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
     });
   };
 
-  const changeEnableDynamicField = (
+  const updateCheckBox = (
     event: ChangeEvent<any>,
+    key: string,
     value: boolean
   ) => {
     setForm({
       ...form,
-      enableDynamicField: value,
+      [key]: value,
     });
   };
 
@@ -372,17 +380,48 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
               variant="filled"
             />
           </fieldset>
-          <fieldset className={classes.dynamicField}>
-            <CustomToolTip title={collectionTrans('partitionKeyTooltip')}>
-              <>
-                <Checkbox
-                  checked={!!form.enableDynamicField}
-                  size="small"
-                  onChange={changeEnableDynamicField}
-                />
-                {collectionTrans('enableDynamicSchema')}
-              </>
-            </CustomToolTip>
+          <fieldset className={classes.chexBoxArea}>
+            <div>
+              <CustomToolTip title={collectionTrans('partitionKeyTooltip')}>
+                <label htmlFor="enableDynamicField">
+                  <Checkbox
+                    id="enableDynamicField"
+                    checked={!!form.enableDynamicField}
+                    size="small"
+                    onChange={event => {
+                      updateCheckBox(
+                        event,
+                        'enableDynamicField',
+                        !form.enableDynamicField
+                      );
+                    }}
+                  />
+                  {collectionTrans('enableDynamicSchema')}
+                </label>
+              </CustomToolTip>
+            </div>
+
+            <div>
+              <CustomToolTip
+                title={collectionTrans('loadCollectionAfterCreateTip')}
+              >
+                <label htmlFor="loadAfterCreate">
+                  <Checkbox
+                    id="loadAfterCreate"
+                    checked={!!form.loadAfterCreate}
+                    size="small"
+                    onChange={event => {
+                      updateCheckBox(
+                        event,
+                        'loadAfterCreate',
+                        !form.loadAfterCreate
+                      );
+                    }}
+                  />
+                  {collectionTrans('loadCollectionAfterCreate')}
+                </label>
+              </CustomToolTip>
+            </div>
           </fieldset>
         </section>
       </div>

+ 7 - 7
client/src/pages/dialogs/create/CreateFields.tsx

@@ -51,8 +51,8 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
   title: {
     fontSize: 14,
-    marginTop: theme.spacing(1),
-    marginBottom: theme.spacing(1),
+    marginTop: theme.spacing(0),
+    marginBottom: theme.spacing(1.5),
     '& button': {
       position: 'relative',
       top: '-1px',
@@ -492,15 +492,15 @@ const CreateFields: FC<CreateFieldsProps> = ({
     field: FieldType,
     fields: FieldType[]
   ) => {
+    const disabled =
+      (fields.some(f => f.is_partition_key) && !field.is_partition_key) ||
+      field.nullable;
     return (
       <div className={classes.setting}>
         <Checkbox
           checked={!!field.is_partition_key}
           size="small"
-          disabled={
-            (fields.some(f => f.is_partition_key) && !field.is_partition_key) ||
-            field.nullable
-          }
+          disabled={disabled}
           onChange={() => {
             changeFields(field.id!, {
               is_partition_key: !field.is_partition_key,
@@ -508,7 +508,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
           }}
         />
         <CustomToolTip
-          title={collectionTrans('partitionKeyTooltip')}
+          title={collectionTrans(disabled ? 'paritionKeyDisabledTooltip' :'partitionKeyTooltip')}
           placement="top"
         >
           <>{collectionTrans('partitionKey')}</>

+ 32 - 1
server/src/collections/collections.controller.ts

@@ -1,7 +1,11 @@
 import { NextFunction, Request, Response, Router } from 'express';
 import { dtoValidationMiddleware } from '../middleware/validation';
 import { CollectionsService } from './collections.service';
-import { LoadCollectionReq } from '@zilliz/milvus2-sdk-node';
+import {
+  LoadCollectionReq,
+  IndexType,
+  MetricType,
+} from '@zilliz/milvus2-sdk-node';
 import {
   CreateAliasDto,
   CreateCollectionDto,
@@ -168,11 +172,38 @@ export class CollectionController {
 
   async createCollection(req: Request, res: Response, next: NextFunction) {
     const createCollectionData = req.body;
+
+    if (createCollectionData.loadAfterCreate) {
+      // build index_params for all fields
+      const fields = createCollectionData.fields;
+      const index_params = fields.map((field: any) => {
+        const params: any = {
+          field_name: field.name,
+          index_type: IndexType.AUTOINDEX,
+        };
+
+        if (field.is_function_output) {
+          params.metric_type = MetricType.BM25;
+        }
+
+        return params;
+      });
+      createCollectionData.index_params = index_params;
+    }
+
     try {
       const result = await this.collectionsService.createCollection(
         req.clientId,
         createCollectionData
       );
+
+      // load collection after create
+      if (createCollectionData.loadAfterCreate) {
+        await this.collectionsService.loadCollectionAsync(req.clientId, {
+          collection_name: createCollectionData.collection_name,
+          db_name: req.db_name,
+        });
+      }
       res.send(result);
     } catch (error) {
       next(error);