diff --git a/client/src/components/customSelector/Types.ts b/client/src/components/customSelector/Types.ts index 286202f5..6855d5bf 100644 --- a/client/src/components/customSelector/Types.ts +++ b/client/src/components/customSelector/Types.ts @@ -1,9 +1,9 @@ import { FormControlClassKey, SelectProps } from '@material-ui/core'; import { ClassNameMap } from '@material-ui/core/styles/withStyles'; -export interface Option { - label: string; - value: string | number; +export interface Option { + label: T; + value: U; } export interface GroupOption { diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index 05f6b86f..9eff0891 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -10,7 +10,7 @@ import { import AccountCircleIcon from '@material-ui/icons/AccountCircle'; import { useNavigate } from 'react-router-dom'; import { navContext, dataContext, authContext } from '@/context'; -import { MilvusHttp } from '@/http'; +import { MilvusService } from '@/http'; import { MILVUS_ADDRESS, LOGIN_USERNAME } from '@/consts'; import CustomSelector from '@/components/customSelector/CustomSelector'; import icons from '../icons/Icons'; @@ -90,7 +90,7 @@ const Header: FC = props => { setAddress(''); setUsername(''); setIsAuth(false); - await MilvusHttp.closeConnection(); + await MilvusService.closeConnection(); window.localStorage.removeItem(MILVUS_ADDRESS); window.localStorage.removeItem(LOGIN_USERNAME); // make sure we clear state in all pages @@ -98,7 +98,7 @@ const Header: FC = props => { }; const useDatabase = async (database: string) => { - await MilvusHttp.useDatabase({ database }); + await MilvusService.useDatabase({ database }); }; const dbOptions = databases.map(d => ({ value: d, label: d })); diff --git a/client/src/components/status/Types.ts b/client/src/components/status/Types.ts index a30ff1d1..067abd1d 100644 --- a/client/src/components/status/Types.ts +++ b/client/src/components/status/Types.ts @@ -1,15 +1,20 @@ import { LOADING_STATE } from '@/consts'; +import { FieldHttp } from '@/http'; -// export enum StatusEnum { -// 'unloaded', -// 'loaded', -// 'error', -// } export type StatusType = { status: LOADING_STATE; percentage?: string; }; +export type StatusActionType = { + status: LOADING_STATE; + percentage?: string; + action?: Function; + field: FieldHttp; + collectionName: string; + onIndexCreate: Function; +}; + // @todo need rename export enum ChildrenStatusType { CREATING = 'creating', diff --git a/client/src/context/Auth.tsx b/client/src/context/Auth.tsx index d49e3c82..c6acec41 100644 --- a/client/src/context/Auth.tsx +++ b/client/src/context/Auth.tsx @@ -1,6 +1,6 @@ import { createContext, useEffect, useState } from 'react'; import { MILVUS_ADDRESS, LOGIN_USERNAME } from '@/consts'; -import { MilvusHttp } from '@/http'; +import { MilvusService } from '@/http'; import { AuthContextType } from './Types'; export const authContext = createContext({ @@ -33,7 +33,7 @@ export const AuthProvider = (props: { children: React.ReactNode }) => { if (!milvusAddress) { return; } - const res = await MilvusHttp.check(milvusAddress); + const res = await MilvusService.check(milvusAddress); setAddress(res.connected ? milvusAddress : ''); res.connected && setIsAuth(true); if (!res.connected) { diff --git a/client/src/context/Data.tsx b/client/src/context/Data.tsx index 0689301b..eac0940a 100644 --- a/client/src/context/Data.tsx +++ b/client/src/context/Data.tsx @@ -1,5 +1,5 @@ import { createContext, useEffect, useState, useContext } from 'react'; -import { DatabaseHttp, UserHttp, MilvusHttp } from '@/http'; +import { Database, User, MilvusService } from '@/http'; import { parseJson, getNode, getSystemConfigs } from '@/utils'; import { MILVUS_NODE_TYPE } from '@/consts'; import { authContext } from '@/context'; @@ -24,10 +24,10 @@ export const DataProvider = (props: { children: React.ReactNode }) => { try { // fetch all data const [databases, metrics, users, roles] = await Promise.all([ - DatabaseHttp.getDatabases(), - MilvusHttp.getMetrics(), - UserHttp.getUsers(), - UserHttp.getRoles(), + Database.getDatabases(), + MilvusService.getMetrics(), + User.getUsers(), + User.getRoles(), ]); // parse data diff --git a/client/src/context/Prometheus.tsx b/client/src/context/Prometheus.tsx index 5439a6c3..fd948539 100644 --- a/client/src/context/Prometheus.tsx +++ b/client/src/context/Prometheus.tsx @@ -13,7 +13,7 @@ import { WITH_PROMETHEUS, } from '@/consts'; import { formatPrometheusAddress } from '@/utils'; -import { PrometheusHttp } from '@/http'; +import { PrometheusService } from '@/http'; export const prometheusContext = createContext({ withPrometheus: false, @@ -59,7 +59,7 @@ export const PrometheusProvider = (props: { children: React.ReactNode }) => { if (withPrometheus) { const prometheusAddressformat = formatPrometheusAddress(prometheusAddress); - PrometheusHttp.setPrometheus({ + PrometheusService.setPrometheus({ prometheusAddress: prometheusAddressformat, prometheusInstance, prometheusNamespace, diff --git a/client/src/context/Root.tsx b/client/src/context/Root.tsx index bb94406a..e5c986df 100644 --- a/client/src/context/Root.tsx +++ b/client/src/context/Root.tsx @@ -11,7 +11,7 @@ import { } from './Types'; import CustomSnackBar from '@/components/customSnackBar/CustomSnackBar'; import CustomDialog from '@/components/customDialog/CustomDialog'; -import { MilvusHttp } from '@/http'; +import { MilvusService } from '@/http'; import { theme } from '../styles/theme'; const DefaultDialogConfigs: DialogType = { @@ -115,7 +115,7 @@ export const RootProvider = (props: { children: React.ReactNode }) => { useEffect(() => { if (isAuth) { const fetchVersion = async () => { - const res = await MilvusHttp.getVersion(); + const res = await MilvusService.getVersion(); setVersionInfo(res); }; fetchVersion(); diff --git a/client/src/context/Types.ts b/client/src/context/Types.ts index 700e4ea0..790c8c65 100644 --- a/client/src/context/Types.ts +++ b/client/src/context/Types.ts @@ -1,5 +1,5 @@ import { Dispatch, ReactElement, SetStateAction } from 'react'; -import { CollectionView } from '@/pages/collections/Types'; +import { Collection } from '@/http'; import { NavInfo } from '@/router/Types'; export type RootContextType = { @@ -90,6 +90,6 @@ export type NavContextType = { }; export type WebSocketType = { - collections: CollectionView[]; - setCollections: (data: CollectionView[]) => void; + collections: Collection[]; + setCollections: (data: Collection[]) => void; }; diff --git a/client/src/context/WebSocket.tsx b/client/src/context/WebSocket.tsx index 12ee9bde..35945a88 100644 --- a/client/src/context/WebSocket.tsx +++ b/client/src/context/WebSocket.tsx @@ -1,8 +1,7 @@ import { createContext, useContext, useEffect, useState, useRef } from 'react'; import { io, Socket } from 'socket.io-client'; import { authContext } from '@/context'; -import { url, CollectionHttp, MilvusHttp } from '@/http'; -import { CollectionView } from '@/pages/collections/Types'; +import { url, Collection, MilvusService } from '@/http'; import { checkIndexBuilding, checkLoading } from '@/utils'; import { WebSocketType } from './Types'; import { WS_EVENTS, WS_EVENTS_TYPE } from '@server/utils/Const'; @@ -15,13 +14,13 @@ export const webSocketContext = createContext({ const { Provider } = webSocketContext; export const WebSocketProvider = (props: { children: React.ReactNode }) => { - const [collections, setCollections] = useState([]); + const [collections, setCollections] = useState([]); const { isAuth } = useContext(authContext); const socket = useRef(null); useEffect(() => { if (isAuth) { - socket.current = io(url); + socket.current = io(url as string); socket.current.on('connect', function () { console.log('--- ws connected ---'); @@ -34,8 +33,8 @@ export const WebSocketProvider = (props: { children: React.ReactNode }) => { * After all collections are not loading or building index, tell server stop pulling data. */ socket.current.on(WS_EVENTS.COLLECTION, (data: any) => { - const collections: CollectionHttp[] = data.map( - (v: any) => new CollectionHttp(v) + const collections: Collection[] = data.map( + (v: any) => new Collection(v) ); const hasLoadingOrBuildingCollection = collections.some( @@ -46,7 +45,7 @@ export const WebSocketProvider = (props: { children: React.ReactNode }) => { // If no collection is building index or loading collection // stop server cron job if (!hasLoadingOrBuildingCollection) { - MilvusHttp.triggerCron({ + MilvusService.triggerCron({ name: WS_EVENTS.COLLECTION, type: WS_EVENTS_TYPE.STOP, }); diff --git a/client/src/http/BaseModel.ts b/client/src/http/BaseModel.ts index 263d808c..a74be639 100644 --- a/client/src/http/BaseModel.ts +++ b/client/src/http/BaseModel.ts @@ -41,7 +41,7 @@ export default class BaseModel { ); } - static async search(data: findParamsType) { + static async search(data: findParamsType) { const { method = 'get', params = {}, path = '', timeout } = data; const httpConfig = { method, @@ -50,26 +50,26 @@ export default class BaseModel { } as any; if (timeout) httpConfig.timeout = timeout; const res = await http(httpConfig); - return new this(res.data.data || {}); + return new this(res.data.data || {}) as T; } /** * Create instance in database */ - static async create(options: updateParamsType) { + static async create(options: updateParamsType) { const { path, data } = options; const res = await http.post(path, data); - return new this(res.data.data || {}); + return new this(res.data.data || {}) as T; } - static async update(options: updateParamsType) { + static async update(options: updateParamsType) { const { path, data } = options; const res = await http.put(path, data); - return new this(res.data.data || {}); + return new this(res.data.data || {}) as T; } - static async delete(options: updateParamsType) { + static async delete(options: updateParamsType) { const { path, data } = options; const res = await http.delete(path, { data: data }); diff --git a/client/src/http/Collection.ts b/client/src/http/Collection.ts index fbada52a..04a01f10 100644 --- a/client/src/http/Collection.ts +++ b/client/src/http/Collection.ts @@ -1,74 +1,58 @@ -import { ChildrenStatusType } from '../components/status/Types'; -import { - CollectionView, - DeleteEntitiesReq, - InsertDataParam, - LoadReplicaReq, - Replica, -} from '../pages/collections/Types'; -import { LoadSampleParam } from '../pages/dialogs/Types'; -import { Field } from '@/pages/schema/Types'; -import { VectorSearchParam } from '../types/SearchTypes'; +import dayjs from 'dayjs'; +import { LoadReplicaReq } from '@/pages/collections/Types'; +import { VectorSearchParam } from '@/types/SearchTypes'; import { QueryParam } from '@/pages/query/Types'; -import { IndexState, ShowCollectionsType } from '../types/Milvus'; -import { formatNumber } from '../utils/Common'; +import { formatNumber } from '@/utils/Common'; import BaseModel from './BaseModel'; -import { FieldHttp } from './Field'; -import dayjs from 'dayjs'; import { LOADING_STATE } from '@/consts'; - -export class CollectionHttp extends BaseModel implements CollectionView { - private aliases!: string[]; - private autoID!: boolean; - private collection_name!: string; - private description!: string; - private consistency_level!: string; - private rowCount!: string; - private index_status!: string; - private id!: string; - private loadedPercentage!: string; - private createdTime!: string; - private schema!: { - fields: Field[]; - autoID: boolean; - description: string; - enable_dynamic_field: boolean; - }; - private replicas!: Replica[]; +import { + IndexDescription, + ShowCollectionsType, + CollectionSchema, + ReplicaInfo, + CollectionData, +} from '@server/types'; +import { MilvusIndex, FieldHttp } from '@/http'; + +export class Collection extends BaseModel implements CollectionData { + public aliases: string[] = []; + public autoID!: boolean; + public collection_name!: string; + public description: string = ''; + public consistency_level!: string; + public rowCount!: string; + public id!: string; + public loadedPercentage!: string; + public createdTime!: number; + public index_descriptions!: IndexDescription[]; + public schema!: CollectionSchema; + public replicas!: ReplicaInfo[]; static COLLECTIONS_URL = '/collections'; - static COLLECTIONS_INDEX_STATUS_URL = '/collections/indexes/status'; static COLLECTIONS_STATISTICS_URL = '/collections/statistics'; - constructor(props: CollectionView) { + constructor(props: Collection) { super(props); Object.assign(this, props); } static getCollections(data?: { type: ShowCollectionsType; - }): Promise { + }): Promise { return super.findAll({ path: this.COLLECTIONS_URL, params: data || {} }); } static getCollection(name: string) { - return super.search({ + return super.search({ path: `${this.COLLECTIONS_URL}/${name}`, params: {}, - }) as Promise; + }); } static createCollection(data: any) { return super.create({ path: this.COLLECTIONS_URL, data }); } - static getCollectionsIndexState(): Promise { - return super.findAll({ - path: this.COLLECTIONS_INDEX_STATUS_URL, - params: {}, - }); - } - static deleteCollection(collectionName: string) { return super.delete({ path: `${this.COLLECTIONS_URL}/${collectionName}` }); } @@ -100,52 +84,13 @@ export class CollectionHttp extends BaseModel implements CollectionView { return super.search({ path: this.COLLECTIONS_STATISTICS_URL, params: {} }); } - static getPSegments(collectionName: string) { - return super.search({ - path: `${this.COLLECTIONS_URL}/${collectionName}/psegments`, - params: {}, - }) as Promise<{ - infos: any; - }>; - } - static count(collectionName: string) { - return super.search({ + return super.search({ path: `${this.COLLECTIONS_URL}/${collectionName}/count`, params: {}, }); } - static getQSegments(collectionName: string) { - return super.search({ - path: `${this.COLLECTIONS_URL}/${collectionName}/qsegments`, - params: {}, - }) as Promise<{ - infos: any; - }>; - } - - static insertData(collectionName: string, param: InsertDataParam) { - return super.create({ - path: `${this.COLLECTIONS_URL}/${collectionName}/insert`, - data: param, - }); - } - - static importSample(collectionName: string, param: LoadSampleParam) { - return super.create({ - path: `${this.COLLECTIONS_URL}/${collectionName}/importSample`, - data: param, - }) as Promise<{ sampleFile: string }>; - } - - static deleteEntities(collectionName: string, param: DeleteEntitiesReq) { - return super.update({ - path: `${this.COLLECTIONS_URL}/${collectionName}/entities`, - data: param, - }); - } - static vectorSearchData(collectionName: string, params: VectorSearchParam) { return super.query({ path: `${this.COLLECTIONS_URL}/${collectionName}/search`, @@ -173,86 +118,64 @@ export class CollectionHttp extends BaseModel implements CollectionView { }); } - static compact(collectionName: string) { - return super.update({ - path: `${this.COLLECTIONS_URL}/${collectionName}/compact`, - }); - } - - get _autoId() { - return this.autoID; - } - - get _aliases() { - return this.aliases || []; - } - - get _desc() { + get desc() { return this.description || '--'; } - get _id() { - return this.id; - } - - get _name() { + get collectionName() { return this.collection_name; } - get _rowCount() { + get entityCount() { return formatNumber(Number(this.rowCount)); } - get _loadedPercentage() { - return this.loadedPercentage; - } // load status - get _status() { + get status() { // If not load, insight server will return '-1'. Otherwise milvus will return percentage - return this._loadedPercentage === '-1' + return this.loadedPercentage === '-1' ? LOADING_STATE.UNLOADED - : this._loadedPercentage === '100' + : this.loadedPercentage === '100' ? LOADING_STATE.LOADED : LOADING_STATE.LOADING; // return LOADING_STATE.LOADING } - get _consistencyLevel() { - return this.consistency_level; - } - - get _fields() { + get fields() { return this.schema.fields.map(f => new FieldHttp(f)); } - get _indexState() { - switch (this.index_status) { - case IndexState.InProgress: - return ChildrenStatusType.CREATING; - case IndexState.Failed: - return ChildrenStatusType.ERROR; - default: - return ChildrenStatusType.FINISH; - } + get indexes() { + return this.index_descriptions.map(index => new MilvusIndex(index)); } // Befor milvus-2.0-rc3 will return '0'. // If milvus is stable, we can remote this condition - get _createdTime(): string { - return this.createdTime && this.createdTime !== '0' + get createdAt(): string { + return this.createdTime && this.createdTime !== 0 ? dayjs(Number(this.createdTime)).format('YYYY-MM-DD HH:mm:ss') : ''; } - get _replicas(): Replica[] { + get replicasInfo(): ReplicaInfo[] { return this.replicas || []; } - get _enableDynamicField(): boolean { + get enableDynamicField(): boolean { return this.schema && this.schema.enable_dynamic_field; } - get _schema() { - return this.schema; + get fieldWithIndexInfo() { + let fields: FieldHttp[] = []; + for (const schema of this.fields) { + let field: FieldHttp = schema; + const index = this.indexes.find(i => i.field_name === schema.name); + field.indexParameterPairs = index?.indexParameterPairs || []; + field.indexType = index?.indexType || ''; + field.indexName = index?.index_name || ''; + + fields.push(field); + } + return fields; } } diff --git a/client/src/http/Data.service.ts b/client/src/http/Data.service.ts new file mode 100644 index 00000000..89ba2d3e --- /dev/null +++ b/client/src/http/Data.service.ts @@ -0,0 +1,44 @@ +import { LoadSampleParam } from '@/pages/dialogs/Types'; +import { InsertDataParam, DeleteEntitiesReq } from '@/pages/collections/Types'; +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({ + path: `${this.COLLECTIONS_URL}/${collectionName}/importSample`, + data: param, + }); + } + + static insertData(collectionName: string, param: InsertDataParam) { + return super.create({ + path: `${this.COLLECTIONS_URL}/${collectionName}/insert`, + data: param, + }); + } + + static deleteEntities(collectionName: string, param: DeleteEntitiesReq) { + return super.update({ + path: `${this.COLLECTIONS_URL}/${collectionName}/entities`, + data: param, + }); + } + + static flush(collectionName: string) { + return super.update({ + path: this.FLUSH_URL, + data: { + collection_names: [collectionName], + }, + }); + } +} diff --git a/client/src/http/Database.ts b/client/src/http/Database.ts index 2446ca7c..c9d9c778 100644 --- a/client/src/http/Database.ts +++ b/client/src/http/Database.ts @@ -4,8 +4,8 @@ import { } from '../pages/database/Types'; import BaseModel from './BaseModel'; -export class DatabaseHttp extends BaseModel { - private names!: string[]; +export class Database extends BaseModel { + public db_names!: string[]; constructor(props: {}) { super(props); @@ -15,9 +15,7 @@ export class DatabaseHttp extends BaseModel { static DATABASE_URL = `/databases`; static getDatabases() { - return super.search({ path: this.DATABASE_URL, params: {} }) as Promise<{ - db_names: string[]; - }>; + return super.search({ path: this.DATABASE_URL, params: {} }) as Promise; } static createDatabase(data: CreateDatabaseParams) { @@ -27,8 +25,4 @@ export class DatabaseHttp extends BaseModel { static dropDatabase(data: DropDatabaseParams) { return super.delete({ path: `${this.DATABASE_URL}/${data.db_name}` }); } - - get _names() { - return this.names; - } } diff --git a/client/src/http/Field.ts b/client/src/http/Field.ts index e35f47dd..db46a94f 100644 --- a/client/src/http/Field.ts +++ b/client/src/http/Field.ts @@ -1,9 +1,8 @@ -import { DataTypeStringEnum } from '@/consts'; -import { FieldData } from '../pages/schema/Types'; import BaseModel from './BaseModel'; +import { KeyValuePair } from '@server/types'; +import { DataTypeStringEnum } from '@/consts'; -export class FieldHttp extends BaseModel implements FieldData { - data_type!: DataTypeStringEnum; +export class FieldHttp extends BaseModel { fieldID!: string; type_params!: { key: string; value: string }[]; is_primary_key!: true; @@ -11,62 +10,49 @@ export class FieldHttp extends BaseModel implements FieldData { name!: string; description!: string; autoID!: boolean; + data_type!: DataTypeStringEnum; element_type!: DataTypeStringEnum; + index_params!: KeyValuePair[]; + createIndexDisabled?: boolean = false; + indexType: string = ''; + indexName: string = ''; + indexParameterPairs: { key: string; value: string }[] = []; constructor(props: {}) { super(props); Object.assign(this, props); } - static async getFields(collectionName: string): Promise { - const path = `/collections/${collectionName}`; - - const res = await super.findAll({ - path, - params: {}, - }); - - return res.schema.fields.map((f: any) => new this(f)); - } - - get _fieldId() { - return this.fieldID; - } - - get _isPrimaryKey() { + get isPrimaryKey() { return this.is_primary_key; } - get _isPartitionKey() { + get isPartitionKey() { return this.is_partition_key; } - get _isAutoId() { + get isAutoId() { return this.autoID; } - get _fieldName() { - return this.name; - } - - get _fieldType() { + get fieldType() { return this.data_type; } - get _desc() { + get desc() { return this.description || '--'; } - get _dimension() { + get dimension() { return this.type_params.find(item => item.key === 'dim')?.value || ''; } - get _maxLength() { + get maxLength() { return ( this.type_params.find(item => item.key === 'max_length')?.value || '' ); } - get _maxCapacity() { + get maxCapacity() { return ( this.type_params.find(item => item.key === 'max_capacity')?.value || '' ); diff --git a/client/src/http/Milvus.ts b/client/src/http/Milvus.service.ts similarity index 85% rename from client/src/http/Milvus.ts rename to client/src/http/Milvus.service.ts index f0a23c34..2192c29c 100644 --- a/client/src/http/Milvus.ts +++ b/client/src/http/Milvus.service.ts @@ -1,11 +1,10 @@ import { WS_EVENTS, WS_EVENTS_TYPE } from '@server/utils/Const'; import BaseModel from './BaseModel'; -export class MilvusHttp extends BaseModel { +export class MilvusService extends BaseModel { static CONNECT_URL = '/milvus/connect'; static DISCONNECT_URL = '/milvus/disconnect'; static CHECK_URL = '/milvus/check'; - static FLUSH_URL = '/milvus/flush'; static METRICS_URL = '/milvus/metrics'; static VERSION_URL = '/milvus/version'; static USE_DB_URL = '/milvus/usedb'; @@ -43,15 +42,6 @@ export class MilvusHttp extends BaseModel { }) as Promise<{ connected: boolean }>; } - static flush(collectionName: string) { - return super.update({ - path: this.FLUSH_URL, - data: { - collection_names: [collectionName], - }, - }); - } - static getMetrics() { return super.search({ path: this.METRICS_URL, diff --git a/client/src/http/MilvusIndex.ts b/client/src/http/MilvusIndex.ts index acf393a4..2528afa3 100644 --- a/client/src/http/MilvusIndex.ts +++ b/client/src/http/MilvusIndex.ts @@ -1,15 +1,12 @@ -import { - IndexCreateParam, - IndexManageParam, - IndexView, -} from '../pages/schema/Types'; +import { IndexCreateParam, IndexManageParam } from '../pages/schema/Types'; import { ManageRequestMethods } from '../types/Common'; -import { IndexDescription, IndexState } from '../types/Milvus'; +import { IndexState } from '../types/Milvus'; import { findKeyValue } from '../utils/Common'; import { getKeyValueListFromJsonString } from '../utils/Format'; import BaseModel from './BaseModel'; +import { IndexDescription } from '@server/types'; -export class IndexHttp extends BaseModel implements IndexView { +export class MilvusIndex extends BaseModel { params!: { key: string; value: string }[]; field_name!: string; index_name!: string; @@ -23,7 +20,7 @@ export class IndexHttp extends BaseModel implements IndexView { static BASE_URL = `/schema/index`; - static async getIndexInfo(collectionName: string): Promise { + static async getIndexInfo(collectionName: string): Promise { const path = this.BASE_URL; const res = await super.findAll({ @@ -52,14 +49,11 @@ export class IndexHttp extends BaseModel implements IndexView { return super.batchDelete({ path, data: { ...param, type } }); } - get _indexType() { + get indexType() { return this.params.find(p => p.key === 'index_type')?.value || ''; } - get _indexName() { - return this.index_name; - } - get _indexParameterPairs() { + get indexParameterPairs() { const metricType = this.params.filter(v => v.key === 'metric_type'); // parms is json string, so we need parse it to key value array const params = findKeyValue(this.params, 'params'); @@ -69,11 +63,7 @@ export class IndexHttp extends BaseModel implements IndexView { return metricType; } - get _fieldName() { - return this.field_name; - } - - get _metricType() { + get metricType() { return this.params.find(p => p.key === 'metric_type')?.value || ''; } } diff --git a/client/src/http/Partition.ts b/client/src/http/Partition.ts index 81378acb..10c688e1 100644 --- a/client/src/http/Partition.ts +++ b/client/src/http/Partition.ts @@ -8,11 +8,11 @@ import { import { formatNumber } from '@/utils'; import BaseModel from './BaseModel'; -export class PartitionHttp extends BaseModel implements PartitionData { - private id!: string; - private name!: string; - private rowCount!: string; - private createdTime!: string; +export class Partition extends BaseModel implements PartitionData { + public id!: string; + public name!: string; + public rowCount!: string; + public createdTime!: string; constructor(props: {}) { super(props); @@ -21,7 +21,7 @@ export class PartitionHttp extends BaseModel implements PartitionData { static URL_BASE = `/partitions`; - static getPartitions(collectionName: string): Promise { + static getPartitions(collectionName: string): Promise { const path = this.URL_BASE; return super.findAll({ path, params: { collection_name: collectionName } }); @@ -64,30 +64,22 @@ export class PartitionHttp extends BaseModel implements PartitionData { }); } - get _id() { - return this.id; - } - - get _name() { - return this.name; - } - - get _formatName() { + get partitionName() { return this.name === '_default' ? 'Default partition' : this.name; } - get _rowCount() { + get entityCount() { return formatNumber(Number(this.rowCount)); } - get _status() { + 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 _createdTime(): string { + get createdAt(): string { return this.createdTime && this.createdTime !== '0' ? dayjs(Number(this.createdTime)).format('YYYY-MM-DD HH:mm:ss') : ''; diff --git a/client/src/http/Prometheus.ts b/client/src/http/Prometheus.service.ts similarity index 83% rename from client/src/http/Prometheus.ts rename to client/src/http/Prometheus.service.ts index bd0e60c8..0672c727 100644 --- a/client/src/http/Prometheus.ts +++ b/client/src/http/Prometheus.service.ts @@ -1,6 +1,6 @@ import BaseModel from './BaseModel'; -export class PrometheusHttp extends BaseModel { +export class PrometheusService extends BaseModel { static SET_PROMETHEUS_URL = '/prometheus/setPrometheus'; static GET_MILVUS_HEALTHY_DATA_URL = '/prometheus/getMilvusHealthyData'; @@ -19,7 +19,7 @@ export class PrometheusHttp extends BaseModel { prometheusNamespace: string; }) { return super.search({ - path: PrometheusHttp.SET_PROMETHEUS_URL, + path: PrometheusService.SET_PROMETHEUS_URL, params: { prometheusAddress, prometheusInstance, prometheusNamespace }, timeout: 1000, }); @@ -35,7 +35,7 @@ export class PrometheusHttp extends BaseModel { step: number; }) { return super.search({ - path: PrometheusHttp.GET_MILVUS_HEALTHY_DATA_URL, + path: PrometheusService.GET_MILVUS_HEALTHY_DATA_URL, params: { start, end, step }, }); } diff --git a/client/src/http/Segment.ts b/client/src/http/Segment.ts new file mode 100644 index 00000000..8ef89c59 --- /dev/null +++ b/client/src/http/Segment.ts @@ -0,0 +1,38 @@ +import BaseModel from './BaseModel'; +import { + GetQuerySegmentInfoResponse, + QuerySegmentInfo, + GePersistentSegmentInfoResponse, + PersistentSegmentInfo, +} from '@server/types'; + +export class Segement extends BaseModel { + static COLLECTIONS_URL = '/collections'; + + infos!: QuerySegmentInfo[] | PersistentSegmentInfo[]; + + constructor(props: {}) { + super(props); + Object.assign(this, props); + } + + static getQSegments(collectionName: string) { + return super.search({ + path: `${this.COLLECTIONS_URL}/${collectionName}/qsegments`, + params: {}, + }) as Promise; + } + + static getPSegments(collectionName: string) { + return super.search({ + path: `${this.COLLECTIONS_URL}/${collectionName}/psegments`, + params: {}, + }) as Promise; + } + + static compact(collectionName: string) { + return super.update({ + path: `${this.COLLECTIONS_URL}/${collectionName}/compact`, + }); + } +} diff --git a/client/src/http/User.ts b/client/src/http/User.ts index 3fe797f3..ee91d9b1 100644 --- a/client/src/http/User.ts +++ b/client/src/http/User.ts @@ -9,8 +9,8 @@ import { } from '../pages/user/Types'; import BaseModel from './BaseModel'; -export class UserHttp extends BaseModel { - private names!: string[]; +export class User extends BaseModel { + public names!: string[]; constructor(props: {}) { super(props); @@ -96,8 +96,4 @@ export class UserHttp extends BaseModel { Privileges: Record; }>; } - - get _names() { - return this.names; - } } diff --git a/client/src/http/index.ts b/client/src/http/index.ts index 733c0289..710987f9 100644 --- a/client/src/http/index.ts +++ b/client/src/http/index.ts @@ -1,10 +1,15 @@ +// objects map export * from './Axios'; export * from './BaseModel'; export * from './Collection'; export * from './Database'; -export * from './Milvus'; export * from './MilvusIndex'; export * from './Field'; export * from './Partition'; -export * from './Prometheus'; export * from './User'; +export * from './Segment'; + +// service +export * from './Data.service'; +export * from './Milvus.service'; +export * from './Prometheus.service'; diff --git a/client/src/i18n/cn/collection.ts b/client/src/i18n/cn/collection.ts index ffd182d2..ff81ed42 100644 --- a/client/src/i18n/cn/collection.ts +++ b/client/src/i18n/cn/collection.ts @@ -131,6 +131,11 @@ const collectionTrans = { '它确保在同一会话中所有数据写入可以立即在读取中感知。', consistencyEventuallyTooltip: '没有保证读写的顺序,副本最终会在没有进一步写操作的情况下收敛到相同的状态。', + releaseCollectionFirst: '请先释放collection.', + + clickToLoad: '点击加载collection。', + clickToRelease: '点击释放collection。', + collectionIsLoading: 'colleciton正在加载...', }; export default collectionTrans; diff --git a/client/src/i18n/en/collection.ts b/client/src/i18n/en/collection.ts index 466178c8..3ae3d33b 100644 --- a/client/src/i18n/en/collection.ts +++ b/client/src/i18n/en/collection.ts @@ -130,6 +130,11 @@ const collectionTrans = { consistencyStrongTooltip: `It ensures that users can read the latest version of data.`, consistencySessionTooltip: `It ensures that all data writes can be immediately perceived in reads during the same session.`, consistencyEventuallyTooltip: `There is no guaranteed order of reads and writes, and replicas eventually converge to the same state given that no further write operations are done.`, + releaseCollectionFirst: `Please release your collection first.`, + + clickToLoad: 'Click to load the collection.', + clickToRelease: 'Click to release the collection.', + collectionIsLoading: 'The collection is loading...', }; export default collectionTrans; diff --git a/client/src/i18n/en/index.ts b/client/src/i18n/en/index.ts index d570266b..58e2a56a 100644 --- a/client/src/i18n/en/index.ts +++ b/client/src/i18n/en/index.ts @@ -2,7 +2,7 @@ const indexTrans = { type: 'Index Type', param: 'Index Parameters', - create: 'Index', + create: 'Create Index', index: 'Index', desc: 'Description', diff --git a/client/src/pages/collections/Aliases.tsx b/client/src/pages/collections/Aliases.tsx index 291bdf11..4e748b70 100644 --- a/client/src/pages/collections/Aliases.tsx +++ b/client/src/pages/collections/Aliases.tsx @@ -7,7 +7,7 @@ import icons from '@/components/icons/Icons'; import DeleteIcon from '@material-ui/icons/Delete'; import CreateAliasDialog from '../dialogs/CreateAliasDialog'; import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate'; -import { CollectionHttp } from '@/http'; +import { Collection } from '@/http'; const useStyles = makeStyles((theme: Theme) => ({ wrapper: { @@ -77,7 +77,7 @@ export default function Aliases(props: AliasesProps) { collection: string; alias: string; }) => { - await CollectionHttp.dropAlias(params.collection, { alias: params.alias }); + await Collection.dropAlias(params.collection, { alias: params.alias }); openSnackBar(successTrans('delete', { name: collectionTrans('alias') })); handleCloseDialog(); onDelete(); diff --git a/client/src/pages/collections/Collection.tsx b/client/src/pages/collections/Collection.tsx index 03991b61..b5d16c99 100644 --- a/client/src/pages/collections/Collection.tsx +++ b/client/src/pages/collections/Collection.tsx @@ -13,7 +13,6 @@ import Schema from '../schema/Schema'; import Query from '../query/Query'; import Preview from '../preview/Preview'; import Segments from '../segments/Segments'; - import { TAB_ENUM } from './Types'; const useStyles = makeStyles((theme: Theme) => ({ diff --git a/client/src/pages/collections/Collections.tsx b/client/src/pages/collections/Collections.tsx index c1ac739a..ca0e6665 100644 --- a/client/src/pages/collections/Collections.tsx +++ b/client/src/pages/collections/Collections.tsx @@ -9,20 +9,18 @@ import { dataContext, webSocketContext, } from '@/context'; +import { Collection, MilvusService, DataService } from '@/http'; import { useNavigationHook, usePaginationHook } from '@/hooks'; import { ALL_ROUTER_TYPES } from '@/router/Types'; import AttuGrid from '@/components/grid/Grid'; import CustomToolBar from '@/components/grid/ToolBar'; -import { CollectionView, InsertDataParam } from './Types'; +import { InsertDataParam } from './Types'; import { ColDefinitionsType, ToolBarConfig } from '@/components/grid/Types'; import icons from '@/components/icons/Icons'; import EmptyCard from '@/components/cards/EmptyCard'; -import Status from '@/components/status/Status'; -import { ChildrenStatusType } from '@/components/status/Types'; -import StatusIcon from '@/components/status/StatusIcon'; +import StatusAction from '@/pages/collections/StatusAction'; import CustomToolTip from '@/components/customToolTip/CustomToolTip'; import CreateCollectionDialog from '../dialogs/CreateCollectionDialog'; -import { CollectionHttp, MilvusHttp } from '@/http'; import LoadCollectionDialog from '../dialogs/LoadCollectionDialog'; import ReleaseCollectionDialog from '../dialogs/ReleaseCollectionDialog'; import DropCollectionDialog from '../dialogs/DropCollectionDialog'; @@ -65,6 +63,7 @@ const useStyles = makeStyles((theme: Theme) => ({ chip: { color: theme.palette.text.primary, marginRight: theme.spacing(0.5), + background: `rgba(0, 0, 0, 0.04)`, }, })); @@ -78,9 +77,9 @@ const Collections = () => { (searchParams.get('search') as string) || '' ); const [loading, setLoading] = useState(false); - const [selectedCollections, setSelectedCollections] = useState< - CollectionView[] - >([]); + const [selectedCollections, setSelectedCollections] = useState( + [] + ); const { setDialog, openSnackBar } = useContext(rootContext); const { collections, setCollections } = useContext(webSocketContext); @@ -89,8 +88,6 @@ const Collections = () => { const { t: successTrans } = useTranslation('success'); const classes = useStyles(); - const LoadIcon = icons.load; - const ReleaseIcon = icons.release; const InfoIcon = icons.info; const SourceIcon = icons.source; @@ -101,48 +98,59 @@ const Collections = () => { Eventually: collectionTrans('consistencyEventuallyTooltip'), }; + const checkCollectionStatus = useCallback((collections: Collection[]) => { + const hasLoadingOrBuildingCollection = collections.some( + v => checkLoading(v) || checkIndexBuilding(v) + ); + + // if some collection is building index or loading, start pulling data + if (hasLoadingOrBuildingCollection) { + MilvusService.triggerCron({ + name: WS_EVENTS.COLLECTION, + type: WS_EVENTS_TYPE.START, + }); + } + }, []); + const fetchData = useCallback(async () => { try { setLoading(true); - const res = await CollectionHttp.getCollections(); - const hasLoadingOrBuildingCollection = res.some( - v => checkLoading(v) || checkIndexBuilding(v) - ); - - // if some collection is building index or loading, start pulling data - if (hasLoadingOrBuildingCollection) { - MilvusHttp.triggerCron({ - name: WS_EVENTS.COLLECTION, - type: WS_EVENTS_TYPE.START, - }); - } - - setCollections(res); + const collections = await Collection.getCollections(); + setCollections(collections); + checkCollectionStatus(collections); } finally { setLoading(false); } - }, [setCollections]); + }, [setCollections, checkCollectionStatus]); useEffect(() => { fetchData(); }, [fetchData, database]); + const getVectorField = (collection: Collection) => { + return collection.fieldWithIndexInfo!.find( + d => d.fieldType === 'FloatVector' || d.fieldType === 'BinaryVector' + ); + }; + const formatCollections = useMemo(() => { const filteredCollections = search - ? collections.filter(collection => collection._name.includes(search)) + ? collections.filter(collection => + collection.collectionName.includes(search) + ) : collections; const data = filteredCollections.map(v => { - // const indexStatus = statusRes.find(item => item._name === v._name); + // const indexStatus = statusRes.find(item => item.collectionName === v.collectionName); Object.assign(v, { nameElement: ( @@ -150,7 +158,7 @@ const Collections = () => { ), features: ( <> - {v._autoId ? ( + {v.autoID ? ( { /> ) : null} - {v._enableDynamicField ? ( + {v.enableDynamicField ? ( { ) : null} ), statusElement: ( - - ), - indexCreatingElement: ( - + { + setDialog({ + open: true, + type: 'custom', + params: { + component: + v.status === LOADING_STATE.UNLOADED ? ( + + ) : ( + + ), + }, + }); + }} + /> ), _aliasElement: ( @@ -232,8 +263,8 @@ const Collections = () => { fields_data: fieldData, }; try { - await CollectionHttp.insertData(collectionName, param); - await MilvusHttp.flush(collectionName); + await DataService.insertData(collectionName, param); + await DataService.flush(collectionName); // update collections fetchData(); return { result: true, msg: '' }; @@ -314,7 +345,7 @@ const Collections = () => { collections={formatCollections} defaultSelectedCollection={ selectedCollections.length === 1 - ? selectedCollections[0]._name + ? selectedCollections[0].collectionName : '' } // user can't select partition on collection page, so default value is '' @@ -344,7 +375,7 @@ const Collections = () => { component: ( ), }, @@ -402,25 +433,25 @@ const Collections = () => { id: 'nameElement', align: 'left', disablePadding: true, - sortBy: '_name', + sortBy: 'collectionName', label: collectionTrans('name'), }, { id: 'statusElement', align: 'left', disablePadding: false, - sortBy: '_status', + sortBy: 'status', label: collectionTrans('status'), }, { id: 'features', align: 'left', disablePadding: true, - sortBy: '_enableDynamicField', + sortBy: 'enableDynamicField', label: collectionTrans('features'), }, { - id: '_rowCount', + id: 'entityCount', align: 'left', disablePadding: false, label: ( @@ -432,84 +463,18 @@ const Collections = () => { ), }, - - // { - // id: 'consistency_level', - // align: 'left', - // disablePadding: true, - // label: ( - // - // {collectionTrans('consistency')} - // - // - // - // - // ), - // }, - { - id: '_desc', + id: 'desc', align: 'left', disablePadding: false, label: collectionTrans('desc'), }, { - id: '_createdTime', + id: 'createdAt', align: 'left', disablePadding: false, label: collectionTrans('createdTime'), }, - // { - // id: 'indexCreatingElement', - // align: 'left', - // disablePadding: false, - // label: '', - // }, - { - id: 'action', - align: 'center', - disablePadding: false, - label: '', - showActionCell: true, - isHoverAction: true, - actionBarConfigs: [ - { - onClick: (e: React.MouseEvent, row: CollectionView) => { - setDialog({ - open: true, - type: 'custom', - params: { - component: - row._status === LOADING_STATE.UNLOADED ? ( - - ) : ( - - ), - }, - }); - - e.preventDefault(); - }, - icon: 'load', - label: 'load', - showIconMethod: 'renderFn', - getLabel: (row: CollectionView) => - row._status === LOADING_STATE.UNLOADED ? 'load' : 'release', - renderIconFn: (row: CollectionView) => - row._status === LOADING_STATE.UNLOADED ? ( - - ) : ( - - ), - }, - ], - }, { id: 'import', align: 'center', @@ -519,12 +484,14 @@ const Collections = () => { isHoverAction: true, actionBarConfigs: [ { - onClick: (e: React.MouseEvent, row: CollectionView) => { + onClick: (e: React.MouseEvent, row: Collection) => { setDialog({ open: true, type: 'custom', params: { - component: , + component: ( + + ), }, }); }, @@ -532,7 +499,7 @@ const Collections = () => { label: 'Import', showIconMethod: 'renderFn', getLabel: () => 'Import sample data', - renderIconFn: (row: CollectionView) => , + renderIconFn: (row: Collection) => , }, ], }, @@ -573,7 +540,7 @@ const Collections = () => { colDefinitions={colDefinitions} rows={collectionList} rowCount={total} - primaryKey="_name" + primaryKey="collectionName" selected={selectedCollections} setSelected={handleSelectChange} page={currentPage} diff --git a/client/src/pages/collections/StatusAction.tsx b/client/src/pages/collections/StatusAction.tsx new file mode 100644 index 00000000..ef595fea --- /dev/null +++ b/client/src/pages/collections/StatusAction.tsx @@ -0,0 +1,167 @@ +import { FC, useMemo, MouseEvent, forwardRef } from 'react'; +import { + ChildrenStatusType, + StatusActionType, +} from '@/components/status/Types'; +import { useTranslation } from 'react-i18next'; +import { + makeStyles, + Theme, + createStyles, + Typography, + useTheme, + Tooltip, + Chip, +} from '@material-ui/core'; +import { LOADING_STATE } from '@/consts'; +import StatusIcon from '@/components/status/StatusIcon'; +import icons from '@/components/icons/Icons'; +import IndexTypeElement from '@/pages/schema/IndexTypeElement'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + display: 'flex', + alignItems: 'center', + }, + chip: { + border: 'none', + background: `rgba(0, 0, 0, 0.04)`, + marginRight: theme.spacing(0.5), + paddingLeft: theme.spacing(0.5), + }, + circle: { + backgroundColor: (props: any) => props.color, + borderRadius: '50%', + width: '10px', + height: '10px', + }, + + circleUnload: { + backgroundColor: theme.palette.attuGrey.main, + borderRadius: '50%', + width: '10px', + height: '10px', + }, + + loading: { + marginRight: '10px', + }, + icon: { + marginTop: theme.spacing(0.5), + }, + flash: { + animation: '$bgColorChange 1.5s infinite', + }, + + '@keyframes bgColorChange': { + '0%': { + backgroundColor: (props: any) => props.color, + }, + '50%': { + backgroundColor: 'transparent', + }, + '100%': { + backgroundColor: (props: any) => props.color, + }, + }, + }) +); + +const StatusAction: FC = props => { + const theme = useTheme(); + const classes = useStyles({ color: theme.palette.primary.main }); + const ReleaseIcon = icons.remove; + const LoadIcon = icons.addOutline; + + const { + status, + percentage = 0, + collectionName, + field, + action = () => {}, + onIndexCreate, + } = props; + const { t: commonTrans } = useTranslation(); + const { t: collectionTrans } = useTranslation('collection'); + + const statusTrans = commonTrans('status'); + const { + label, + tooltip, + icon = , + deleteIcon = , + } = useMemo(() => { + switch (status) { + case LOADING_STATE.UNLOADED: + return { + label: statusTrans.unloaded, + icon:
, + tooltip: collectionTrans('clickToLoad'), + deleteIcon: , + }; + + case LOADING_STATE.LOADED: + return { + label: statusTrans.loaded, + icon:
, + tooltip: collectionTrans('clickToRelease'), + deleteIcon: , + }; + case LOADING_STATE.LOADING: + return { + label: `${percentage}% ${statusTrans.loading}`, + tooltip: collectionTrans('collectionIsLoading'), + icon: ( + + ), + }; + + default: + return { + label: statusTrans.error, + icon: , + tooltip: '', + deleteIcon: , + }; + } + }, [status, statusTrans, percentage]); + + // UI state + const hasVectorIndex = field?.indexType !== ''; + const collectionLoaded = status === LOADING_STATE.LOADED; + + return ( +
+ {hasVectorIndex && ( + + {label}} + onDelete={() => action()} + onClick={(e: MouseEvent) => { + e.stopPropagation(); + action(); + }} + deleteIcon={deleteIcon} + size="small" + icon={icon} + /> + + )} + onIndexCreate()} + disabled={collectionLoaded} + disabledTooltip={collectionTrans('releaseCollectionFirst')} + /> +
+ ); +}; + +export default StatusAction; diff --git a/client/src/pages/collections/Types.ts b/client/src/pages/collections/Types.ts index 7649c194..6eea8c7a 100644 --- a/client/src/pages/collections/Types.ts +++ b/client/src/pages/collections/Types.ts @@ -1,23 +1,5 @@ -import { Dispatch, ReactElement, SetStateAction } from 'react'; -import { ChildrenStatusType } from '@/components/status/Types'; -import { LOADING_STATE, DataTypeEnum } from '@/consts'; -import { FieldData } from '../schema/Types'; - -export interface CollectionData { - _name: string; - _id: string; - _loadedPercentage: string; - _status: LOADING_STATE; - _rowCount: string; - _desc: string; - _indexState: ChildrenStatusType; - _fields?: FieldData[]; - _consistencyLevel: string; - _aliases: string[]; - _replicas: Replica[]; - _enableDynamicField: boolean; - _autoId: boolean; -} +import { Dispatch, SetStateAction } from 'react'; +import { DataTypeEnum } from '@/consts'; export interface Replica { collectionID: string; @@ -34,12 +16,6 @@ export interface ShardReplica { node_id: string[]; } -export interface CollectionView extends CollectionData { - nameElement?: ReactElement; - statusElement?: ReactElement; - indexCreatingElement?: ReactElement; -} - export interface CollectionCreateProps { onCreate?: () => void; } @@ -48,11 +24,11 @@ export interface CollectionCreateParam { collection_name: string; description: string; autoID: boolean; - fields: Field[]; + fields: CreateField[]; consistency_level: string; } -export interface Field { +export interface CreateField { name: string | null; data_type: DataTypeEnum; is_primary_key: boolean; @@ -79,8 +55,8 @@ export type CreateFieldType = | 'number'; export interface CreateFieldsProps { - fields: Field[]; - setFields: Dispatch>; + fields: CreateField[]; + setFields: Dispatch>; setFieldsValidation: Dispatch< SetStateAction<{ [x: string]: string | boolean }[]> >; diff --git a/client/src/pages/connect/AuthForm.tsx b/client/src/pages/connect/AuthForm.tsx index d2a737bd..0169cdc5 100644 --- a/client/src/pages/connect/AuthForm.tsx +++ b/client/src/pages/connect/AuthForm.tsx @@ -7,7 +7,7 @@ import icons from '@/components/icons/Icons'; import { ITextfieldConfig } from '@/components/customInput/Types'; import { useFormValidation } from '@/hooks'; import { formatForm } from '@/utils'; -import { MilvusHttp } from '@/http'; +import { MilvusService } from '@/http'; import { useNavigate } from 'react-router-dom'; import { rootContext, @@ -203,7 +203,7 @@ export const AuthForm = (props: any) => { const handleConnect = async (event: React.FormEvent) => { event.preventDefault(); - const result = await MilvusHttp.connect(form); + const result = await MilvusService.connect(form); setIsAuth(true); setAddress(form.address); diff --git a/client/src/pages/database/Database.tsx b/client/src/pages/database/Database.tsx index eb0aa2b5..e41df6f5 100644 --- a/client/src/pages/database/Database.tsx +++ b/client/src/pages/database/Database.tsx @@ -1,6 +1,6 @@ import { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { DatabaseHttp } from '@/http'; +import { Database } from '@/http'; import AttuGrid from '@/components/grid/Grid'; import { ColDefinitionsType, ToolBarConfig } from '@/components/grid/Types'; import { @@ -14,7 +14,7 @@ import { useNavigationHook } from '@/hooks'; import { ALL_ROUTER_TYPES } from '@/router/Types'; import CreateUser from './Create'; -const Database = () => { +const DatabasePage = () => { useNavigationHook(ALL_ROUTER_TYPES.DATABASES); const { setDatabaseList } = useContext(dataContext); @@ -31,7 +31,7 @@ const Database = () => { const fetchDatabases = async () => { try { - const res = await DatabaseHttp.getDatabases(); + const res = await Database.getDatabases(); setDatabases(res.db_names.map((v: string) => ({ name: v }))); setDatabaseList(res.db_names); } catch (error) { @@ -40,7 +40,7 @@ const Database = () => { }; const handleCreate = async (data: CreateDatabaseParams) => { - await DatabaseHttp.createDatabase(data); + await Database.createDatabase(data); fetchDatabases(); openSnackBar(successTrans('create', { name: dbTrans('database') })); handleCloseDialog(); @@ -51,7 +51,7 @@ const Database = () => { const param: DropDatabaseParams = { db_name: db.name, }; - await DatabaseHttp.dropDatabase(param); + await Database.dropDatabase(param); } openSnackBar(successTrans('delete', { name: dbTrans('database') })); @@ -150,4 +150,4 @@ const Database = () => { ); }; -export default Database; +export default DatabasePage; diff --git a/client/src/pages/dialogs/CompactDialog.tsx b/client/src/pages/dialogs/CompactDialog.tsx index d3b8417d..3ee90995 100644 --- a/client/src/pages/dialogs/CompactDialog.tsx +++ b/client/src/pages/dialogs/CompactDialog.tsx @@ -4,12 +4,12 @@ import { useTranslation } from 'react-i18next'; import { rootContext } from '@/context'; import DialogTemplate from '@/components/customDialog/DialogTemplate'; import { CompactDialogProps } from './Types'; -import { CollectionHttp } from '@/http'; +import { Segement } from '@/http'; const useStyles = makeStyles((theme: Theme) => ({ desc: { margin: '8px 0 16px 0', - maxWidth: '500px' + maxWidth: '500px', }, dialog: {}, })); @@ -26,7 +26,7 @@ const CompactDialog: FC = props => { const { t: btnTrans } = useTranslation('btn'); const handleConfirm = async () => { - await CollectionHttp.compact(collectionName); + await Segement.compact(collectionName); handleCloseDialog(); cb && cb(); @@ -43,10 +43,14 @@ const CompactDialog: FC = props => { handleClose={handleCloseDialog} children={ <> - - + } confirmLabel={btnTrans('confirm')} diff --git a/client/src/pages/dialogs/CreateAliasDialog.tsx b/client/src/pages/dialogs/CreateAliasDialog.tsx index 110df33a..7dcc7874 100644 --- a/client/src/pages/dialogs/CreateAliasDialog.tsx +++ b/client/src/pages/dialogs/CreateAliasDialog.tsx @@ -7,7 +7,7 @@ import CustomInput from '@/components/customInput/CustomInput'; import { formatForm } from '@/utils'; import { useFormValidation } from '@/hooks'; import { ITextfieldConfig } from '@/components/customInput/Types'; -import { CollectionHttp } from '@/http'; +import { Collection } from '@/http'; import { CreateAliasProps } from './Types'; const useStyles = makeStyles((theme: Theme) => ({ @@ -42,7 +42,7 @@ const CreateAliasDialog: FC = props => { }; const handleConfirm = async () => { - await CollectionHttp.createAlias(collectionName, form); + await Collection.createAlias(collectionName, form); handleCloseDialog(); cb && cb(); }; diff --git a/client/src/pages/dialogs/CreateCollectionDialog.tsx b/client/src/pages/dialogs/CreateCollectionDialog.tsx index c8439216..e1355506 100644 --- a/client/src/pages/dialogs/CreateCollectionDialog.tsx +++ b/client/src/pages/dialogs/CreateCollectionDialog.tsx @@ -15,11 +15,11 @@ import { useFormValidation } from '@/hooks'; import { formatForm, TypeEnum } from '@/utils'; import { DataTypeEnum, ConsistencyLevelEnum, DEFAULT_ATTU_DIM } from '@/consts'; import CreateFields from '../collections/CreateFields'; -import { CollectionHttp } from '@/http'; +import { Collection } from '@/http'; import { CollectionCreateParam, CollectionCreateProps, - Field, + CreateField, } from '../collections/Types'; import { CONSISTENCY_LEVEL_OPTIONS } from '../collections/Constants'; @@ -74,7 +74,7 @@ const CreateCollectionDialog: FC = ({ onCreate }) => { const [consistencyLevel, setConsistencyLevel] = useState(ConsistencyLevelEnum.Bounded); // Bounded is the default value of consistency level - const [fields, setFields] = useState([ + const [fields, setFields] = useState([ { data_type: DataTypeEnum.Int64, is_primary_key: true, @@ -197,7 +197,7 @@ const CreateCollectionDialog: FC = ({ onCreate }) => { const param: CollectionCreateParam = { ...form, fields: fields.map(v => { - let data: Field = { + let data: CreateField = { name: v.name, description: v.description, is_primary_key: v.is_primary_key, @@ -242,7 +242,7 @@ const CreateCollectionDialog: FC = ({ onCreate }) => { consistency_level: consistencyLevel, }; - await CollectionHttp.createCollection({ + await Collection.createCollection({ ...param, }); diff --git a/client/src/pages/dialogs/CreatePartitionDialog.tsx b/client/src/pages/dialogs/CreatePartitionDialog.tsx index 0029093b..5e3c2c69 100644 --- a/client/src/pages/dialogs/CreatePartitionDialog.tsx +++ b/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 { PartitionHttp } from '@/http'; +import { Partition } from '@/http'; import { PartitionCreateProps } from './Types'; import { PartitionManageParam } from '../partitions/Types'; import { ManageRequestMethods } from '../../types/Common'; @@ -66,7 +66,7 @@ const CreatePartition: FC = ({ type: ManageRequestMethods.CREATE, }; - await PartitionHttp.managePartition(param); + await Partition.managePartition(param); onCreate && onCreate(); handleCloseDialog(); }; diff --git a/client/src/pages/dialogs/DropCollectionDialog.tsx b/client/src/pages/dialogs/DropCollectionDialog.tsx index 72f240e8..ffc17d91 100644 --- a/client/src/pages/dialogs/DropCollectionDialog.tsx +++ b/client/src/pages/dialogs/DropCollectionDialog.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 { CollectionHttp } from '@/http'; +import { Collection } from '@/http'; import { DropCollectionProps } from './Types'; const DropCollectionDialog: FC = props => { @@ -14,7 +14,7 @@ const DropCollectionDialog: FC = props => { const handleDelete = async () => { for (const item of collections) { - await CollectionHttp.deleteCollection(item._name); + await Collection.deleteCollection(item._name); } handleCloseDialog(); diff --git a/client/src/pages/dialogs/DropPartitionDialog.tsx b/client/src/pages/dialogs/DropPartitionDialog.tsx index a6752fba..c8951580 100644 --- a/client/src/pages/dialogs/DropPartitionDialog.tsx +++ b/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 { PartitionHttp } from '@/http'; +import { Partition } from '@/http'; import { PartitionManageParam } from '../partitions/Types'; import { ManageRequestMethods } from '../../types/Common'; import { DropPartitionProps } from './Types'; @@ -21,7 +21,7 @@ const DropPartitionDialog: FC = props => { collectionName, type: ManageRequestMethods.DELETE, }; - await PartitionHttp.managePartition(param); + await Partition.managePartition(param); } handleCloseDialog(); diff --git a/client/src/pages/dialogs/ImportSampleDialog.tsx b/client/src/pages/dialogs/ImportSampleDialog.tsx index d19ee0b2..db7a84ca 100644 --- a/client/src/pages/dialogs/ImportSampleDialog.tsx +++ b/client/src/pages/dialogs/ImportSampleDialog.tsx @@ -6,9 +6,10 @@ import DialogTemplate from '@/components/customDialog/DialogTemplate'; import CustomSelector from '@/components/customSelector/CustomSelector'; import { rootContext } from '@/context'; import { InsertStatusEnum } from './insert/Types'; -import { CollectionHttp, MilvusHttp } from '@/http'; +import { DataService } from '@/http'; import { LoadSampleParam } from './Types'; import icons from '@/components/icons/Icons'; + const DownloadIcon = icons.download; const getStyles = makeStyles((theme: Theme) => { @@ -107,7 +108,7 @@ const ImportSampleDialog: FC<{ collection: string }> = props => { format: format, }; try { - const res = await CollectionHttp.importSample(collectionName, param); + const res = await DataService.importSample(collectionName, param); if (download) { const fileName = format === 'csv' ? csvFileName : jsonFileName; const type = @@ -116,7 +117,7 @@ const ImportSampleDialog: FC<{ collection: string }> = props => { saveAs(blob, fileName); return { result: res.sampleFile, msg: '' }; } - await MilvusHttp.flush(collectionName); + await DataService.flush(collectionName); return { result: true, msg: '' }; } catch (err: any) { const { diff --git a/client/src/pages/dialogs/LoadCollectionDialog.tsx b/client/src/pages/dialogs/LoadCollectionDialog.tsx index 67b6b136..28f9d759 100644 --- a/client/src/pages/dialogs/LoadCollectionDialog.tsx +++ b/client/src/pages/dialogs/LoadCollectionDialog.tsx @@ -8,7 +8,7 @@ import { } from '@material-ui/core'; import { useTranslation } from 'react-i18next'; import { authContext, rootContext } from '@/context'; -import { CollectionHttp, MilvusHttp } from '@/http'; +import { Collection, MilvusService } from '@/http'; import { useFormValidation } from '@/hooks'; import { formatForm, parseJson, getNode } from '@/utils'; import { MILVUS_NODE_TYPE, MILVUS_DEPLOY_MODE } from '@/consts'; @@ -56,7 +56,7 @@ const LoadCollectionDialog = (props: any) => { // check if it is cluster useEffect(() => { async function fetchData() { - const res = await MilvusHttp.getMetrics(); + const res = await MilvusService.getMetrics(); const parsedJson = parseJson(res); // get root cord const rootCoords = getNode( @@ -98,7 +98,7 @@ const LoadCollectionDialog = (props: any) => { } // load collection request - await CollectionHttp.loadCollection(collection, params); + await Collection.loadCollection(collection, params); // callback onLoad && onLoad(); diff --git a/client/src/pages/dialogs/ReleaseCollectionDialog.tsx b/client/src/pages/dialogs/ReleaseCollectionDialog.tsx index a3968338..f8e5a0f5 100644 --- a/client/src/pages/dialogs/ReleaseCollectionDialog.tsx +++ b/client/src/pages/dialogs/ReleaseCollectionDialog.tsx @@ -2,7 +2,7 @@ import { useContext, useState } from 'react'; import { Typography, makeStyles, Theme } from '@material-ui/core'; import { useTranslation } from 'react-i18next'; import DialogTemplate from '@/components/customDialog/DialogTemplate'; -import { CollectionHttp } from '@/http'; +import { Collection } from '@/http'; import { rootContext } from '@/context'; const useStyles = makeStyles((theme: Theme) => ({ @@ -27,7 +27,7 @@ const ReleaseCollectionDialog = (props: any) => { setDisabled(true); try { // release collection - await CollectionHttp.releaseCollection(collection); + await Collection.releaseCollection(collection); // execute callback onRelease && onRelease(); // enable confirm button diff --git a/client/src/pages/dialogs/RenameCollectionDialog.tsx b/client/src/pages/dialogs/RenameCollectionDialog.tsx index d6260b2b..5988e78b 100644 --- a/client/src/pages/dialogs/RenameCollectionDialog.tsx +++ b/client/src/pages/dialogs/RenameCollectionDialog.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { rootContext } from '@/context'; import { formatForm } from '@/utils'; import { useFormValidation } from '@/hooks'; -import { CollectionHttp } from '@/http'; +import { Collection } from '@/http'; import DialogTemplate from '@/components/customDialog/DialogTemplate'; import CustomInput from '@/components/customInput/CustomInput'; import { ITextfieldConfig } from '@/components/customInput/Types'; @@ -42,7 +42,7 @@ const RenameCollectionDialog: FC = props => { }; const handleConfirm = async () => { - await CollectionHttp.renameCollection(collectionName, form); + await Collection.renameCollection(collectionName, form); handleCloseDialog(); cb && cb(); }; diff --git a/client/src/pages/dialogs/Types.ts b/client/src/pages/dialogs/Types.ts index f56a9861..5bfe1617 100644 --- a/client/src/pages/dialogs/Types.ts +++ b/client/src/pages/dialogs/Types.ts @@ -1,8 +1,8 @@ -import { CollectionData } from '../collections/Types'; +import { Collection } from '@/http'; import { PartitionData } from '../partitions/Types'; export interface DropCollectionProps { - collections: CollectionData[]; + collections: Collection[]; onDelete: () => void; } diff --git a/client/src/pages/dialogs/insert/Dialog.tsx b/client/src/pages/dialogs/insert/Dialog.tsx index ff01aeba..69b36ad5 100644 --- a/client/src/pages/dialogs/insert/Dialog.tsx +++ b/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 { PartitionHttp } from '@/http'; +import { Partition } from '@/http'; import { rootContext } from '@/context'; import { combineHeadsAndData } from '@/utils'; import { FILE_MIME_TYPE } from '@/consts'; @@ -130,10 +130,10 @@ const InsertContainer: FC = ({ // every time selected collection value change, partition options and default value will change const fetchPartition = useCallback(async () => { if (collectionValue) { - const partitions = await PartitionHttp.getPartitions(collectionValue); + const partitions = await Partition.getPartitions(collectionValue); const partitionOptions: Option[] = partitions.map(p => ({ - label: p._formatName, - value: p._name, + label: p.partitionName, + value: p.name, })); setPartitionOptions(partitionOptions); @@ -152,8 +152,8 @@ const InsertContainer: FC = ({ } else { const options = partitions .map(p => ({ - label: p._formatName, - value: p._name, + label: p.partitionName, + value: p.name, })) // when there's single selected partition // insert dialog partitions shouldn't selectable @@ -209,8 +209,8 @@ const InsertContainer: FC = ({ () => defaultSelectedCollection === '' ? collections.map(c => ({ - label: c._name, - value: c._name, + label: c.collectionName, + value: c.collectionName, })) : [ { @@ -232,21 +232,20 @@ const InsertContainer: FC = ({ const list = schema && schema.length > 0 ? schema - : collections.find(c => c._name === collectionValue)?._fields; + : collections.find(c => c.collectionName === collectionValue)?.fields; const autoIdFieldName = - list?.find(item => item._isPrimaryKey && item._isAutoId)?._fieldName || - ''; + list?.find(item => item.isPrimaryKey && item.isAutoId)?.name || ''; /** * 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) + .filter(s => !s.isAutoId || !s.isPrimaryKey) .map(s => ({ - label: s._fieldName, - value: s._fieldId, + label: s.name, + value: s.fieldID, })); return { schemaOptions: options, diff --git a/client/src/pages/dialogs/insert/Preview.tsx b/client/src/pages/dialogs/insert/Preview.tsx index 4490e83e..b53bb5ad 100644 --- a/client/src/pages/dialogs/insert/Preview.tsx +++ b/client/src/pages/dialogs/insert/Preview.tsx @@ -116,8 +116,9 @@ const InsertPreview: FC = ({ menuItems={schemaOptions.map(schema => ({ label: schema.label, callback: () => handleTableHeadChange(index, schema.label), - wrapperClass: `${classes.menuItem} ${head === schema.label ? classes.menuActive : '' - }`, + wrapperClass: `${classes.menuItem} ${ + head === schema.label ? classes.menuActive : '' + }`, }))} buttonProps={{ className: classes.menuLabel, diff --git a/client/src/pages/dialogs/insert/Types.ts b/client/src/pages/dialogs/insert/Types.ts index 39cb0f82..0440e277 100644 --- a/client/src/pages/dialogs/insert/Types.ts +++ b/client/src/pages/dialogs/insert/Types.ts @@ -1,14 +1,13 @@ -import { CollectionData } from '../../collections/Types'; import { PartitionView } from '../../partitions/Types'; -import { FieldData } from '../../schema/Types'; +import { FieldHttp, Collection } from '@/http'; import { Option } from '@/components/customSelector/Types'; import { FILE_MIME_TYPE } from '@/consts'; export interface InsertContentProps { // optional on partition page since its collection is fixed - collections?: CollectionData[]; + collections?: Collection[]; // required on partition page since user can't select collection to get schema - schema?: FieldData[]; + schema?: FieldHttp[]; // required on partition page partitions?: PartitionView[]; diff --git a/client/src/pages/overview/Overview.tsx b/client/src/pages/overview/Overview.tsx index 0cf72e9b..f2af4cab 100644 --- a/client/src/pages/overview/Overview.tsx +++ b/client/src/pages/overview/Overview.tsx @@ -16,7 +16,7 @@ import icons from '@/components/icons/Icons'; import { LOADING_STATE, MILVUS_DEPLOY_MODE } from '@/consts'; import { WS_EVENTS, WS_EVENTS_TYPE } from '@server/utils/Const'; import { useNavigationHook } from '@/hooks'; -import { CollectionHttp, MilvusHttp } from '@/http'; +import { Collection, MilvusService } from '@/http'; import { ALL_ROUTER_TYPES } from '@/router/Types'; import { checkLoading, checkIndexBuilding, formatNumber } from '@/utils'; import CollectionCard from './collectionCard/CollectionCard'; @@ -130,14 +130,14 @@ const Overview = () => { const fetchData = useCallback(async () => { setLoading(true); setCollections([]); - const res = (await CollectionHttp.getStatistics()) as statisticsType; - const collections = await CollectionHttp.getCollections(); + const res = (await Collection.getStatistics()) as statisticsType; + const collections = await Collection.getCollections(); const hasLoadingOrBuildingCollection = collections.some( v => checkLoading(v) || checkIndexBuilding(v) ); // if some collection is building index or loading, start pulling data if (hasLoadingOrBuildingCollection) { - MilvusHttp.triggerCron({ + MilvusService.triggerCron({ name: WS_EVENTS.COLLECTION, type: WS_EVENTS_TYPE.START, }); @@ -152,7 +152,7 @@ const Overview = () => { }, [fetchData]); const loadCollections = collections.filter( - c => c._status !== LOADING_STATE.UNLOADED + c => c.status !== LOADING_STATE.UNLOADED ); const onRelease = () => { @@ -237,7 +237,7 @@ const Overview = () => {
{loadCollections.map(collection => ( diff --git a/client/src/pages/overview/collectionCard/CollectionCard.tsx b/client/src/pages/overview/collectionCard/CollectionCard.tsx index f0677518..d8802f60 100644 --- a/client/src/pages/overview/collectionCard/CollectionCard.tsx +++ b/client/src/pages/overview/collectionCard/CollectionCard.tsx @@ -6,7 +6,7 @@ import { Card, CardContent, } from '@material-ui/core'; -import { FC, useContext, useEffect, useState, useCallback } from 'react'; +import { FC, useContext, useEffect, useState } from 'react'; import CustomButton from '@/components/customButton/CustomButton'; import icons from '@/components/icons/Icons'; import Status from '@/components/status/Status'; @@ -17,8 +17,7 @@ import { LOADING_STATE } from '@/consts'; import { rootContext, dataContext } from '@/context'; import ReleaseCollectionDialog from '../../dialogs/ReleaseCollectionDialog'; import { CollectionCardProps } from './Types'; -import { CollectionHttp } from '@/http'; -import { CollectionData } from '@/pages/collections/Types'; +import { Collection } from '@/http'; const useStyles = makeStyles((theme: Theme) => ({ wrapper: { @@ -94,7 +93,7 @@ const CollectionCard: FC = ({ const classes = useStyles(); const { setDialog } = useContext(rootContext); - const { _name: name, _status: status, _loadedPercentage, _replicas } = data; + const { collectionName, status: status, loadedPercentage, replicasInfo } = data; const navigate = useNavigate(); // icons const RightArrowIcon = icons.rightArrow; @@ -110,14 +109,14 @@ const CollectionCard: FC = ({ type: 'custom', params: { component: ( - + ), }, }); }; const onVectorSearchClick = () => { - navigate({ pathname: '/search', search: `?collectionName=${name}` }); + navigate({ pathname: '/search', search: `?collectionName=${collectionName}` }); }; useEffect(() => { @@ -125,8 +124,8 @@ const CollectionCard: FC = ({ try { setLoading(true); if (status === LOADING_STATE.LOADED) { - const data = (await CollectionHttp.count(name)) as CollectionData; - setCount(data._rowCount); + const data = await Collection.count(collectionName); + setCount(data.entityCount); } } catch (e) { } finally { @@ -141,7 +140,6 @@ const CollectionCard: FC = ({ } return () => { - console.log('existing', name, database); exiting = true; }; }, [status, database]); @@ -149,23 +147,23 @@ const CollectionCard: FC = ({ return (
- +
- - {name} + + {collectionName}
    - {_replicas && _replicas.length > 1 ? ( + {replicasInfo && replicasInfo.length > 1 ? (
  • {collectionTrans('replicaNum')}: - {_replicas.length} + {replicasInfo.length}
  • ) : null} diff --git a/client/src/pages/overview/collectionCard/Types.ts b/client/src/pages/overview/collectionCard/Types.ts index 87078e91..af60d95a 100644 --- a/client/src/pages/overview/collectionCard/Types.ts +++ b/client/src/pages/overview/collectionCard/Types.ts @@ -1,7 +1,7 @@ -import { CollectionData } from '../../collections/Types'; +import { Collection } from '@/http'; export interface CollectionCardProps { - data: CollectionData; + data: Collection; onRelease: () => void; wrapperClass?: string; } diff --git a/client/src/pages/partitions/Partitions.tsx b/client/src/pages/partitions/Partitions.tsx index 679db7f7..4a388443 100644 --- a/client/src/pages/partitions/Partitions.tsx +++ b/client/src/pages/partitions/Partitions.tsx @@ -9,9 +9,8 @@ import { usePaginationHook, useInsertDialogHook } from '@/hooks'; import icons from '@/components/icons/Icons'; import CustomToolTip from '@/components/customToolTip/CustomToolTip'; import { rootContext } from '@/context'; -import { CollectionHttp, PartitionHttp, FieldHttp, MilvusHttp } from '@/http'; +import { Collection, Partition, FieldHttp, DataService } from '@/http'; import InsertContainer from '../dialogs/insert/Dialog'; -import { Field } from '../schema/Types'; import { InsertDataParam } from '../collections/Types'; import CreatePartitionDialog from '../dialogs/CreatePartitionDialog'; import DropPartitionDialog from '../dialogs/DropPartitionDialog'; @@ -73,7 +72,7 @@ const Partitions: FC<{ const fetchPartitions = async (collectionName: string) => { try { - const res = await PartitionHttp.getPartitions(collectionName); + const res = await Partition.getPartitions(collectionName); setLoading(false); setPartitions(res); } catch (err) { @@ -82,7 +81,7 @@ const Partitions: FC<{ }; const fetchCollectionDetail = async (name: string) => { - const res = await CollectionHttp.getCollection(name); + const res = await Collection.getCollection(name); return res; }; @@ -99,14 +98,14 @@ const Partitions: FC<{ timer = setTimeout(() => { const searchWords = [search]; const list = search - ? partitions.filter(p => p._formatName.includes(search)) + ? partitions.filter(p => p.partitionName.includes(search)) : partitions; const highlightList = list.map(c => { Object.assign(c, { _nameElement: ( @@ -138,8 +137,8 @@ const Partitions: FC<{ fields_data: fieldData, }; try { - await CollectionHttp.insertData(collectionName, param); - await MilvusHttp.flush(collectionName); + await DataService.insertData(collectionName, param); + await DataService.flush(collectionName); // update partitions fetchPartitions(collectionName); @@ -177,9 +176,7 @@ const Partitions: FC<{ label: btnTrans('insert'), onClick: async () => { const collection = await fetchCollectionDetail(collectionName); - const schema = collection._schema.fields.map( - (f: Field) => new FieldHttp(f) - ); + const schema = collection.schema.fields.map(f => new FieldHttp(f)); handleInsertDialog( selectedPartitions.length === 0 || - selectedPartitions.some(p => p._name === '_default'), - tooltip: selectedPartitions.some(p => p._name === '_default') + selectedPartitions.some(p => p.name === '_default'), + tooltip: selectedPartitions.some(p => p.name === '_default') ? t('deletePartitionError') : '', }, @@ -248,7 +245,7 @@ const Partitions: FC<{ label: t('name'), }, { - id: '_createdTime', + id: 'createdAt', align: 'left', disablePadding: false, label: t('createdTime'), @@ -260,7 +257,7 @@ const Partitions: FC<{ // label: t('status'), // }, { - id: '_rowCount', + id: 'entityCount', align: 'left', disablePadding: false, label: ( diff --git a/client/src/pages/partitions/Types.ts b/client/src/pages/partitions/Types.ts index be1a365b..6ac111e4 100644 --- a/client/src/pages/partitions/Types.ts +++ b/client/src/pages/partitions/Types.ts @@ -3,11 +3,11 @@ import { LOADING_STATE } from '@/consts'; import { ManageRequestMethods } from '../../types/Common'; export interface PartitionData { - _id: string; - _name: string; - _status: LOADING_STATE; - _rowCount: string; - _formatName: string; + id: string; + name: string; + status: LOADING_STATE; + entityCount: string; + partitionName: string; } export interface PartitionView extends PartitionData { diff --git a/client/src/pages/preview/Preview.tsx b/client/src/pages/preview/Preview.tsx index 9ed29cba..6089ff01 100644 --- a/client/src/pages/preview/Preview.tsx +++ b/client/src/pages/preview/Preview.tsx @@ -1,7 +1,7 @@ import { FC, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import AttuGrid from '@/components/grid/Grid'; -import { CollectionHttp, IndexHttp } from '@/http'; +import { Collection, MilvusIndex } from '@/http'; import { usePaginationHook, useSearchResult } from '@/hooks'; import { generateVector } from '@/utils'; import { @@ -45,42 +45,42 @@ const Preview: FC<{ const loadData = async (collectionName: string) => { // get schema list - const collection = await CollectionHttp.getCollection(collectionName); + const collection = await Collection.getCollection(collectionName); - const schemaList = collection._fields!; + const schemaList = collection.fields!; let nameList = schemaList.map(v => ({ name: v.name, - type: v.data_type, + type: v.fieldType, })); // if the dynamic field is enabled, we add $meta column in the grid - if (collection._enableDynamicField) { + if (collection.enableDynamicField) { nameList.push({ name: DYNAMIC_FIELD, type: DataTypeStringEnum.JSON, }); } - const id = schemaList.find(v => v._isPrimaryKey === true); - const primaryKey = id?._fieldName || ''; - const delimiter = id?.data_type === 'Int64' ? '' : '"'; + const id = schemaList.find(v => v.isPrimaryKey === true); + const primaryKey = id?.name || ''; + const delimiter = id?.fieldType === 'Int64' ? '' : '"'; const vectorField = schemaList.find( - v => v.data_type === 'FloatVector' || v.data_type === 'BinaryVector' + v => v.fieldType === 'FloatVector' || v.fieldType === 'BinaryVector' ); - const anns_field = vectorField?._fieldName!; - const dim = Number(vectorField?._dimension); + const anns_field = vectorField?.name!; + const dim = Number(vectorField?.dimension); const vectors = [ - generateVector(vectorField?.data_type === 'FloatVector' ? dim : dim / 8), + generateVector(vectorField?.fieldType === 'FloatVector' ? dim : dim / 8), ]; // get search params - const indexesInfo = await IndexHttp.getIndexInfo(collectionName); - const vectorIndex = indexesInfo.filter(i => i._fieldName === anns_field)[0]; + const indexesInfo = await MilvusIndex.getIndexInfo(collectionName); + const vectorIndex = indexesInfo.filter(i => i.field_name === anns_field)[0]; - const indexType = indexesInfo.length == 0 ? 'FLAT' : vectorIndex._indexType; + const indexType = indexesInfo.length == 0 ? 'FLAT' : vectorIndex.indexType; const indexConfig = INDEX_CONFIG[indexType]; const metric_type = - indexesInfo.length === 0 ? 'L2' : vectorIndex._metricType; + indexesInfo.length === 0 ? 'L2' : vectorIndex.metricType; const searchParamKey = indexConfig.search[0]; const searchParamValue = DEFAULT_SEARCH_PARAM_VALUE_MAP[searchParamKey]; const searchParam = { [searchParamKey]: searchParamValue }; @@ -95,7 +95,7 @@ const Preview: FC<{ try { // first, search random data to get random id - const searchRes = await CollectionHttp.vectorSearchData(collectionName, { + const searchRes = await Collection.vectorSearchData(collectionName, { search_params: { topk: 100, anns_field, @@ -106,7 +106,7 @@ const Preview: FC<{ vectors, output_fields: [primaryKey], vector_type: - DataTypeEnum[vectorField!._fieldType as keyof typeof DataTypeEnum], + DataTypeEnum[vectorField!.fieldType as keyof typeof DataTypeEnum], }); // compose random id list expression @@ -115,7 +115,7 @@ const Preview: FC<{ .join(',')}]`; // query by random id - const res = await CollectionHttp.queryData(collectionName, { + const res = await Collection.queryData(collectionName, { expr: expr, output_fields: [...nameList.map(i => i.name)], }); diff --git a/client/src/pages/query/Query.tsx b/client/src/pages/query/Query.tsx index ea8b1af0..74f623c5 100644 --- a/client/src/pages/query/Query.tsx +++ b/client/src/pages/query/Query.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { saveAs } from 'file-saver'; import { Parser } from '@json2csv/plainjs'; import { rootContext } from '@/context'; -import { CollectionHttp } from '@/http'; +import { Collection, DataService } from '@/http'; import { usePaginationHook, useSearchResult } from '@/hooks'; import EmptyCard from '@/components/cards/EmptyCard'; import icons from '@/components/icons/Icons'; @@ -12,10 +12,8 @@ import CustomButton from '@/components/customButton/CustomButton'; import AttuGrid from '@/components/grid/Grid'; import { ToolBarConfig } from '@/components/grid/Types'; import Filter from '@/components/advancedSearch'; -// import { useTimeTravelHook } from '@/hooks'; import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate'; import CustomToolBar from '@/components/grid/ToolBar'; -// import { CustomDatePicker } from '@/components/customDatePicker/CustomDatePicker'; import { getLabelDisplayedRows } from '../search/Utils'; import { getQueryStyles } from './Styles'; import { DYNAMIC_FIELD, DataTypeStringEnum } from '@/consts'; @@ -68,24 +66,24 @@ const Query: FC<{ }; const getFields = async (collectionName: string) => { - const collection = await CollectionHttp.getCollection(collectionName); - const schemaList = collection._fields; + const collection = await Collection.getCollection(collectionName); + const schemaList = collection.fields; const nameList = schemaList.map(v => ({ name: v.name, - type: v.data_type, + type: v.fieldType, })); // if the dynamic field is enabled, we add $meta column in the grid - if (collection._enableDynamicField) { + if (collection.enableDynamicField) { nameList.push({ name: DYNAMIC_FIELD, type: DataTypeStringEnum.JSON, }); } - const primaryKey = schemaList.find(v => v._isPrimaryKey === true)!; - setPrimaryKey({ value: primaryKey['name'], type: primaryKey['data_type'] }); + const primaryKey = schemaList.find(v => v.isPrimaryKey === true)!; + setPrimaryKey({ value: primaryKey['name'], type: primaryKey['fieldType'] }); setFields(nameList); }; @@ -118,7 +116,7 @@ const Query: FC<{ return; } try { - const res = await CollectionHttp.queryData(collectionName, { + const res = await Collection.queryData(collectionName, { expr: expr, output_fields: fields.map(i => i.name), offset: 0, @@ -140,7 +138,7 @@ const Query: FC<{ }; const handleDelete = async () => { - await CollectionHttp.deleteEntities(collectionName, { + await DataService.deleteEntities(collectionName, { expr: `${primaryKey.value} in [${selectedData .map(v => primaryKey.type === DataTypeStringEnum.VarChar diff --git a/client/src/pages/schema/IndexTypeElement.tsx b/client/src/pages/schema/IndexTypeElement.tsx index caf55de2..7884103a 100644 --- a/client/src/pages/schema/IndexTypeElement.tsx +++ b/client/src/pages/schema/IndexTypeElement.tsx @@ -1,28 +1,30 @@ -import { FC, useContext, useEffect, useMemo, useState } from 'react'; +import { + FC, + useContext, + useEffect, + useMemo, + useState, + MouseEvent, +} from 'react'; import { useTranslation } from 'react-i18next'; import Chip from '@material-ui/core/Chip'; -import { makeStyles, Theme } from '@material-ui/core'; -import { - FieldView, - IndexCreateParam, - IndexExtraParam, - IndexManageParam, -} from './Types'; -import { IndexHttp } from '@/http'; +import { makeStyles, Theme, Tooltip } from '@material-ui/core'; +import { IndexCreateParam, IndexExtraParam, IndexManageParam } from './Types'; +import { MilvusIndex, FieldHttp } from '@/http'; import { rootContext } from '@/context'; import icons from '@/components/icons/Icons'; import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate'; import StatusIcon from '@/components/status/StatusIcon'; import { ChildrenStatusType } from '@/components/status/Types'; import { sleep } from '@/utils'; -import CreateIndex from './Create'; -import { IndexState } from '../../types/Milvus'; +import { IndexState } from '@/types/Milvus'; import { NONE_INDEXABLE_DATA_TYPES } from '@/consts'; +import CreateIndex from './Create'; const useStyles = makeStyles((theme: Theme) => ({ wrapper: { // give fixed width to prevent table cell stretching - width: 150, + width: 'auto', }, item: { paddingLeft: theme.spacing(1), @@ -30,10 +32,7 @@ const useStyles = makeStyles((theme: Theme) => ({ btn: { display: 'flex', alignItems: 'center', - textTransform: 'uppercase', whiteSpace: 'nowrap', - - fontSize: '14px', color: theme.palette.primary.main, '&:hover': { @@ -49,8 +48,7 @@ const useStyles = makeStyles((theme: Theme) => ({ }, }, chip: { - height: '24px', - backgroundColor: '#e9e9ed', + background: `rgba(0, 0, 0, 0.04)`, padding: theme.spacing(0.5), '& .icon': { @@ -60,7 +58,6 @@ const useStyles = makeStyles((theme: Theme) => ({ }, chipLabel: { fontSize: '12px', - lineHeight: '16px', }, addIcon: { width: '20px', @@ -69,10 +66,12 @@ const useStyles = makeStyles((theme: Theme) => ({ })); const IndexTypeElement: FC<{ - data: FieldView; + data: FieldHttp; collectionName: string; + disabled?: boolean; + disabledTooltip?: string; cb: (collectionName: string) => void; -}> = ({ data, collectionName, cb }) => { +}> = ({ data, collectionName, cb, disabled, disabledTooltip }) => { const classes = useStyles(); // set empty string as default status const [status, setStatus] = useState(IndexState.Default); @@ -104,7 +103,7 @@ const IndexTypeElement: FC<{ indexName: string ) => { // get fetch data - const index_descriptions = await IndexHttp.getIndexInfo(collectionName); + const index_descriptions = await MilvusIndex.getIndexInfo(collectionName); const indexDescription = index_descriptions.find( i => i.field_name === fieldName @@ -127,14 +126,14 @@ const IndexTypeElement: FC<{ } }; // prevent delete index trigger fetching index status - if (data._indexType !== '' && status !== IndexState.Delete) { - fetchStatus(collectionName, data._fieldName, data._indexName); + if (data.indexType !== '' && status !== IndexState.Delete) { + fetchStatus(collectionName, data.name, data.indexName!); } return () => { running = false; }; - }, [collectionName, data._indexType, data._fieldName, data._indexName]); + }, [collectionName, data.indexType, data.name, data.indexName]); const requestCreateIndex = async ( params: IndexExtraParam, @@ -142,11 +141,11 @@ const IndexTypeElement: FC<{ ) => { const indexCreateParam: IndexCreateParam = { collection_name: collectionName, - field_name: data._fieldName, + field_name: data.name, index_name, extra_params: params, }; - await IndexHttp.createIndex(indexCreateParam); + await MilvusIndex.createIndex(indexCreateParam); // reset status to default empty string setStatus(IndexState.Default); handleCloseDialog(); @@ -154,7 +153,9 @@ const IndexTypeElement: FC<{ cb(collectionName); }; - const handleCreate = () => { + const handleCreate = (e: MouseEvent) => { + e.stopPropagation(); + setDialog({ open: true, type: 'custom', @@ -162,9 +163,9 @@ const IndexTypeElement: FC<{ component: ( @@ -176,11 +177,11 @@ const IndexTypeElement: FC<{ const requestDeleteIndex = async () => { const indexDeleteParam: IndexManageParam = { collection_name: collectionName, - field_name: data._fieldName, - index_name: data._indexName, + field_name: data.name, + index_name: data.indexName!, }; - await IndexHttp.deleteIndex(indexDeleteParam); + await MilvusIndex.deleteIndex(indexDeleteParam); // use 'delete' as special status for whether fetching index status check setStatus(IndexState.Delete); cb(collectionName); @@ -208,20 +209,20 @@ const IndexTypeElement: FC<{ const generateElement = () => { // only vector type field is able to create index if ( - data._isPrimaryKey || - NONE_INDEXABLE_DATA_TYPES.indexOf(data._fieldType) !== -1 + data.isPrimaryKey || + NONE_INDEXABLE_DATA_TYPES.indexOf(data.fieldType) !== -1 ) { return
    --
    ; } - // _indexType example: FLAT - switch (data._indexType) { + // indexType example: FLAT + switch (data.indexType) { case '': { return (
    @@ -242,17 +243,35 @@ const IndexTypeElement: FC<{ return ; } - /** - * if creating finished, show chip that contains index type - */ - return ( + const chipComp = () => ( } onDelete={handleDelete} + disabled={disabled} + onClick={(e: MouseEvent) => { + e.stopPropagation(); + handleDelete(); + }} + size="small" /> ); + /** + * if creating finished, show chip that contains index type + */ + return disabled ? ( + +
    {chipComp()}
    +
    + ) : ( +
    {chipComp()}
    + ); } } }; diff --git a/client/src/pages/schema/Schema.tsx b/client/src/pages/schema/Schema.tsx index da0e5e17..87862f93 100644 --- a/client/src/pages/schema/Schema.tsx +++ b/client/src/pages/schema/Schema.tsx @@ -5,9 +5,8 @@ import { ColDefinitionsType } from '@/components/grid/Types'; import { useTranslation } from 'react-i18next'; import { usePaginationHook } from '@/hooks'; import icons from '@/components/icons/Icons'; -import { FieldHttp, IndexHttp } from '@/http'; import { formatFieldType } from '@/utils'; -import { FieldView } from './Types'; +import { Collection, FieldHttp } from '@/http'; import IndexTypeElement from './IndexTypeElement'; const useStyles = makeStyles((theme: Theme) => ({ @@ -66,7 +65,7 @@ const Schema: FC<{ const { t: collectionTrans } = useTranslation('collection'); const { t: indexTrans } = useTranslation('index'); - const [fields, setFields] = useState([]); + const [fields, setFields] = useState([]); const [loading, setLoading] = useState(true); const { @@ -81,40 +80,18 @@ const Schema: FC<{ handleGridSort, } = usePaginationHook(fields); - const fetchSchemaListWithIndex = async ( - collectionName: string - ): Promise => { - const indexList = await IndexHttp.getIndexInfo(collectionName); - const schemaList = await FieldHttp.getFields(collectionName); - let fields: FieldView[] = []; - for (const schema of schemaList) { - let field: FieldView = Object.assign(schema, { - _indexParameterPairs: [], - _indexType: '', - _indexName: '', - }); - const index = indexList.find(i => i._fieldName === schema.name); - field._indexParameterPairs = index?._indexParameterPairs || []; - field._indexType = index?._indexType || ''; - field._indexName = index?._indexName || ''; - - fields = [...fields, field]; - } - return fields; - }; - const fetchFields = useCallback( async (collectionName: string) => { const KeyIcon = icons.key; try { - const list = await fetchSchemaListWithIndex(collectionName); - const fields: FieldView[] = list.map(f => + const collection = await Collection.getCollection(collectionName); + const fields = collection.fieldWithIndexInfo.map(f => Object.assign(f, { _fieldNameElement: (
    - {f._fieldName} - {f._isPrimaryKey ? ( + {f.name} + {f.isPrimaryKey ? (
    ) : null} - {f._isAutoId ? ( + {f.autoID ? ( - {f._indexParameterPairs?.length > 0 ? ( - f._indexParameterPairs.map(p => + {f.indexParameterPairs?.length > 0 ? ( + f.indexParameterPairs.map(p => p.value ? ( <> - + {`${p.key}:`} @@ -196,7 +173,7 @@ const Schema: FC<{ align: 'left', disablePadding: true, label: collectionTrans('fieldName'), - sortBy: '_fieldName', + sortBy: 'name', }, { id: '_fieldTypeElement', @@ -205,7 +182,7 @@ const Schema: FC<{ label: collectionTrans('fieldType'), }, { - id: '_indexName', + id: 'indexName', align: 'left', disablePadding: true, label: 'Index name', @@ -215,7 +192,7 @@ const Schema: FC<{ align: 'left', disablePadding: true, label: indexTrans('type'), - sortBy: '_indexType', + sortBy: 'indexType', }, { id: '_indexParamElement', @@ -225,7 +202,7 @@ const Schema: FC<{ notSort: true, }, { - id: '_desc', + id: 'desc', align: 'left', disablePadding: false, label: indexTrans('desc'), @@ -243,7 +220,7 @@ const Schema: FC<{ colDefinitions={colDefinitions} rows={schemaList} rowCount={total} - primaryKey="_fieldId" + primaryKey="fieldID" showHoverStyle={false} page={currentPage} onPageChange={handlePageChange} diff --git a/client/src/pages/schema/Types.ts b/client/src/pages/schema/Types.ts index 92d465ca..8c94a92e 100644 --- a/client/src/pages/schema/Types.ts +++ b/client/src/pages/schema/Types.ts @@ -1,48 +1,9 @@ -import { ReactElement } from 'react'; -import { MetricType, INDEX_TYPES_ENUM, DataTypeStringEnum } from '@/consts'; - -export interface Field { - data_type: DataTypeStringEnum; - fieldID: string; - type_params: { key: string; value: string }[]; - is_primary_key: true; - name: string; - description: string; -} - -export interface FieldData { - _fieldId: string; - _isPrimaryKey: boolean; - is_partition_key: boolean; - _isAutoId: boolean; - _fieldName: string; - _fieldNameElement?: ReactElement; - _fieldType: DataTypeStringEnum; - _dimension: string; - _desc: string; - _maxLength: string; - _maxCapacity: string; - element_type: string; -} - -export interface FieldView extends FieldData, IndexView { - _createIndexDisabled?: boolean; -} +import { INDEX_TYPES_ENUM } from '@/consts'; export interface Index { params: { key: string; value: string }[]; } -export interface IndexView { - _fieldName: string; - _indexType: string; - _indexName: string; - _indexTypeElement?: ReactElement; - _indexParameterPairs: { key: string; value: string }[]; - _indexParamElement?: ReactElement; - _metricType?: MetricType | string; -} - export type IndexType = | INDEX_TYPES_ENUM.FLAT | INDEX_TYPES_ENUM.IVF_FLAT diff --git a/client/src/pages/search/Types.ts b/client/src/pages/search/Types.ts index 4844473f..1b15751a 100644 --- a/client/src/pages/search/Types.ts +++ b/client/src/pages/search/Types.ts @@ -1,10 +1,7 @@ import { Option } from '@/components/customSelector/Types'; import { searchKeywordsType } from '@/consts'; -import { - DataTypeEnum, - DataTypeStringEnum, -} from '../collections/Types'; -import { IndexView } from '../schema/Types'; +import { DataTypeEnum, DataTypeStringEnum } from '@/consts'; +import { MilvusIndex } from '@/http'; export interface SearchParamsProps { // if user created index, pass metric type choosed when creating @@ -37,7 +34,7 @@ export interface FieldOption extends Option { fieldType: DataTypeStringEnum; // used to get metric type, index type and index params for search params // if user doesn't create index, default value is null - indexInfo: IndexView | null; + indexInfo: MilvusIndex | null; // used for check vector input validation dimension: number; } diff --git a/client/src/pages/search/VectorSearch.tsx b/client/src/pages/search/VectorSearch.tsx index ae09c4b8..58535dee 100644 --- a/client/src/pages/search/VectorSearch.tsx +++ b/client/src/pages/search/VectorSearch.tsx @@ -15,7 +15,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 { CollectionHttp, IndexHttp } from '@/http'; +import { Collection, MilvusIndex } from '@/http'; import { parseValue, parseLocationSearch, @@ -36,7 +36,6 @@ import { import { getLabelDisplayedRows } from './Utils'; import SearchParams from './SearchParams'; import { getVectorSearchStyles } from './Styles'; -import { CollectionData } from '../collections/Types'; import { TOP_K_OPTIONS } from './Constants'; import { FieldOption, SearchResultView, VectorSearchParam } from './Types'; @@ -53,7 +52,7 @@ const VectorSearch = () => { // data stored inside the component const [tableLoading, setTableLoading] = useState(false); - const [collections, setCollections] = useState([]); + const [collections, setCollections] = useState([]); const [selectedCollection, setSelectedCollection] = useState(''); const [fieldOptions, setFieldOptions] = useState([]); // fields for advanced filter @@ -95,29 +94,29 @@ const VectorSearch = () => { const collectionOptions: Option[] = useMemo( () => collections.map(c => ({ - label: c._name, - value: c._name, + label: c.collectionName, + value: c.collectionName, })), [collections] ); const outputFields: string[] = useMemo(() => { - const s = collections.find(c => c._name === selectedCollection); + const s = collections.find(c => c.collectionName === selectedCollection); if (!s) { return []; } - const fields = s._fields || []; + const fields = s.fields || []; // vector field can't be output fields const invalidTypes = ['BinaryVector', 'FloatVector']; const nonVectorFields = fields.filter( - field => !invalidTypes.includes(field._fieldType) + field => !invalidTypes.includes(field.fieldType) ); - const _outputFields = nonVectorFields.map(f => f._fieldName); - if (s._enableDynamicField) { + const _outputFields = nonVectorFields.map(f => f.name); + if (s.enableDynamicField) { _outputFields.push(DYNAMIC_FIELD); } return _outputFields; @@ -125,10 +124,10 @@ const VectorSearch = () => { const primaryKeyField = useMemo(() => { const selectedCollectionInfo = collections.find( - c => c._name === selectedCollection + c => c.collectionName === selectedCollection ); - const fields = selectedCollectionInfo?._fields || []; - return fields.find(f => f._isPrimaryKey)?._fieldName; + const fields = selectedCollectionInfo?.fields || []; + return fields.find(f => f.isPrimaryKey)?.name; }, [selectedCollection, collections]); const orderArray = [primaryKeyField, 'id', 'score', ...outputFields]; @@ -178,16 +177,19 @@ const VectorSearch = () => { const index = selectedFieldInfo?.indexInfo; const embeddingType = getEmbeddingType(selectedFieldInfo!.fieldType); const metric = - index?._metricType || DEFAULT_METRIC_VALUE_MAP[embeddingType]; - const indexParams = index?._indexParameterPairs || []; + index?.metricType || DEFAULT_METRIC_VALUE_MAP[embeddingType]; + const indexParams = index?.indexParameterPairs || []; const dim = selectedFieldInfo?.dimension || 0; setSelectedMetricType(metric); return { metricType: metric, - indexType: index?._indexType || getDefaultIndexType(embeddingType), + indexType: index?.indexType || getDefaultIndexType(embeddingType), indexParams, - fieldType: DataTypeEnum[selectedFieldInfo?.fieldType!], + fieldType: + DataTypeEnum[ + selectedFieldInfo?.fieldType! as keyof typeof DataTypeEnum + ], embeddingType, selectedFieldDimension: dim, }; @@ -245,15 +247,16 @@ const VectorSearch = () => { // fetch data const fetchCollections = useCallback(async () => { - const collections = await CollectionHttp.getCollections(); - setCollections(collections.filter(c => c._status === LOADING_STATE.LOADED)); + const collections = await Collection.getCollections(); + setCollections(collections.filter(c => c.status === LOADING_STATE.LOADED)); }, [database]); const fetchFieldsWithIndex = useCallback( - async (collectionName: string, collections: CollectionData[]) => { + async (collectionName: string, collections: Collection[]) => { const fields = - collections.find(c => c._name === collectionName)?._fields || []; - const indexes = await IndexHttp.getIndexInfo(collectionName); + collections.find(c => c.collectionName === collectionName)?.fields || + []; + const indexes = await MilvusIndex.getIndexInfo(collectionName); const { vectorFields, nonVectorFields } = classifyFields(fields); @@ -295,7 +298,7 @@ const VectorSearch = () => { const { collectionName } = parseLocationSearch(location.search); // collection name validation const isNameValid = collections - .map(c => c._name) + .map(c => c.collectionName) .includes(collectionName); isNameValid && setSelectedCollection(collectionName); } @@ -348,7 +351,7 @@ const VectorSearch = () => { setTableLoading(true); try { - const res = await CollectionHttp.vectorSearchData( + const res = await Collection.vectorSearchData( selectedCollection, params ); @@ -390,9 +393,9 @@ const VectorSearch = () => { disabled={collectionOptions.length === 0} value={selectedCollection} onChange={(e: { target: { value: unknown } }) => { - const collection = e.target.value; + const collection = e.target.value as string; - setSelectedCollection(collection as string); + setSelectedCollection(collection); // every time selected collection changed, reset field setSelectedField(''); setSearchResult([]); @@ -407,8 +410,8 @@ const VectorSearch = () => { label={searchTrans('field')} value={selectedField} onChange={(e: { target: { value: unknown } }) => { - const field = e.target.value; - setSelectedField(field as string); + const field = e.target.value as string; + setSelectedField(field); }} /> diff --git a/client/src/pages/segments/Segments.tsx b/client/src/pages/segments/Segments.tsx index 1f532814..e5fa93a4 100644 --- a/client/src/pages/segments/Segments.tsx +++ b/client/src/pages/segments/Segments.tsx @@ -1,6 +1,6 @@ import { useEffect, useState, FC, useContext } from 'react'; import { useTranslation } from 'react-i18next'; -import { CollectionHttp } from '@/http'; +import { Segement } from '@/http'; import { usePaginationHook } from '@/hooks'; import { rootContext } from '@/context'; import AttuGrid from '@/components/grid/Grid'; @@ -24,18 +24,17 @@ const Segments: FC<{ const fetchSegments = async () => { setLoading(true); - const psegments = await CollectionHttp.getPSegments(collectionName); - const qsegments = await CollectionHttp.getQSegments(collectionName); + const psegments = (await Segement.getPSegments(collectionName)) || {}; + const qsegments = (await Segement.getQSegments(collectionName)) || {}; - const combinedArray = psegments.infos.map((p: any) => { - const q = qsegments.infos.find((q: any) => q.segmentID === p.segmentID); + const combinedArray = psegments.infos.map(p => { + const q = qsegments.infos.find(q => q.segmentID === p.segmentID)! as any; return { ...p, - ...(q && - Object.keys(q).reduce((acc: any, key) => { - acc[`q_${key}`] = q[key]; - return acc; - }, {})), + ...Object.keys(q).reduce((acc, key) => { + acc[`q_${key}`] = q[key]; + return acc; + }, {} as any), }; }); diff --git a/client/src/pages/system/SystemView.tsx b/client/src/pages/system/SystemView.tsx index 10d393ce..c1e4eb12 100644 --- a/client/src/pages/system/SystemView.tsx +++ b/client/src/pages/system/SystemView.tsx @@ -4,7 +4,7 @@ import { makeStyles, Theme } from '@material-ui/core'; import clsx from 'clsx'; import { useNavigationHook, useInterval } from '@/hooks'; import { ALL_ROUTER_TYPES } from '@/router/Types'; -import { MilvusHttp } from '@/http'; +import { MilvusService } from '@/http'; import { parseJson } from '@/utils'; import Topo from './Topology'; import NodeListView from './NodeListView'; @@ -83,14 +83,14 @@ const SystemView: any = () => { useInterval(async () => { if (!selectedCord) { - const res = await MilvusHttp.getMetrics(); + const res = await MilvusService.getMetrics(); setData(parseJson(res)); } }, INTERVAL); useEffect(() => { async function fetchData() { - const res = await MilvusHttp.getMetrics(); + const res = await MilvusService.getMetrics(); setData(parseJson(res)); } fetchData(); diff --git a/client/src/pages/systemHealthy/SystemHealthyView.tsx b/client/src/pages/systemHealthy/SystemHealthyView.tsx index 2fc349a0..b37e3098 100644 --- a/client/src/pages/systemHealthy/SystemHealthyView.tsx +++ b/client/src/pages/systemHealthy/SystemHealthyView.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react'; import { makeStyles, Theme } from '@material-ui/core'; import { useTranslation } from 'react-i18next'; import { useNavigationHook, useInterval } from '@/hooks'; -import { PrometheusHttp } from '@/http'; +import { PrometheusService } from '@/http'; import { ALL_ROUTER_TYPES } from '@/router/Types'; import { LAST_TIME_HEALTHY_THRESHOLD_CPU, @@ -109,7 +109,7 @@ const SystemHealthyView = () => { const updateData = async () => { const curT = new Date().getTime(); - const result = (await PrometheusHttp.getHealthyData({ + const result = (await PrometheusService.getHealthyData({ start: curT - timeRange.value, end: curT, step: timeRange.step, diff --git a/client/src/pages/user/Roles.tsx b/client/src/pages/user/Roles.tsx index 58340edd..c0c74bde 100644 --- a/client/src/pages/user/Roles.tsx +++ b/client/src/pages/user/Roles.tsx @@ -1,7 +1,7 @@ import { useContext, useEffect, useState } from 'react'; import { makeStyles, Theme, Chip } from '@material-ui/core'; import { useTranslation } from 'react-i18next'; -import { UserHttp } from '@/http'; +import { User } from '@/http'; import { rootContext, dataContext } from '@/context'; import { useNavigationHook } from '@/hooks'; import AttuGrid from '@/components/grid/Grid'; @@ -35,7 +35,7 @@ const Roles = () => { const { t: dialogTrans } = useTranslation('dialog'); const fetchRoles = async () => { - const roles = (await UserHttp.getRoles()) as RolesType; + const roles = (await User.getRoles()) as RolesType; setSelectedRole([]); setRoles( @@ -81,7 +81,7 @@ const Roles = () => { roleName: role.name, force, }; - await UserHttp.deleteRole(param); + await User.deleteRole(param); } openSnackBar(successTrans('delete', { name: userTrans('role') })); diff --git a/client/src/pages/user/UpdateRoleDialog.tsx b/client/src/pages/user/UpdateRoleDialog.tsx index 26a4c4c7..6157e397 100644 --- a/client/src/pages/user/UpdateRoleDialog.tsx +++ b/client/src/pages/user/UpdateRoleDialog.tsx @@ -6,7 +6,7 @@ import CustomInput from '@/components/customInput/CustomInput'; import { ITextfieldConfig } from '@/components/customInput/Types'; import { useFormValidation } from '@/hooks'; import { formatForm } from '@/utils'; -import { UserHttp } from '@/http'; +import { User } from '@/http'; import { CreateRoleProps, CreateRoleParams, @@ -50,7 +50,7 @@ const UpdateRoleDialog: FC = ({ }); const fetchRBAC = async () => { - const rbacOptions = await UserHttp.getRBAC(); + const rbacOptions = await User.getRBAC(); setRbacOptions(rbacOptions); }; @@ -108,10 +108,10 @@ const UpdateRoleDialog: FC = ({ const handleCreateRole = async () => { if (!isEditing) { - await UserHttp.createRole(form); + await User.createRole(form); } - await UserHttp.updateRolePrivileges(form); + await User.updateRolePrivileges(form); onUpdate({ data: form, isEditing: isEditing }); }; diff --git a/client/src/pages/user/UpdateUserRole.tsx b/client/src/pages/user/UpdateUserRole.tsx index fea2c8dd..88cc41ed 100644 --- a/client/src/pages/user/UpdateUserRole.tsx +++ b/client/src/pages/user/UpdateUserRole.tsx @@ -9,7 +9,7 @@ import { FC, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import DialogTemplate from '@/components/customDialog/DialogTemplate'; import { UpdateUserRoleProps, UpdateUserRoleParams } from './Types'; -import { UserHttp } from '@/http'; +import { User } from '@/http'; const useStyles = makeStyles((theme: Theme) => ({ input: { @@ -41,12 +41,12 @@ const UpdateUserRole: FC = ({ const classes = useStyles(); const handleUpdate = async () => { - await UserHttp.updateUserRole(form); + await User.updateUserRole(form); onUpdate(form); }; const fetchAllRoles = async () => { - const roles = await UserHttp.getRoles(); + const roles = await User.getRoles(); setRoleOptions(roles.results.map((r: any) => r.role.name)); }; diff --git a/client/src/pages/user/User.tsx b/client/src/pages/user/User.tsx index 29b1adeb..23a12c3c 100644 --- a/client/src/pages/user/User.tsx +++ b/client/src/pages/user/User.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { makeStyles, Theme } from '@material-ui/core'; import { useTranslation } from 'react-i18next'; -import { UserHttp } from '@/http'; +import { User } from '@/http'; import AttuGrid from '@/components/grid/Grid'; import { ColDefinitionsType, ToolBarConfig } from '@/components/grid/Types'; import { @@ -39,8 +39,8 @@ const Users = () => { const { t: dialogTrans } = useTranslation('dialog'); const fetchUsers = async () => { - const res = await UserHttp.getUsers(); - const roles = await UserHttp.getRoles(); + const res = await User.getUsers(); + const roles = await User.getRoles(); setUsers( res.usernames.map((v: string) => { @@ -60,9 +60,9 @@ const Users = () => { }; const handleCreate = async (data: CreateUserParams) => { - await UserHttp.createUser(data); + await User.createUser(data); // assign user role if - await UserHttp.updateUserRole({ + await User.updateUserRole({ username: data.username, roles: data.roles, }); @@ -81,7 +81,7 @@ const Users = () => { }; const handleUpdate = async (data: UpdateUserParams) => { - await UserHttp.updateUser(data); + await User.updateUser(data); fetchUsers(); openSnackBar(successTrans('update', { name: userTrans('user') })); handleCloseDialog(); @@ -92,7 +92,7 @@ const Users = () => { const param: DeleteUserParams = { username: user.name, }; - await UserHttp.deleteUser(param); + await User.deleteUser(param); } openSnackBar(successTrans('delete', { name: userTrans('user') })); @@ -104,7 +104,7 @@ const Users = () => { { label: userTrans('user'), onClick: async () => { - const roles = await UserHttp.getRoles(); + const roles = await User.getRoles(); setDialog({ open: true, type: 'custom', diff --git a/client/src/types/Milvus.ts b/client/src/types/Milvus.ts index f3133a55..1393f208 100644 --- a/client/src/types/Milvus.ts +++ b/client/src/types/Milvus.ts @@ -9,19 +9,3 @@ export enum IndexState { Default = '', Delete = 'Delete', } - -export type IndexDescription = { - fields_name: string; - index_name: string; - indexed_rows: string | number; - state: IndexState; -}; - -export interface DescribeIndexResponse { - index_descriptions: IndexDescription[]; -} - -export enum ShowCollectionsType { - All = 0, - InMemory = 1, -} diff --git a/client/src/types/SearchTypes.ts b/client/src/types/SearchTypes.ts index 8e4f8da0..a5c09674 100644 --- a/client/src/types/SearchTypes.ts +++ b/client/src/types/SearchTypes.ts @@ -1,6 +1,6 @@ import { Option } from '@/components/customSelector/Types'; import { searchKeywordsType, DataTypeEnum, DataTypeStringEnum } from '@/consts'; -import { IndexView } from '@/pages/schema/Types'; +import { MilvusIndex } from '@/http'; export interface SearchParamsProps { // if user created index, pass metric type choosed when creating @@ -32,7 +32,7 @@ export interface FieldOption extends Option { fieldType: DataTypeStringEnum; // used to get metric type, index type and index params for search params // if user doesn't create index, default value is null - indexInfo: IndexView | null; + indexInfo: MilvusIndex | null; // used for check vector input validation dimension: number; } diff --git a/client/src/utils/Format.ts b/client/src/utils/Format.ts index 256d2726..141720ba 100644 --- a/client/src/utils/Format.ts +++ b/client/src/utils/Format.ts @@ -4,8 +4,8 @@ import { DEFAULT_PROMETHEUS_PORT, DataTypeEnum, } from '@/consts'; -import { CreateFieldType, Field } from '@/pages/collections/Types'; -import { FieldView } from '@/pages/schema/Types'; +import { CreateFieldType, CreateField } from '@/pages/collections/Types'; +import { FieldHttp } from '@/http'; /** * transform large capacity to capacity in b. @@ -125,7 +125,7 @@ export const checkIsBinarySubstructure = (metricLabel: string): boolean => { return metricLabel === 'Superstructure' || metricLabel === 'Substructure'; }; -export const getCreateFieldType = (config: Field): CreateFieldType => { +export const getCreateFieldType = (config: CreateField): CreateFieldType => { if (config.is_primary_key) { return 'primaryKey'; } @@ -232,17 +232,16 @@ export const formatUtcToMilvus = (bigNumber: number) => { * @param bigNumber * @returns */ -export const formatFieldType = (field: FieldView) => { - const { _fieldType, element_type, _maxLength, _maxCapacity, _dimension } = - field; +export const formatFieldType = (field: FieldHttp) => { + const { fieldType, element_type, maxLength, maxCapacity, dimension } = field; const elementType = element_type !== 'None' - ? `<${element_type}${_maxLength ? `(${_maxLength})` : ''}>` + ? `<${element_type}${maxLength ? `(${maxLength})` : ''}>` : ''; - const maxCapacity = _maxCapacity ? `[${_maxCapacity}]` : ''; - const dimension = _dimension ? `(${_dimension})` : ''; - const maxLength = _fieldType === 'VarChar' ? `(${_maxLength})` : ''; + const maxCap = maxCapacity ? `[${maxCapacity}]` : ''; + const dim = dimension ? `(${dimension})` : ''; + const maxLn = fieldType === 'VarChar' ? `(${maxLength})` : ''; - return `${_fieldType}${elementType}${maxCapacity}${dimension}${maxLength}`; + return `${fieldType}${elementType}${maxCap}${dim}${maxLn}`; }; diff --git a/client/src/utils/Validation.ts b/client/src/utils/Validation.ts index 9207e08e..a1f9bb54 100644 --- a/client/src/utils/Validation.ts +++ b/client/src/utils/Validation.ts @@ -1,5 +1,6 @@ import { ChildrenStatusType } from '@/components/status/Types'; import { MetricType, METRIC_TYPES_VALUES } from '@/consts'; +import { Collection } from '@/http'; export type ValidType = | 'email' @@ -248,8 +249,8 @@ export const getCheckResult = (param: ICheckMapParam): boolean => { /** * Check collection is loading or not */ -export const checkLoading = (v: any): boolean => - v._loadedPercentage !== '-1' && v._loadedPercentage !== '100'; +export const checkLoading = (v: Collection): boolean => + v.loadedPercentage !== '-1' && v.loadedPercentage !== '100'; /** * Check collection is index building or not. diff --git a/client/src/utils/search.ts b/client/src/utils/search.ts index 9419c9c9..2b7a94d3 100644 --- a/client/src/utils/search.ts +++ b/client/src/utils/search.ts @@ -1,7 +1,8 @@ import { Field } from '../components/advancedSearch/Types'; -import { FieldData, IndexType, IndexView } from '../pages/schema/Types'; +import { IndexType } from '../pages/schema/Types'; import { INDEX_TYPES_ENUM, DataTypeEnum, DataTypeStringEnum } from '@/consts'; import { FieldOption } from '../types/SearchTypes'; +import { FieldHttp, MilvusIndex } from '@/http'; /** * function to get EmbeddingType @@ -35,15 +36,15 @@ export const getDefaultIndexType = (embeddingType: DataTypeEnum): IndexType => { * funtion to divide fields into two categories: vector or nonVector */ export const classifyFields = ( - fields: FieldData[] -): { vectorFields: FieldData[]; nonVectorFields: FieldData[] } => { + fields: FieldHttp[] +): { vectorFields: FieldHttp[]; nonVectorFields: FieldHttp[] } => { const vectorTypes: DataTypeStringEnum[] = [ DataTypeStringEnum.BinaryVector, DataTypeStringEnum.FloatVector, ]; return fields.reduce( (result, cur) => { - const changedFieldType = vectorTypes.includes(cur._fieldType) + const changedFieldType = vectorTypes.includes(cur.fieldType) ? 'vectorFields' : 'nonVectorFields'; @@ -51,34 +52,34 @@ export const classifyFields = ( return result; }, - { vectorFields: [] as FieldData[], nonVectorFields: [] as FieldData[] } + { vectorFields: [] as FieldHttp[], nonVectorFields: [] as FieldHttp[] } ); }; export const getVectorFieldOptions = ( - fields: FieldData[], - indexes: IndexView[] + fields: FieldHttp[], + indexes: MilvusIndex[] ): FieldOption[] => { const options: FieldOption[] = fields.map(f => { - const embeddingType = getEmbeddingType(f._fieldType); + const embeddingType = getEmbeddingType(f.fieldType); const defaultIndex = getDefaultIndexType(embeddingType); - const index = indexes.find(i => i._fieldName === f._fieldName); + const index = indexes.find(i => i.field_name === f.name); return { - label: `${f._fieldName} (${index?._indexType || defaultIndex})`, - value: f._fieldName, - fieldType: f._fieldType, + label: `${f.name} (${index?.indexType || defaultIndex})`, + value: f.name, + fieldType: f.fieldType, indexInfo: index || null, - dimension: Number(f._dimension), + dimension: Number(f.dimension), }; }); return options; }; -export const getNonVectorFieldsForFilter = (fields: FieldData[]): Field[] => { +export const getNonVectorFieldsForFilter = (fields: FieldHttp[]): Field[] => { return fields.map(f => ({ - name: f._fieldName, - type: f._fieldType, + name: f.name, + type: f.fieldType, })); }; diff --git a/server/src/collections/collections.controller.ts b/server/src/collections/collections.controller.ts index 4445758c..d3c1c1c3 100644 --- a/server/src/collections/collections.controller.ts +++ b/server/src/collections/collections.controller.ts @@ -155,10 +155,10 @@ export class CollectionController { async describeCollection(req: Request, res: Response, next: NextFunction) { const name = req.params?.name; try { - const result = await this.collectionsService.describeCollection({ - collection_name: name, + const result = await this.collectionsService.getAllCollections({ + data: [{ name }], }); - res.send(result); + res.send(result[0]); } catch (error) { next(error); } diff --git a/server/src/collections/collections.service.ts b/server/src/collections/collections.service.ts index aab1fc69..9608c1c1 100644 --- a/server/src/collections/collections.service.ts +++ b/server/src/collections/collections.service.ts @@ -26,6 +26,7 @@ import { import { Parser } from '@json2csv/plainjs'; import { throwErrorFromSDK, findKeyValue, genRows, ROW_COUNT } from '../utils'; import { QueryDto, ImportSampleDto, GetReplicasDto } from './dto'; +import { CollectionData } from '../types'; export class CollectionsService { constructor(private milvusService: MilvusService) {} @@ -166,49 +167,49 @@ export class CollectionsService { * Get all collections meta data * @returns {id:string, collection_name:string, schema:Field[], autoID:boolean, rowCount: string, consistency_level:string} */ - async getAllCollections() { - const data: any = []; - const res = await this.getCollections(); + async getAllCollections(collections?: { + data: { name: string }[]; + }): Promise { + const data: CollectionData[] = []; + const res = collections || (await this.getCollections()); const loadedCollections = await this.getCollections({ type: ShowCollectionsType.Loaded, }); if (res.data.length > 0) { for (const item of res.data) { const { name } = item; + + // get collection schema and properties const collectionInfo = await this.describeCollection({ collection_name: name, }); - let count: number | string; - + // get collection statistic data const collectionStatisticsRes = await this.getCollectionStatistics({ collection_name: name, }); - count = collectionStatisticsRes.data.row_count; - // try { - // const countRes = await this.count({ - // collection_name: name, - // }); - // count = countRes.data; - // } catch (error) { - // } + // get index info for collections const indexRes = await this.getIndexInfo({ collection_name: item.name, }); + // extract autoID const autoID = collectionInfo.schema.fields.find( v => v.is_primary_key === true )?.autoID; + // if it is loaded const loadCollection = loadedCollections.data.find( v => v.name === name ); + // loading info const loadedPercentage = !loadCollection ? '-1' : loadCollection.loadedPercentage; + // get replica info let replicas; try { replicas = loadCollection @@ -226,18 +227,18 @@ export class CollectionsService { schema: collectionInfo.schema, description: collectionInfo.schema.description, autoID, - rowCount: count, + rowCount: collectionStatisticsRes.data.row_count, id: collectionInfo.collectionID, loadedPercentage, createdTime: parseInt(collectionInfo.created_utc_timestamp, 10), - index_descriptions: indexRes, + index_descriptions: indexRes.index_descriptions, consistency_level: collectionInfo.consistency_level, replicas: replicas && replicas.replicas, }); } } // add default sort - Descending order - data.sort((a: any, b: any) => b.createdTime - a.createdTime); + data.sort((a, b) => b.createdTime - a.createdTime); return data; } diff --git a/server/src/types/collections.type.ts b/server/src/types/collections.type.ts new file mode 100644 index 00000000..2f36dc72 --- /dev/null +++ b/server/src/types/collections.type.ts @@ -0,0 +1,20 @@ +import { + IndexDescription, + CollectionSchema, + ReplicaInfo, +} from '@zilliz/milvus2-sdk-node'; + +export interface CollectionData { + collection_name: string; + schema: CollectionSchema; + rowCount: number | string; + createdTime: number; + aliases: string[]; + description: string; + autoID: boolean; + id: string; + loadedPercentage: string; + index_descriptions: IndexDescription[]; + consistency_level: string; + replicas: ReplicaInfo[]; +} diff --git a/server/src/types/index.ts b/server/src/types/index.ts new file mode 100644 index 00000000..a82c84bc --- /dev/null +++ b/server/src/types/index.ts @@ -0,0 +1,14 @@ +export { + IndexDescription, + CollectionSchema, + ReplicaInfo, + FieldSchema, + KeyValuePair, + ShowCollectionsType, + GetQuerySegmentInfoResponse, + QuerySegmentInfo, + GePersistentSegmentInfoResponse, + PersistentSegmentInfo, +} from '@zilliz/milvus2-sdk-node'; + +export * from './collections.type';