Browse Source

refactor database & partition service & pages (#407)

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 1 year ago
parent
commit
45e42aec39

+ 12 - 7
client/src/context/Data.tsx

@@ -8,7 +8,7 @@ import {
 } from 'react';
 import { io, Socket } from 'socket.io-client';
 import { authContext } from '@/context';
-import { url, Collection, MilvusService, Database } from '@/http';
+import { url, Collection, MilvusService, DatabaseService } from '@/http';
 import { checkIndexBuilding, checkLoading } from '@/utils';
 import { DataContextType } from './Types';
 import { WS_EVENTS, WS_EVENTS_TYPE } from '@server/utils/Const';
@@ -22,6 +22,8 @@ export const dataContext = createContext<DataContextType>({
   setDatabase: () => {},
   databases: [],
   setDatabaseList: () => {},
+  fetchDatabases: async () => {},
+  fetchCollections: async () => {},
 });
 
 const { Provider } = dataContext;
@@ -63,7 +65,7 @@ export const DataProvider = (props: { children: React.ReactNode }) => {
   );
 
   // http fetch collection
-  const fetchCollection = async () => {
+  const fetchCollections = async () => {
     try {
       setLoading(true);
       const res = await Collection.getCollections();
@@ -76,15 +78,16 @@ export const DataProvider = (props: { children: React.ReactNode }) => {
     }
   };
 
-  const fetchDatabase = async () => {
-    const res = await Database.getDatabases();
+  const fetchDatabases = async () => {
+    const res = await DatabaseService.getDatabases();
+
     setDatabases(res.db_names);
   };
 
   useEffect(() => {
     if (isAuth) {
       // fetch db
-      fetchDatabase();
+      fetchDatabases();
       // connect to socket server
       socket.current = io(url as string);
       // register client
@@ -116,7 +119,7 @@ export const DataProvider = (props: { children: React.ReactNode }) => {
       // listen to collection event
       socket.current?.on(WS_EVENTS.COLLECTION, socketCallBack);
       // get data
-      fetchCollection();
+      fetchCollections();
     }
   }, [socketCallBack, connected]);
 
@@ -130,9 +133,11 @@ export const DataProvider = (props: { children: React.ReactNode }) => {
         databases,
         setDatabase,
         setDatabaseList: setDatabases,
+        fetchDatabases,
+        fetchCollections,
       }}
     >
       {props.children}
     </Provider>
   );
-};
+};

+ 3 - 1
client/src/context/Types.ts

@@ -96,4 +96,6 @@ export type DataContextType = {
   setDatabase: Dispatch<SetStateAction<string>>;
   databases: string[];
   setDatabaseList: Dispatch<SetStateAction<string[]>>;
-};
+  fetchDatabases: () => Promise<void>;
+  fetchCollections: () => Promise<void>;
+};

+ 3 - 3
client/src/http/BaseModel.ts

@@ -18,7 +18,7 @@ export default class BaseModel {
     return this;
   }
 
-  static async findAll(data: findParamsType) {
+  static async findAll<T>(data: findParamsType) {
     const { params = {}, path = '', method = 'get' } = data;
     const type = method === 'post' ? 'data' : 'params';
     const httpConfig = {
@@ -30,14 +30,14 @@ export default class BaseModel {
     const res = await http(httpConfig);
     let list = res.data.data || [];
     if (!Array.isArray(list)) {
-      return list;
+      return list as T;
     }
 
     return Object.assign(
       list.map(v => new this(v)),
       {
         _total: res.data.data.total_count || list.length,
-      }
+      } as T
     );
   }
 

+ 0 - 7
client/src/http/Collection.ts

@@ -1,6 +1,5 @@
 import dayjs from 'dayjs';
 import { LoadReplicaReq } from '@/pages/collections/Types';
-import { VectorSearchParam } from '@/types/SearchTypes';
 import { QueryParam } from '@/pages/query/Types';
 import { formatNumber } from '@/utils/Common';
 import BaseModel from './BaseModel';
@@ -109,12 +108,6 @@ export class Collection extends BaseModel implements CollectionData {
     });
   }
 
