Browse Source

Merge pull request #25 from Tumao727/feature/partition-list

add partition table
ryjiang 4 years ago
parent
commit
ba350ec0e8

+ 1 - 0
client/src/components/customTabList/CustomTabList.tsx

@@ -23,6 +23,7 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
   tabPanel: {
     flexGrow: 1,
+    marginTop: theme.spacing(2),
   },
 }));
 

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

@@ -20,7 +20,7 @@ const useStyles = makeStyles(theme => ({
     // minHeight: '29vh',
     width: '100%',
     flexGrow: 1,
-    flexBasis: 0,
+    // flexBasis: 0,
 
     // change scrollbar style
     '&::-webkit-scrollbar': {

+ 7 - 7
client/src/components/layout/Layout.tsx

@@ -55,13 +55,13 @@ const Layout = (props: any) => {
       label: t('collection'),
       onClick: () => history.push('/collections'),
     },
-    {
-      icon: icons.navConsole,
-      label: t('console'),
-      onClick: () => history.push('/console'),
-      iconActiveClass: classes.activeConsole,
-      iconNormalClass: classes.normalConsole,
-    },
+    // {
+    //   icon: icons.navConsole,
+    //   label: t('console'),
+    //   onClick: () => history.push('/console'),
+    //   iconActiveClass: classes.activeConsole,
+    //   iconNormalClass: classes.normalConsole,
+    // },
   ];
 
   return (

+ 4 - 8
client/src/hooks/Navigation.ts

@@ -1,6 +1,6 @@
 import { useContext, useEffect } from 'react';
 import { useTranslation } from 'react-i18next';
-import { useParams } from 'react-router';
+// import { useParams } from 'react-router-dom';
 import { navContext } from '../context/Navigation';
 import { ALL_ROUTER_TYPES, NavInfo } from '../router/Types';
 
@@ -12,11 +12,7 @@ export const useNavigationHook = (
 ) => {
   const { t } = useTranslation('nav');
   const { setNavInfo } = useContext(navContext);
-
-  const { collectionId = '' } =
-    useParams<{
-      collectionId?: string;
-    }>();
+  const { collectionName } = extraParam || { collectionName: '' };
 
   useEffect(() => {
     switch (type) {
@@ -38,7 +34,7 @@ export const useNavigationHook = (
       }
       case ALL_ROUTER_TYPES.COLLECTION_DETAIL: {
         const navInfo: NavInfo = {
-          navTitle: extraParam?.collectionName as string,
+          navTitle: collectionName,
           backPath: '/collections',
         };
         setNavInfo(navInfo);
@@ -55,5 +51,5 @@ export const useNavigationHook = (
       default:
         break;
     }
-  }, [type, extraParam, t, setNavInfo, collectionId]);
+  }, [type, t, setNavInfo, collectionName]);
 };

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

@@ -27,7 +27,7 @@ export default class BaseModel {
     };
 
     const res = await http(httpConfig);
-    let list = res.data.data.data || [];
+    let list = res.data.data || [];
     if (!Array.isArray(list)) {
       return list;
     }

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

@@ -0,0 +1,39 @@
+import { StatusEnum } from '../components/status/Types';
+import { PartitionView } from '../pages/partitions/Types';
+import { formatNumber } from '../utils/Common';
+import BaseModel from './BaseModel';
+
+export class PartitionHttp extends BaseModel implements PartitionView {
+  id!: string;
+  name!: string;
+  rowCount!: string;
+  status!: StatusEnum;
+
+  constructor(props: {}) {
+    super(props);
+    Object.assign(this, props);
+  }
+
+  static getPartitions(collectionName: string): Promise<PartitionHttp[]> {
+    const path = '/partitions';
+
+    return super.findAll({ path, params: { collection_name: collectionName } });
+  }
+
+  get _id() {
+    return this.id;
+  }
+
+  get _name() {
+    return this.name === '_default' ? 'Default partition' : this.name;
+  }
+
+  get _rowCount() {
+    return formatNumber(Number(this.rowCount));
+  }
+
+  get _status() {
+    // @TODO replace mock data
+    return StatusEnum.unloaded;
+  }
+}

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

@@ -43,6 +43,10 @@ const collectionTrans = {
   // delete dialog
   deleteWarning:
     'You are trying to delete a collection with data. This action cannot be undone.',
+
+  // collection tabs
+  partitionTab: 'Partitions',
+  structureTab: 'Structure',
 };
 
 export default collectionTrans;

+ 12 - 0
client/src/i18n/cn/partition.ts

@@ -0,0 +1,12 @@
+const partitionTrans = {
+  create: 'Create Partition',
+  delete: 'Delete partition',
+
+  id: 'ID',
+  name: 'Name',
+  status: 'Status',
+  rowCount: 'Row Count',
+  tooltip: 'data in one row',
+};
+
+export default partitionTrans;

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

@@ -43,6 +43,10 @@ const collectionTrans = {
   // delete dialog
   deleteWarning:
     'You are trying to delete a collection with data. This action cannot be undone.',
+
+  // collection tabs
+  partitionTab: 'Partitions',
+  structureTab: 'Structure',
 };
 
 export default collectionTrans;

+ 12 - 0
client/src/i18n/en/partition.ts

@@ -0,0 +1,12 @@
+const partitionTrans = {
+  create: 'Create Partition',
+  delete: 'Delete partition',
+
+  id: 'ID',
+  name: 'Name',
+  status: 'Status',
+  rowCount: 'Row Count',
+  tooltip: 'data in one row',
+};
+
+export default partitionTrans;

+ 4 - 0
client/src/i18n/index.ts

@@ -15,6 +15,8 @@ import collectionCn from './cn/collection';
 import collectionEn from './en/collection';
 import dialogCn from './cn/dialog';
 import dialogEn from './en/dialog';
+import partitionCn from './cn/partition';
+import partitionEn from './en/partition';
 import successEn from './en/success';
 import successCn from './cn/success';
 
@@ -27,6 +29,7 @@ export const resources = {
     overview: overviewCn,
     collection: collectionCn,
     dialog: dialogCn,
+    partition: partitionCn,
     success: successCn,
   },
   en: {
@@ -37,6 +40,7 @@ export const resources = {
     overview: overviewEn,
     collection: collectionEn,
     dialog: dialogEn,
+    partition: partitionEn,
     success: successEn,
   },
 };

+ 87 - 0
client/src/pages/collections/Collection.tsx

@@ -0,0 +1,87 @@
+import { useTranslation } from 'react-i18next';
+import { useNavigationHook } from '../../hooks/Navigation';
+import { ALL_ROUTER_TYPES } from '../../router/Types';
+import CustomTabList from '../../components/customTabList/CustomTabList';
+import { ITab } from '../../components/customTabList/Types';
+import Partitions from '../partitions/partitions';
+import { useHistory, useLocation, useParams } from 'react-router-dom';
+import { useEffect, useMemo, useState } from 'react';
+import { PartitionView } from '../partitions/Types';
+import { parseLocationSearch } from '../../utils/Format';
+import Status from '../../components/status/Status';
+import { PartitionHttp } from '../../http/Partition';
+
+enum TAB_EMUM {
+  'partition',
+  'structure',
+}
+
+const Collection = () => {
+  const { collectionName = '' } =
+    useParams<{
+      collectionName: string;
+    }>();
+
+  useNavigationHook(ALL_ROUTER_TYPES.COLLECTION_DETAIL, { collectionName });
+  const [partitions, setPartitions] = useState<PartitionView[]>([]);
+  const [loading, setLoading] = useState<boolean>(true);
+
+  const history = useHistory();
+  const location = useLocation();
+
+  const { t } = useTranslation('collection');
+
+  const activeTabIndex = useMemo(() => {
+    const { activeIndex } = location.search
+      ? parseLocationSearch(location.search)
+      : { activeIndex: TAB_EMUM.partition };
+    return Number(activeIndex);
+  }, [location]);
+
+  const handleTabChange = (activeIndex: number) => {
+    const path = location.pathname;
+    history.push(`${path}?activeIndex=${activeIndex}`);
+
+    // fetch data
+    if (activeIndex === TAB_EMUM.partition) {
+      fetchPartitions(collectionName);
+    }
+  };
+
+  const fetchPartitions = async (collectionName: string) => {
+    const res = await PartitionHttp.getPartitions(collectionName);
+
+    const partitons: PartitionView[] = res.map(p =>
+      Object.assign(p, { _statusElement: <Status status={p._status} /> })
+    );
+    setLoading(false);
+    setPartitions(partitons);
+  };
+
+  useEffect(() => {
+    fetchPartitions(collectionName);
+  }, [collectionName]);
+
+  const tabs: ITab[] = [
+    {
+      label: t('partitionTab'),
+      component: <Partitions data={partitions} loading={loading} />,
+    },
+    {
+      label: t('structureTab'),
+      component: <section>structure section</section>,
+    },
+  ];
+
+  return (
+    <section className="page-wrapper">
+      <CustomTabList
+        tabs={tabs}
+        activeIndex={activeTabIndex}
+        handleTabChange={handleTabChange}
+      />
+    </section>
+  );
+};
+
+export default Collection;

+ 14 - 6
client/src/pages/collections/Collections.tsx

@@ -66,10 +66,14 @@ const Collections = () => {
   useEffect(() => {
     const mockCollections: CollectionView[] = [
       {
-        name: 'collection',
+        name: 'collection_1',
         nameElement: (
-          <Link href="/overview" underline="always" color="textPrimary">
-            collection
+          <Link
+            href="/collection/collection_1"
+            underline="always"
+            color="textPrimary"
+          >
+            collection_1
           </Link>
         ),
         id: 'c1',
@@ -80,10 +84,14 @@ const Collections = () => {
         indexCreatingElement: <StatusIcon type="creating" />,
       },
       {
-        name: 'collection 2',
+        name: 'collection_2',
         nameElement: (
-          <Link href="/overview" underline="always" color="textPrimary">
-            collection 2
+          <Link
+            href="/collection/collection_2"
+            underline="always"
+            color="textPrimary"
+          >
+            collection_2
           </Link>
         ),
         id: 'c2',

+ 1 - 1
client/src/pages/overview/Overview.tsx

@@ -45,7 +45,7 @@ const Overview = () => {
       },
       {
         label: t('data'),
-        value: t('rows', { number: formatNumber(209379100) }),
+        value: t('rows', { number: formatNumber(209379100) }) as string,
         valueColor: '#0689d2',
       },
     ],

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

@@ -0,0 +1,10 @@
+import { ReactElement } from 'react';
+import { StatusEnum } from '../../components/status/Types';
+
+export interface PartitionView {
+  _id: string;
+  _name: string;
+  _status: StatusEnum;
+  _statusElement?: ReactElement;
+  _rowCount: string;
+}

+ 118 - 0
client/src/pages/partitions/partitions.tsx

@@ -0,0 +1,118 @@
+import { makeStyles, Theme } from '@material-ui/core';
+import { FC, useState } from 'react';
+import { PartitionView } from './Types';
+import MilvusGrid from '../../components/grid';
+import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
+import { useTranslation } from 'react-i18next';
+import { usePaginationHook } from '../../hooks/Pagination';
+import icons from '../../components/icons/Icons';
+import CustomToolTip from '../../components/customToolTip/CustomToolTip';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {},
+  icon: {
+    fontSize: '20px',
+    marginLeft: theme.spacing(0.5),
+  },
+}));
+
+const Partitions: FC<{ data: PartitionView[]; loading: boolean }> = ({
+  data,
+  loading,
+}) => {
+  const classes = useStyles();
+  const { t } = useTranslation('partition');
+  const InfoIcon = icons.info;
+
+  const {
+    pageSize,
+    currentPage,
+    handleCurrentPage,
+    // offset,
+    total,
+    // setTotal
+  } = usePaginationHook();
+
+  const [selectedPartitions, setSelectedPartitions] = useState<PartitionView[]>(
+    []
+  );
+
+  const toolbarConfigs: ToolBarConfig[] = [
+    {
+      label: t('create'),
+      onClick: () => {},
+      icon: 'add',
+    },
+    {
+      type: 'iconBtn',
+      onClick: () => {},
+      label: t('delete'),
+      icon: 'delete',
+    },
+  ];
+
+  const colDefinitions: ColDefinitionsType[] = [
+    {
+      id: '_id',
+      align: 'left',
+      disablePadding: true,
+      label: t('id'),
+    },
+    {
+      id: '_name',
+      align: 'left',
+      disablePadding: false,
+      label: t('name'),
+    },
+    {
+      id: '_statusElement',
+      align: 'left',
+      disablePadding: false,
+      label: t('status'),
+    },
+    {
+      id: '_rowCount',
+      align: 'left',
+      disablePadding: false,
+      label: (
+        <span className="flex-center">
+          {t('rowCount')}
+          <CustomToolTip title={t('tooltip')}>
+            <InfoIcon classes={{ root: classes.icon }} />
+          </CustomToolTip>
+        </span>
+      ),
+    },
+  ];
+
+  const handleSelectChange = (value: PartitionView[]) => {
+    setSelectedPartitions(value);
+  };
+
+  const handlePageChange = (e: any, page: number) => {
+    handleCurrentPage(page);
+    setSelectedPartitions([]);
+  };
+
+  return (
+    <section className={classes.wrapper}>
+      <MilvusGrid
+        toolbarConfigs={toolbarConfigs}
+        colDefinitions={colDefinitions}
+        rows={data}
+        rowCount={total}
+        primaryKey="id"
+        openCheckBox={true}
+        showHoverStyle={true}
+        selected={selectedPartitions}
+        setSelected={handleSelectChange}
+        page={currentPage}
+        onChangePage={handlePageChange}
+        rowsPerPage={pageSize}
+        isLoading={loading}
+      />
+    </section>
+  );
+};
+
+export default Partitions;

+ 11 - 4
client/src/router/Config.ts

@@ -1,9 +1,11 @@
+import Collection from '../pages/collections/Collection';
 import Collections from '../pages/collections/Collections';
 import Connect from '../pages/connect/Connect';
-import Console from '../pages/console/Console';
+// import Console from '../pages/console/Console';
 import Overview from '../pages/overview/Overview';
+import { RouterConfigType } from './Types';
 
-const RouterConfig = [
+const RouterConfig: RouterConfigType[] = [
   {
     path: '/',
     component: Overview,
@@ -20,10 +22,15 @@ const RouterConfig = [
     auth: true,
   },
   {
-    path: '/console',
-    component: Console,
+    path: '/collection/:collectionName',
+    component: Collection,
     auth: true,
   },
+  // {
+  //   path: '/console',
+  //   component: Console,
+  //   auth: true,
+  // },
 ];
 
 export default RouterConfig;

+ 6 - 0
client/src/router/Types.ts

@@ -13,3 +13,9 @@ export type NavInfo = {
   navTitle: string;
   backPath: string;
 };
+
+export type RouterConfigType = {
+  path: string;
+  component: () => JSX.Element;
+  auth: boolean;
+};