-  static vectorSearchData(collectionName: string, params: VectorSearchParam) {
-    return super.query({
-      path: `${this.COLLECTIONS_URL}/${collectionName}/search`,
-      data: params,
-    });
-  }
 
   static createAlias(collectionName: string, params: { alias: string }) {
     return super.create({

+ 15 - 11
client/src/http/Data.service.ts

@@ -1,41 +1,38 @@
 import { LoadSampleParam } from '@/pages/dialogs/Types';
 import { InsertDataParam, DeleteEntitiesReq } from '@/pages/collections/Types';
+import { VectorSearchParam } from '@/types/SearchTypes';
 import BaseModel from './BaseModel';
 
 export class DataService extends BaseModel {
-  static COLLECTIONS_URL = '/collections';
-  static FLUSH_URL = '/milvus/flush';
-
-  sampleFile!: string;
-
   constructor(props: DataService) {
     super(props);
     Object.assign(this, props);
   }
+
   static importSample(collectionName: string, param: LoadSampleParam) {
-    return super.create<DataService>({
-      path: `${this.COLLECTIONS_URL}/${collectionName}/importSample`,
+    return super.create<{ sampleFile: string }>({
+      path: `/collections/${collectionName}/importSample`,
       data: param,
     });
   }
 
   static insertData(collectionName: string, param: InsertDataParam) {
     return super.create({
-      path: `${this.COLLECTIONS_URL}/${collectionName}/insert`,
+      path: `/collections/${collectionName}/insert`,
       data: param,
     });
   }
 
   static deleteEntities(collectionName: string, param: DeleteEntitiesReq) {
     return super.update({
-      path: `${this.COLLECTIONS_URL}/${collectionName}/entities`,
+      path: `/collections/${collectionName}/entities`,
       data: param,
     });
   }
 
   static flush(collectionName: string) {
     return super.update({
-      path: this.FLUSH_URL,
+      path: `/milvus/flush`,
       data: {
         collection_names: [collectionName],
       },
@@ -44,7 +41,14 @@ export class DataService extends BaseModel {
 
   static emptyData(collectionName: string) {
     return super.update({
-      path: `${this.COLLECTIONS_URL}/${collectionName}/empty`,
+      path: `/collections/${collectionName}/empty`,
+    });
+  }
+
+  static vectorSearchData(collectionName: string, params: VectorSearchParam) {
+    return super.query({
+      path: `/collections/${collectionName}/search`,
+      data: params,
     });
   }
 }

+ 27 - 0
client/src/http/Database.service.ts

@@ -0,0 +1,27 @@
+import {
+  CreateDatabaseParams,
+  DropDatabaseParams,
+} from '../pages/database/Types';
+import BaseModel from './BaseModel';
+
+export class DatabaseService extends BaseModel {
+  constructor(props: DatabaseService) {
+    super(props);
+    Object.assign(this, props);
+  }
+
+  static getDatabases() {
+    return super.search<{ db_names: [] }>({
+      path: `/databases`,
+      params: {},
+    });
+  }
+
+  static createDatabase(data: CreateDatabaseParams) {
+    return super.create({ path: `/databases`, data });
+  }
+
+  static dropDatabase(data: DropDatabaseParams) {
+    return super.delete({ path: `/databases/${data.db_name}` });
+  }
+}

+ 0 - 28
client/src/http/Database.ts

@@ -1,28 +0,0 @@
-import {
-  CreateDatabaseParams,
-  DropDatabaseParams,
-} from '../pages/database/Types';
-import BaseModel from './BaseModel';
-
-export class Database extends BaseModel {
-  public db_names!: string[];
-
-  constructor(props: {}) {
-    super(props);
-    Object.assign(this, props);
-  }
-
-  static DATABASE_URL = `/databases`;
-
-  static getDatabases() {
-    return super.search({ path: this.DATABASE_URL, params: {} }) as Promise<Database>;
-  }
-
-  static createDatabase(data: CreateDatabaseParams) {
-    return super.create({ path: this.DATABASE_URL, data });
-  }
-
-  static dropDatabase(data: DropDatabaseParams) {
-    return super.delete({ path: `${this.DATABASE_URL}/${data.db_name}` });
-  }
-}

+ 1 - 3
client/src/http/Field.ts

@@ -1,8 +1,7 @@
-import BaseModel from './BaseModel';
 import { KeyValuePair } from '@server/types';
 import { DataTypeStringEnum } from '@/consts';
 
-export class FieldHttp extends BaseModel {
+export class FieldHttp {
   fieldID!: string;
   type_params!: { key: string; value: string }[];
   is_primary_key!: true;
@@ -19,7 +18,6 @@ export class FieldHttp extends BaseModel {
   indexParameterPairs: { key: string; value: string }[] = [];
 
   constructor(props: {}) {
-    super(props);
     Object.assign(this, props);
   }
 

+ 7 - 15
client/src/http/Milvus.service.ts

@@ -2,14 +2,6 @@ import { WS_EVENTS, WS_EVENTS_TYPE } from '@server/utils/Const';
 import BaseModel from './BaseModel';
 
 export class MilvusService extends BaseModel {
-  static CONNECT_URL = '/milvus/connect';
-  static DISCONNECT_URL = '/milvus/disconnect';
-  static CHECK_URL = '/milvus/check';
-  static METRICS_URL = '/milvus/metrics';
-  static VERSION_URL = '/milvus/version';
-  static USE_DB_URL = '/milvus/usedb';
-  static TIGGER_CRON_URL = '/crons';
-
   constructor(props: {}) {
     super(props);
     Object.assign(this, props);
@@ -21,7 +13,7 @@ export class MilvusService extends BaseModel {
     password?: string;
     database?: string;
   }) {
-    return super.create({ path: this.CONNECT_URL, data }) as Promise<{
+    return super.create({ path: '/milvus/connect', data }) as Promise<{
       address: string;
       database: string;
       clientId: string;
@@ -29,35 +21,35 @@ export class MilvusService extends BaseModel {
   }
 
   static closeConnection() {
-    return super.create({ path: this.DISCONNECT_URL });
+    return super.create({ path: '/milvus/disconnect' });
   }
 
   static getVersion() {
-    return super.search({ path: this.VERSION_URL, params: {} });
+    return super.search({ path: '/milvus/version', params: {} });
   }
 
   static check(address: string) {
     return super.search({
-      path: this.CHECK_URL,
+      path: '/milvus/check',
       params: { address },
     }) as Promise<{ connected: boolean }>;
   }
 
   static getMetrics() {
     return super.search({
-      path: this.METRICS_URL,
+      path: '/milvus/metrics',
       params: {},
     });
   }
 
   static triggerCron(data: { name: WS_EVENTS; type: WS_EVENTS_TYPE }) {
     return super.update({
-      path: this.TIGGER_CRON_URL,
+      path: '/crons',
       data,
     });
   }
 
   static useDatabase(data: { database: string }) {
-    return super.create({ path: this.USE_DB_URL, data });
+    return super.create({ path: '/milvus/usedb', data });
   }
 }

+ 55 - 0
client/src/http/Partition.service.ts

@@ -0,0 +1,55 @@
+import { PartitionManageParam, PartitionParam } from '@/pages/partitions/Types';
+import BaseModel from './BaseModel';
+import { PartitionData } from '@server/types';
+
+export class PartitionService extends BaseModel {
+  constructor(props: {}) {
+    super(props);
+    Object.assign(this, props);
+  }
+
+  static getPartitions(collectionName: string) {
+    const path = `/partitions`;
+
+    return super.findAll<PartitionData[]>({
+      path,
+      params: { collection_name: collectionName },
+    });
+  }
+
+  static managePartition(param: PartitionManageParam) {
+    const { collectionName, partitionName, type } = param;
+    return super.create({
+      path: `/partitions`,
+      data: {
+        collection_name: collectionName,
+        partition_name: partitionName,
+        type,
+      },
+    });
+  }
+
+  static loadPartition(param: PartitionParam) {
+    const { collectionName, partitionNames } = param;
+    const path = `/partitions/load`;
+    return super.update({
+      path,
+      data: {
+        collection_name: collectionName,
+        partition_names: partitionNames,
+      },
+    });
+  }
+
+  static releasePartition(param: PartitionParam) {
+    const { collectionName, partitionNames } = param;
+    const path = `/partitions/release`;
+    return super.update({
+      path,
+      data: {
+        collection_name: collectionName,
+        partition_names: partitionNames,
+      },
+    });
+  }
+}

+ 0 - 87
client/src/http/Partition.ts

@@ -1,87 +0,0 @@
-import dayjs from 'dayjs';
-import { LOADING_STATE } from '@/consts';
-import {
-  PartitionManageParam,
-  PartitionParam,
-  PartitionData,
-} from '@/pages/partitions/Types';
-import { formatNumber } from '@/utils';
-import BaseModel from './BaseModel';
-
-export class Partition extends BaseModel implements PartitionData {
-  public id!: string;
-  public name!: string;
-  public rowCount!: string;
-  public createdTime!: string;
-
-  constructor(props: {}) {
-    super(props);
-    Object.assign(this, props);
-  }
-
-  static URL_BASE = `/partitions`;
-
-  static getPartitions(collectionName: string): Promise<Partition[]> {
-    const path = this.URL_BASE;
-
-    return super.findAll({ path, params: { collection_name: collectionName } });
-  }
-
-  static managePartition(param: PartitionManageParam) {
-    const { collectionName, partitionName, type } = param;
-    const path = this.URL_BASE;
-    return super.create({
-      path,
-      data: {
-        collection_name: collectionName,
-        partition_name: partitionName,
-        type,
-      },
-    });
-  }
-
-  static loadPartition(param: PartitionParam) {
-    const { collectionName, partitionNames } = param;
-    const path = `${this.URL_BASE}/load`;
-    return super.update({
-      path,
-      data: {
-        collection_name: collectionName,
-        partition_names: partitionNames,
-      },
-    });
-  }
-
-  static releasePartition(param: PartitionParam) {
-    const { collectionName, partitionNames } = param;
-    const path = `${this.URL_BASE}/release`;
-    return super.update({
-      path,
-      data: {
-        collection_name: collectionName,
-        partition_names: partitionNames,
-      },
-    });
-  }
-
-  get partitionName() {
-    return this.name === '_default' ? 'Default partition' : this.name;
-  }
-
-  get entityCount() {
-    return formatNumber(Number(this.rowCount));
-  }
-
-  get status() {
-    // @TODO replace mock data
-    return LOADING_STATE.UNLOADED;
-  }
-
-  // Befor milvus-2.0-rc3  will return '0'.
-  // If milvus is stable, we can remote this condition/
-  get createdAt(): string {
-    return this.createdTime && this.createdTime !== '0'
-      ? dayjs(Number(this.createdTime)).format('YYYY-MM-DD HH:mm:ss')
-      : '';
-  }
-}

+ 3 - 3
client/src/http/index.ts

@@ -1,15 +1,15 @@
-// objects map
+// objects map(deprecating)
 export * from './Axios';
 export * from './BaseModel';
 export * from './Collection';
-export * from './Database';
 export * from './MilvusIndex';
 export * from './Field';
-export * from './Partition';
 export * from './User';
 export * from './Segment';
 
 // service
+export * from './Partition.service';
 export * from './Data.service';
 export * from './Milvus.service';
+export * from './Database.service';
 export * from './Prometheus.service';

+ 4 - 4
client/src/i18n/en/insert.ts

@@ -1,5 +1,5 @@
 const insertTrans = {
-  import: 'Import Data',
+  import: 'Import File',
   targetTip: 'Where to put your data',
   file: 'File',
   uploaderLabel: 'Select a .csv or .json file',
@@ -10,7 +10,7 @@ const insertTrans = {
     `CSV or JSON file is supported`,
     `Ensure data column names match field label names in Schema.`,
     `Data should be <150MB and <100,000 rows for proper import.`,
-    `"Import Data" only appends new records; it doesn't update existing ones.`,
+    `"Import File" only appends new records; it doesn't update existing ones.`,
   ],
   overSizeWarning: 'File data size should less than {{size}}MB',
   isContainFieldNames: 'First row contains field names?',
@@ -27,8 +27,8 @@ const insertTrans = {
 
   statusLoading: 'Your data is importing now...It may take few minutes',
   statusLoadingTip: 'Please wait patiently, thank you',
-  statusSuccess: 'Import Data Successfully!',
-  statusError: 'Import Data Failed!',
+  statusSuccess: 'Import File Successfully!',
+  statusError: 'Import File Failed!',
 
   importSampleData: 'Insert sample data into {{collection}}',
   sampleDataSize: 'Choose sample data size',

+ 5 - 2
client/src/pages/database/Create.tsx

@@ -14,7 +14,10 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
 }));
 
-const CreateUser: FC<CreateDatabaseProps> = ({ handleCreate, handleClose }) => {
+const CreateDatabaseDialog: FC<CreateDatabaseProps> = ({
+  handleCreate,
+  handleClose,
+}) => {
   const { t: databaseTrans } = useTranslation('database');
   const { t: btnTrans } = useTranslation('btn');
   const { t: warningTrans } = useTranslation('warning');
@@ -81,4 +84,4 @@ const CreateUser: FC<CreateDatabaseProps> = ({ handleCreate, handleClose }) => {
   );
 };
 
-export default CreateUser;
+export default CreateDatabaseDialog;

+ 9 - 25
client/src/pages/database/Database.tsx

@@ -1,6 +1,6 @@
-import { useContext, useEffect, useState } from 'react';
+import { useContext, useState } from 'react';
 import { useTranslation } from 'react-i18next';
-import { Database } from '@/http';
+import { DatabaseService } from '@/http';
 import AttuGrid from '@/components/grid/Grid';
 import { ColDefinitionsType, ToolBarConfig } from '@/components/grid/Types';
 import {
@@ -12,15 +12,12 @@ import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate';
 import { rootContext, dataContext } from '@/context';
 import { useNavigationHook } from '@/hooks';
 import { ALL_ROUTER_TYPES } from '@/router/Types';
-import CreateUser from './Create';
+import CreateDatabaseDialog from './Create';
 
 const DatabaseAdminPage = () => {
   useNavigationHook(ALL_ROUTER_TYPES.DB_ADMIN);
-  const { setDatabaseList } = useContext(dataContext);
+  const { databases, fetchDatabases } = useContext(dataContext);
 
-  const [databases, setDatabases] = useState<DatabaseData[]>([
-    { name: 'default' },
-  ]);
   const [selectedDatabase, setSelectedDatabase] = useState<DatabaseData[]>([]);
   const { setDialog, handleCloseDialog, openSnackBar } =
     useContext(rootContext);
@@ -29,18 +26,8 @@ const DatabaseAdminPage = () => {
   const { t: btnTrans } = useTranslation('btn');
   const { t: dialogTrans } = useTranslation('dialog');
 
-  const fetchDatabases = async () => {
-    try {
-      const res = await Database.getDatabases();
-      setDatabases(res.db_names.map((v: string) => ({ name: v })));
-      setDatabaseList(res.db_names);
-    } catch (error) {
-      // do nothing
-    }
-  };
-
   const handleCreate = async (data: CreateDatabaseParams) => {
-    await Database.createDatabase(data);
+    await DatabaseService.createDatabase(data);
     fetchDatabases();
     openSnackBar(successTrans('create', { name: dbTrans('database') }));
     handleCloseDialog();
@@ -51,11 +38,12 @@ const DatabaseAdminPage = () => {
       const param: DropDatabaseParams = {
         db_name: db.name,
       };
-      await Database.dropDatabase(param);
+      await DatabaseService.dropDatabase(param);
     }
 
     openSnackBar(successTrans('delete', { name: dbTrans('database') }));
     fetchDatabases();
+    setSelectedDatabase([]);
     handleCloseDialog();
   };
 
@@ -68,7 +56,7 @@ const DatabaseAdminPage = () => {
           type: 'custom',
           params: {
             component: (
-              <CreateUser
+              <CreateDatabaseDialog
                 handleCreate={handleCreate}
                 handleClose={handleCloseDialog}
               />
@@ -124,16 +112,12 @@ const DatabaseAdminPage = () => {
     setSelectedDatabase(value);
   };
 
-  useEffect(() => {
-    fetchDatabases();
-  }, []);
-
   return (
     <div className="page-wrapper">
       <AttuGrid
         toolbarConfigs={toolbarConfigs}
         colDefinitions={colDefinitions}
-        rows={databases}
+        rows={databases.map(d => ({ name: d }))}
         rowCount={databases.length}
         primaryKey="name"
         showPagination={false}

+ 2 - 2
client/src/pages/dialogs/CreatePartitionDialog.tsx

@@ -7,7 +7,7 @@ import CustomInput from '@/components/customInput/CustomInput';
 import { ITextfieldConfig } from '@/components/customInput/Types';
 import { useFormValidation } from '@/hooks';
 import { formatForm } from '@/utils';
-import { Partition } from '@/http';
+import { PartitionService } from '@/http';
 import { PartitionCreateProps } from './Types';
 import { PartitionManageParam } from '../partitions/Types';
 import { ManageRequestMethods } from '../../types/Common';
@@ -66,7 +66,7 @@ const CreatePartition: FC<PartitionCreateProps> = ({
       type: ManageRequestMethods.CREATE,
     };
 
-    await Partition.managePartition(param);
+    await PartitionService.managePartition(param);
     onCreate && onCreate();
     handleCloseDialog();
   };

+ 3 - 3
client/src/pages/dialogs/DropPartitionDialog.tsx

@@ -2,7 +2,7 @@ import { FC, useContext } from 'react';
 import { useTranslation } from 'react-i18next';
 import { rootContext } from '@/context';
 import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate';
-import { Partition } from '@/http';
+import { PartitionService } from '@/http';
 import { PartitionManageParam } from '../partitions/Types';
 import { ManageRequestMethods } from '../../types/Common';
 import { DropPartitionProps } from './Types';
@@ -17,11 +17,11 @@ const DropPartitionDialog: FC<DropPartitionProps> = props => {
   const handleDelete = async () => {
     for (const partition of partitions) {
       const param: PartitionManageParam = {
-        partitionName: partition.partitionName,
+        partitionName: partition.name,
         collectionName,
         type: ManageRequestMethods.DELETE,
       };
-      await Partition.managePartition(param);
+      await PartitionService.managePartition(param);
     }
 
     handleCloseDialog();

+ 1 - 1
client/src/pages/dialogs/Types.ts

@@ -1,5 +1,5 @@
 import { Collection } from '@/http';
-import { PartitionData } from '../partitions/Types';
+import { PartitionData } from '@server/types';
 
 export interface DropCollectionProps {
   collections: Collection[];

+ 4 - 4
client/src/pages/dialogs/insert/Dialog.tsx

@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
 import DialogTemplate from '@/components/customDialog/DialogTemplate';
 import icons from '@/components/icons/Icons';
 import { Option } from '@/components/customSelector/Types';
-import { Partition } from '@/http';
+import { PartitionService } from '@/http';
 import { rootContext } from '@/context';
 import { combineHeadsAndData } from '@/utils';
 import { FILE_MIME_TYPE } from '@/consts';
@@ -132,9 +132,9 @@ const InsertContainer: FC<InsertContentProps> = ({
   // every time selected collection value change, partition options and default value will change
   const fetchPartition = useCallback(async () => {
     if (collectionValue) {
-      const partitions = await Partition.getPartitions(collectionValue);
+      const partitions = await PartitionService.getPartitions(collectionValue);
       const partitionOptions: Option[] = partitions.map(p => ({
-        label: p.partitionName,
+        label: p.name,
         value: p.name,
       }));
       setPartitionOptions(partitionOptions);
@@ -154,7 +154,7 @@ const InsertContainer: FC<InsertContentProps> = ({
     } else {
       const options = partitions
         .map(p => ({
-          label: p.partitionName,
+          label: p.name,
           value: p.name,
         }))
         // when there's single selected partition

+ 2 - 2
client/src/pages/dialogs/insert/Types.ts

@@ -1,7 +1,7 @@
-import { PartitionView } from '../../partitions/Types';
 import { FieldHttp, Collection } from '@/http';
 import { Option } from '@/components/customSelector/Types';
 import { FILE_MIME_TYPE } from '@/consts';
+import { PartitionData } from '@server/types';
 
 export interface InsertContentProps {
   // optional on partition page since its collection is fixed
@@ -9,7 +9,7 @@ export interface InsertContentProps {
   // required on partition page since user can't select collection to get schema
   schema?: FieldHttp[];
   // required on partition page
-  partitions?: PartitionView[];
+  partitions?: PartitionData[];
 
   // insert default selected collection
   // if default value is not '', collections not selectable

+ 63 - 87
client/src/pages/partitions/Partitions.tsx

@@ -9,16 +9,16 @@ import { usePaginationHook, useInsertDialogHook } from '@/hooks';
 import icons from '@/components/icons/Icons';
 import CustomToolTip from '@/components/customToolTip/CustomToolTip';
 import { rootContext } from '@/context';
-import { Collection, Partition, FieldHttp, DataService } from '@/http';
+import { Collection, PartitionService, FieldHttp } from '@/http';
 import InsertContainer from '../dialogs/insert/Dialog';
-import { InsertDataParam } from '../collections/Types';
 import CreatePartitionDialog from '../dialogs/CreatePartitionDialog';
 import DropPartitionDialog from '../dialogs/DropPartitionDialog';
-import { PartitionView } from './Types';
+import { PartitionData } from '@server/types';
+import { formatNumber } from '@/utils';
 
 const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
-    height: `calc(100vh - 160px)`,
+    height: `100%`,
   },
   icon: {
     fontSize: '20px',
@@ -30,8 +30,6 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
 }));
 
-let timer: NodeJS.Timeout | null = null;
-
 const Partitions = () => {
   const { collectionName = '' } = useParams<{ collectionName: string }>();
   const classes = useStyles();
@@ -46,30 +44,20 @@ const Partitions = () => {
 
   const { handleInsertDialog } = useInsertDialogHook();
 
-  const [selectedPartitions, setSelectedPartitions] = useState<PartitionView[]>(
+  const [selectedPartitions, setSelectedPartitions] = useState<PartitionData[]>(
     []
   );
-  const [partitions, setPartitions] = useState<PartitionView[]>([]);
-  const [searchedPartitions, setSearchedPartitions] = useState<PartitionView[]>(
+  const [partitions, setPartitions] = useState<PartitionData[]>([]);
+  const [searchedPartitions, setSearchedPartitions] = useState<PartitionData[]>(
     []
   );
-  const {
-    pageSize,
-    handlePageSize,
-    currentPage,
-    handleCurrentPage,
-    total,
-    data: partitionList,
-    order,
-    orderBy,
-    handleGridSort,
-  } = usePaginationHook(searchedPartitions);
+
   const [loading, setLoading] = useState<boolean>(true);
   const { setDialog, openSnackBar } = useContext(rootContext);
 
   const fetchPartitions = async (collectionName: string) => {
     try {
-      const res = await Partition.getPartitions(collectionName);
+      const res = await PartitionService.getPartitions(collectionName);
       setLoading(false);
       setPartitions(res);
     } catch (err) {
@@ -86,72 +74,41 @@ const Partitions = () => {
     fetchPartitions(collectionName);
   }, [collectionName]);
 
+  // search
   useEffect(() => {
-    if (timer) {
-      clearTimeout(timer);
-    }
-    // add loading manually
-    setLoading(true);
-    timer = setTimeout(() => {
-      const searchWords = [search];
-      const list = search
-        ? partitions.filter(p => p.partitionName.includes(search))
-        : partitions;
+    const list = search
+      ? partitions.filter(p => p.name.includes(search))
+      : partitions;
 
-      const highlightList = list.map(c => {
-        Object.assign(c, {
-          _nameElement: (
-            <Highlighter
-              textToHighlight={c.partitionName}
-              searchWords={searchWords}
-              highlightClassName={classes.highlight}
-            />
-          ),
-        });
-        return c;
-      });
-      setLoading(false);
-      setSearchedPartitions(highlightList);
-    }, 300);
+    setSearchedPartitions(list);
   }, [search, partitions]);
 
+  const {
+    pageSize,
+    handlePageSize,
+    currentPage,
+    handleCurrentPage,
+    total,
+    data: partitionList,
+    order,
+    orderBy,
+    handleGridSort,
+  } = usePaginationHook(searchedPartitions);
+
+  // on delete
   const onDelete = () => {
     openSnackBar(successTrans('delete', { name: t('partition') }));
     fetchPartitions(collectionName);
   };
 
+  // on handle search
   const handleSearch = (value: string) => {
     setSearch(value);
   };
 
-  const handleInsert = async (
-    collectionName: string,
-    partitionName: string,
-    fieldData: any[]
-  ): Promise<{ result: boolean; msg: string }> => {
-    const param: InsertDataParam = {
-      partition_name: partitionName,
-      fields_data: fieldData,
-    };
-    try {
-      await DataService.insertData(collectionName, param);
-      await DataService.flush(collectionName);
-      // update partitions
-      fetchPartitions(collectionName);
-
-      return { result: true, msg: '' };
-    } catch (err: any) {
-      const {
-        response: {
-          data: { message },
-        },
-      } = err;
-      return { result: false, msg: message || '' };
-    }
-  };
-
   const toolbarConfigs: ToolBarConfig[] = [
     {
+      btnVariant: 'text',
       label: t('create'),
       onClick: () => {
         setDialog({
@@ -173,7 +130,8 @@ const Partitions = () => {
       type: 'button',
       btnVariant: 'text',
       btnColor: 'secondary',
-      label: btnTrans('insert'),
+      label: btnTrans('importFile'),
+      icon: 'uploadFile',
       onClick: async () => {
         const collection = await fetchCollectionDetail(collectionName);
         const schema = collection.schema.fields.map(f => new FieldHttp(f));
@@ -183,12 +141,12 @@ const Partitions = () => {
             schema={schema}
             defaultSelectedCollection={collectionName}
             defaultSelectedPartition={
-              selectedPartitions.length === 1
-                ? selectedPartitions[0].partitionName
-                : ''
+              selectedPartitions.length === 1 ? selectedPartitions[0].name : ''
             }
             partitions={partitions}
-            onInsert={handleInsert}
+            onInsert={async () => {
+              await fetchPartitions(collectionName);
+            }}
           />
         );
       },
@@ -240,17 +198,23 @@ const Partitions = () => {
 
   const colDefinitions: ColDefinitionsType[] = [
     {
-      id: '_nameElement',
+      id: 'name',
       align: 'left',
-      disablePadding: false,
+      disablePadding: true,
+      sortBy: 'collectionName',
+      formatter({ name }) {
+        const newName = name === '_default' ? 'Default partition' : name;
+        return (
+          <Highlighter
+            textToHighlight={newName}
+            searchWords={[search]}
+            highlightClassName={classes.highlight}
+          />
+        );
+      },
       label: t('name'),
     },
-    {
-      id: 'createdAt',
-      align: 'left',
-      disablePadding: false,
-      label: t('createdTime'),
-    },
+
     // {
     //   id: '_statusElement',
     //   align: 'left',
@@ -258,7 +222,7 @@ const Partitions = () => {
     //   label: t('status'),
     // },
     {
-      id: 'entityCount',
+      id: 'rowCount',
       align: 'left',
       disablePadding: false,
       label: (
@@ -269,6 +233,9 @@ const Partitions = () => {
           </CustomToolTip>
         </span>
       ),
+      formatter(data) {
+        return formatNumber(Number(data.rowCount));
+      },
     },
     // {
     //   id: 'action',
@@ -294,9 +261,18 @@ const Partitions = () => {
     //     },
     //   ],
     // },
+    {
+      id: 'createdTime',
+      align: 'left',
+      disablePadding: false,
+      formatter(data) {
+        return new Date(Number(data.createdTime)).toLocaleString();
+      },
+      label: t('createdTime'),
+    },
   ];
 
-  const handleSelectChange = (value: PartitionView[]) => {
+  const handleSelectChange = (value: PartitionData[]) => {
     setSelectedPartitions(value);
   };
 

+ 0 - 13
client/src/pages/partitions/Types.ts

@@ -2,19 +2,6 @@ import { ReactElement } from 'react';
 import { LOADING_STATE } from '@/consts';
 import { ManageRequestMethods } from '../../types/Common';
 
-export interface PartitionData {
-  id: string;
-  name: string;
-  status: LOADING_STATE;
-  entityCount: string;
-  partitionName: string;
-}
-
-export interface PartitionView extends PartitionData {
-  _nameElement?: ReactElement;
-  _statusElement?: ReactElement;
-}
-
 // delete and create
 export interface PartitionManageParam {
   collectionName: string;

+ 20 - 21
client/src/pages/search/VectorSearch.tsx

@@ -16,7 +16,7 @@ import SimpleMenu from '@/components/menu/SimpleMenu';
 import { Option } from '@/components/customSelector/Types';
 import Filter from '@/components/advancedSearch';
 import { Field } from '@/components/advancedSearch/Types';
-import { Collection } from '@/http';
+import { Collection, DataService } from '@/http';
 import {
   parseValue,
   parseLocationSearch,
@@ -43,7 +43,7 @@ import { FieldOption, SearchResultView, VectorSearchParam } from './Types';
 const VectorSearch = () => {
   useNavigationHook(ALL_ROUTER_TYPES.SEARCH);
   const location = useLocation();
-  const { database } = useContext(dataContext);
+  const { database, collections } = useContext(dataContext);
 
   // i18n
   const { t: searchTrans } = useTranslation('search');
@@ -53,7 +53,6 @@ const VectorSearch = () => {
 
   // data stored inside the component
   const [tableLoading, setTableLoading] = useState<boolean>(false);
-  const [collections, setCollections] = useState<Collection[]>([]);
   const [selectedCollection, setSelectedCollection] = useState<string>('');
   const [fieldOptions, setFieldOptions] = useState<FieldOption[]>([]);
   // fields for advanced filter
@@ -92,15 +91,6 @@ const VectorSearch = () => {
     handleGridSort,
   } = usePaginationHook(searchResultMemo || []);
 
-  const collectionOptions: Option[] = useMemo(
-    () =>
-      collections.map(c => ({
-        label: c.collectionName,
-        value: c.collectionName,
-      })),
-    [collections]
-  );
-
   const outputFields: string[] = useMemo(() => {
     const s = collections.find(c => c.collectionName === selectedCollection);
 
@@ -249,10 +239,17 @@ const VectorSearch = () => {
   ]);
 
   // fetch data
-  const fetchCollections = useCallback(async () => {
-    const collections = await Collection.getCollections();
-    setCollections(collections.filter(c => c.status === LOADING_STATE.LOADED));
-  }, [database]);
+  const loadedCollections = collections.filter(
+    c => c.status === LOADING_STATE.LOADED
+  );
+  const collectionOptions: Option[] = useMemo(
+    () =>
+      loadedCollections.map(c => ({
+        label: c.collectionName,
+        value: c.collectionName,
+      })),
+    [loadedCollections]
+  );
 
   const fetchFieldsWithIndex = useCallback(
     async (collectionName: string, collections: Collection[]) => {
@@ -281,13 +278,11 @@ const VectorSearch = () => {
     [collections]
   );
 
-  useEffect(() => {
-    fetchCollections();
-  }, [fetchCollections]);
-
   // clear selection if database is changed
   useEffect(() => {
     setSelectedCollection('');
+    setVectors('');
+    setSearchResult(null);
   }, [database]);
 
   // get field options with index when selected collection changed
@@ -364,7 +359,10 @@ const VectorSearch = () => {
 
     setTableLoading(true);
     try {
-      const res = await Collection.vectorSearchData(selectedCollection, params);
+      const res = await DataService.vectorSearchData(
+        selectedCollection,
+        params
+      );
       setTableLoading(false);
       setSearchResult(res.results);
       setLatency(res.latency);
@@ -552,6 +550,7 @@ const VectorSearch = () => {
             rowCount={total}
             primaryKey="rank"
             page={currentPage}
+            rowHeight={43}
             onPageChange={handlePageChange}
             rowsPerPage={pageSize}
             setRowsPerPage={handlePageSize}

+ 1 - 6
server/src/partitions/partitions.controller.ts

@@ -1,12 +1,7 @@
 import { NextFunction, Request, Response, Router } from 'express';
 import { dtoValidationMiddleware } from '../middleware/validation';
 import { PartitionsService } from './partitions.service';
-
-import {
-  GetPartitionsInfoDto,
-  ManagePartitionDto,
-  LoadPartitionsDto,
-} from './dto';
+import { ManagePartitionDto, LoadPartitionsDto } from './dto';
 
 export class PartitionController {
   private router: Router;

+ 11 - 7
server/src/partitions/partitions.service.ts

@@ -10,9 +10,13 @@ import { throwErrorFromSDK } from '../utils/Error';
 import { findKeyValue } from '../utils/Helper';
 import { ROW_COUNT } from '../utils';
 import { clientCache } from '../app';
+import { PartitionData } from '../types';
 
 export class PartitionsService {
-  async getPartitionsInfo(clientId: string, data: ShowPartitionsReq) {
+  async getPartitionsInfo(
+    clientId: string,
+    data: ShowPartitionsReq
+  ): Promise<PartitionData[]> {
     const result = [];
     const res = await this.getPartitions(clientId, data);
     if (res.partition_names && res.partition_names.length) {
@@ -33,7 +37,7 @@ export class PartitionsService {
   }
 
   async getPartitions(clientId: string, data: ShowPartitionsReq) {
-        const { milvusClient } = clientCache.get(clientId);
+    const { milvusClient } = clientCache.get(clientId);
 
     const res = await milvusClient.showPartitions(data);
     throwErrorFromSDK(res.status);
@@ -41,7 +45,7 @@ export class PartitionsService {
   }
 
   async createPartition(clientId: string, data: CreatePartitionReq) {
-        const { milvusClient } = clientCache.get(clientId);
+    const { milvusClient } = clientCache.get(clientId);
 
     const res = await milvusClient.createPartition(data);
     throwErrorFromSDK(res);
@@ -49,7 +53,7 @@ export class PartitionsService {
   }
 
   async deletePartition(clientId: string, data: DropPartitionReq) {
-        const { milvusClient } = clientCache.get(clientId);
+    const { milvusClient } = clientCache.get(clientId);
 
     const res = await milvusClient.dropPartition(data);
     throwErrorFromSDK(res);
@@ -60,7 +64,7 @@ export class PartitionsService {
     clientId: string,
     data: GetPartitionStatisticsReq
   ) {
-        const { milvusClient } = clientCache.get(clientId);
+    const { milvusClient } = clientCache.get(clientId);
 
     const res = await milvusClient.getPartitionStatistics(data);
     throwErrorFromSDK(res.status);
@@ -68,7 +72,7 @@ export class PartitionsService {
   }
 
   async loadPartitions(clientId: string, data: LoadPartitionsReq) {
-        const { milvusClient } = clientCache.get(clientId);
+    const { milvusClient } = clientCache.get(clientId);
 
     const res = await milvusClient.loadPartitions(data);
     throwErrorFromSDK(res);
@@ -76,7 +80,7 @@ export class PartitionsService {
   }
 
   async releasePartitions(clientId: string, data: ReleasePartitionsReq) {
-        const { milvusClient } = clientCache.get(clientId);
+    const { milvusClient } = clientCache.get(clientId);
 
     const res = await milvusClient.releasePartitions(data);
     throwErrorFromSDK(res);

+ 1 - 0
server/src/types/index.ts

@@ -14,3 +14,4 @@ export {
 } from '@zilliz/milvus2-sdk-node';
 
 export * from './collections.type';
+export * from './partitions.type'

+ 6 - 0
server/src/types/partitions.type.ts

@@ -0,0 +1,6 @@
+export type PartitionData = {
+  name: string;
+  id: number;
+  rowCount: string | number;
+  createdTime: string;
+};