Forráskód Böngészése

update schema page (#702)

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 5 hónapja
szülő
commit
b79a3b4c6e

+ 1 - 5
client/src/components/customButton/CustomIconButton.tsx

@@ -19,11 +19,7 @@ const CustomIconButton = (props: IconButtonProps & { tooltip?: string }) => {
       {tooltip ? (
         <Tooltip title={tooltip} arrow>
           <span>
-            <IconButton
-              classes={{ root: classes.iconBtn }}
-              {...otherProps}
-              size="large"
-            >
+            <IconButton classes={{ root: classes.iconBtn }} {...otherProps}>
               {props.children}
             </IconButton>
           </span>

+ 18 - 5
client/src/pages/databases/RefreshButton.tsx → client/src/components/customButton/RefreshButton.tsx

@@ -4,9 +4,14 @@ import StatusIcon, { LoadingType } from '@/components/status/StatusIcon';
 import { IconButtonProps } from '@mui/material';
 import icons from '@/components/icons/Icons';
 
-const RefreshButton = (props: IconButtonProps & { tooltip?: string }) => {
+const RefreshButton = (
+  props: IconButtonProps & {
+    tooltip?: string;
+    icon?: React.ReactNode;
+  }
+) => {
   // props
-  const { onClick, ...otherProps } = props;
+  const { onClick, icon, ...otherProps } = props;
   // UI states
   const [isLoading, setIsLoading] = useState(false);
 
@@ -19,8 +24,17 @@ const RefreshButton = (props: IconButtonProps & { tooltip?: string }) => {
     setIsLoading(false);
   };
 
+  const styleObj = {
+    display: 'flex',
+    width: '23px',
+  };
+
   if (isLoading) {
-    return <StatusIcon type={LoadingType.CREATING} size={16} />;
+    return (
+      <div className={props.className} style={styleObj}>
+        <StatusIcon type={LoadingType.CREATING} />
+      </div>
+    );
   }
 
   return (
@@ -28,10 +42,9 @@ const RefreshButton = (props: IconButtonProps & { tooltip?: string }) => {
       className={props.className}
       {...otherProps}
       onClick={onBtnClicked}
-      style={{}}
       disabled={isLoading}
     >
-      <RefreshIcon />
+      {icon ? icon : <RefreshIcon />}
     </CustomIconButton>
   );
 };

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

@@ -382,7 +382,7 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
     </svg>
   ),
   refresh: (props = {}) => (
-    <svg
+    <SvgIcon
       width="15"
       height="15"
       viewBox="0 0 15 15"
@@ -396,7 +396,7 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
         fillRule="evenodd"
         clipRule="evenodd"
       ></path>
-    </svg>
+    </SvgIcon>
   ),
   navPerson: (props = {}) => (
     <SvgIcon
@@ -959,6 +959,23 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
       ></path>
     </svg>
   ),
+  file: (props = {}) => (
+    <SvgIcon
+      width="15"
+      height="15"
+      viewBox="0 0 15 15"
+      fill="none"
+      {...props}
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M3.5 2C3.22386 2 3 2.22386 3 2.5V12.5C3 12.7761 3.22386 13 3.5 13H11.5C11.7761 13 12 12.7761 12 12.5V6H8.5C8.22386 6 8 5.77614 8 5.5V2H3.5ZM9 2.70711L11.2929 5H9V2.70711ZM2 2.5C2 1.67157 2.67157 1 3.5 1H8.5C8.63261 1 8.75979 1.05268 8.85355 1.14645L12.8536 5.14645C12.9473 5.24021 13 5.36739 13 5.5V12.5C13 13.3284 12.3284 14 11.5 14H3.5C2.67157 14 2 13.3284 2 12.5V2.5Z"
+        fill="currentColor"
+        fillRule="evenodd"
+        clipRule="evenodd"
+      ></path>
+    </SvgIcon>
+  ),
 };
 
 export default icons;

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

@@ -59,4 +59,5 @@ export type IconsType =
   | 'day'
   | 'night'
   | 'img'
-  | 'fileplus';
+  | 'fileplus'
+  | 'file';

+ 1 - 1
client/src/components/status/StatusIcon.tsx

@@ -22,7 +22,7 @@ const useStyles = makeStyles((theme: Theme) => ({
     paddingLeft: theme.spacing(.5),
   },
   svg: {
-    color: theme.palette.primary.light,
+    color: theme.palette.primary.main,
   },
 }));
 

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

@@ -24,6 +24,13 @@ export class CollectionService extends BaseModel {
     return super.findAll({ path: `/collections`, params: data || {} });
   }
 
+  static describeCollectionUnformatted(collectionName: string) {
+    return super.search({
+      path: `/collections/${collectionName}/unformatted`,
+      params: {},
+    });
+  }
+
   static getCollection(collectionName: string) {
     return super.search<CollectionFullObject>({
       path: `/collections/${collectionName}`,

+ 1 - 0
client/src/i18n/cn/button.ts

@@ -40,6 +40,7 @@ const btnTrans = {
   explore: '探索',
   close: '关闭',
   modify: '修改',
+  downloadSchema: '下载 Schema',
 
   // tips
   loadColTooltip: '加载Collection',

+ 3 - 1
client/src/i18n/en/button.ts

@@ -40,6 +40,7 @@ const btnTrans = {
   explore: 'Explore',
   close: 'Close',
   modify: 'Modify',
+  downloadSchema: 'Download Schema',
 
   // tips
   loadColTooltip: 'Load Collection',
@@ -59,7 +60,8 @@ const btnTrans = {
   downloadDisabledTooltip: 'Please select data before exporting',
   deleteDisableTooltip: 'Please select at least one item to delete.',
   editEntityDisabledTooltip: 'Only one entity can be edited at a time.',
-  editEntityDisabledTooltipAutoId: 'The auto-generated ID entity cannot be edited.',
+  editEntityDisabledTooltipAutoId:
+    'The auto-generated ID entity cannot be edited.',
 };
 
 export default btnTrans;

+ 0 - 12
client/src/pages/databases/Databases.tsx

@@ -17,8 +17,6 @@ import { dataContext, authContext } from '@/context';
 import Collections from './collections/Collections';
 import StatusIcon, { LoadingType } from '@/components/status/StatusIcon';
 import { ConsistencyLevelEnum, DYNAMIC_FIELD } from '@/consts';
-import RefreshButton from './RefreshButton';
-import CopyButton from '@/components/advancedSearch/CopyButton';
 import { SearchParams } from './types';
 import { CollectionObject, CollectionFullObject } from '@server/types';
 import { makeStyles } from '@mui/styles';
@@ -248,16 +246,6 @@ const Databases = () => {
   useNavigationHook(ALL_ROUTER_TYPES.DATABASES, {
     collectionName,
     databaseName,
-    extra: (
-      <>
-        <CopyButton label="" value={collectionName} />
-        <RefreshButton
-          onClick={async () => {
-            await fetchCollection(collectionName);
-          }}
-        />
-      </>
-    ),
   });
 
   const setCollectionSearchParams = (params: SearchParams) => {

+ 43 - 2
client/src/pages/databases/collections/schema/Schema.tsx

@@ -15,6 +15,9 @@ import { FieldObject } from '@server/types';
 import { useStyles } from './Styles';
 import CustomIconButton from '@/components/customButton/CustomIconButton';
 import LoadCollectionDialog from '@/pages/dialogs/LoadCollectionDialog';
+import CopyButton from '@/components/advancedSearch/CopyButton';
+import RefreshButton from '@/components/customButton/RefreshButton';
+import { CollectionService } from '@/http';
 
 const Overview = () => {
   const { fetchCollection, collections, loading } = useContext(dataContext);
@@ -25,6 +28,7 @@ const Overview = () => {
   const classes = useStyles();
   const { t: collectionTrans } = useTranslation('collection');
   const { t: indexTrans } = useTranslation('index');
+  const { t: btnTrans } = useTranslation('btn');
   const { t: commonTrans } = useTranslation();
   const gridTrans = commonTrans('grid');
 
@@ -188,7 +192,43 @@ const Overview = () => {
           <div className={classes.cardWrapper}>
             <div className={classes.card}>
               <Typography variant="h5">{collectionTrans('name')}</Typography>
-              <Typography variant="h6">{collection.collection_name}</Typography>
+              <Typography variant="h6">
+                <p title={collection.collection_name}>
+                  {collection.collection_name}
+                </p>
+                <CopyButton
+                  className={classes.extraBtn}
+                  label={collection.collection_name}
+                  value={collection.collection_name}
+                />
+                <RefreshButton
+                  className={classes.extraBtn}
+                  onClick={async () => {
+                    const res =
+                      await CollectionService.describeCollectionUnformatted(
+                        collection.collection_name
+                      );
+
+                    // download json file
+                    const json = JSON.stringify(res, null, 2);
+                    const blob = new Blob([json], { type: 'application/json' });
+                    const url = URL.createObjectURL(blob);
+                    const a = document.createElement('a');
+                    a.href = url;
+                    a.download = `${collection.collection_name}.json`;
+                    a.click();
+                  }}
+                  tooltip={btnTrans('downloadSchema')}
+                  icon={<Icons.download />}
+                />
+                <RefreshButton
+                  className={classes.extraBtn}
+                  tooltip={btnTrans('refresh')}
+                  onClick={async () => {
+                    await fetchCollection(collectionName);
+                  }}
+                />
+              </Typography>
               <Typography variant="h5">
                 {collectionTrans('description')}
               </Typography>
@@ -222,6 +262,7 @@ const Overview = () => {
                 {collection.loaded ? collection.replicas?.length : '...'}
                 {collection.loaded && enableModifyReplica && (
                   <CustomIconButton
+                    className={classes.extraBtn}
                     tooltip={collectionTrans('modifyReplicaTooltip')}
                     onClick={() => {
                       setDialog({
@@ -238,7 +279,7 @@ const Overview = () => {
                       });
                     }}
                   >
-                    <Icons.settings className={classes.addReplicaBtn} />
+                    <Icons.settings />
                   </CustomIconButton>
                 )}
               </Typography>

+ 19 - 5
client/src/pages/databases/collections/schema/Styles.tsx

@@ -16,8 +16,19 @@ export const useStyles = makeStyles((theme: Theme) => ({
     },
     '& h6': {
       fontSize: 14,
+      lineHeight: '20px',
+      display: 'flex',
       color: theme.palette.text.primary,
-      marginBottom: theme.spacing(1),
+      marginBottom: theme.spacing(0.5),
+
+      '& p': {
+        margin: 0,
+        marginRight: 8,
+        textOverflow: 'ellipsis',
+        overflow: 'hidden',
+        maxWidth: 140,
+        fontWeight: 700,
+      },
     },
   },
   infoWrapper: {
@@ -46,11 +57,14 @@ export const useStyles = makeStyles((theme: Theme) => ({
     marginLeft: theme.spacing(0.5),
   },
   extraBtn: {
-    height: 24,
-  },
-  addReplicaBtn: {
-    width: 15,
+    position: 'relative',
+    top: -6,
+    '& svg': {
+      width: 15,
+      color: theme.palette.text.primary,
+    },
   },
+
   questionIcon: {
     width: 12,
     position: 'relative',

+ 23 - 0
server/src/collections/collections.controller.ts

@@ -44,6 +44,10 @@ export class CollectionController {
 
     // get collection with index info
     this.router.get('/:name', this.describeCollection.bind(this));
+    this.router.get(
+      '/:name/unformatted',
+      this.describeUnformattedCollection.bind(this)
+    );
     // get count
     this.router.get('/:name/count', this.count.bind(this));
     // create collection
@@ -260,6 +264,25 @@ export class CollectionController {
     }
   }
 
+  async describeUnformattedCollection(
+    req: Request,
+    res: Response,
+    next: NextFunction
+  ) {
+    const name = req.params?.name;
+    try {
+      const result =
+        await this.collectionsService.describeUnformattedCollection(
+          req.clientId,
+          name,
+          req.db_name
+        );
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
+
   async getCollectionStatistics(
     req: Request,
     res: Response,

+ 15 - 1
server/src/collections/collections.service.ts

@@ -82,6 +82,20 @@ export class CollectionsService {
     return newCollection[0];
   }
 
+  async describeUnformattedCollection(
+    clientId: string,
+    collection_name: string,
+    db_name?: string
+  ) {
+    const { milvusClient } = clientCache.get(clientId);
+    const res = await milvusClient.describeCollection({
+      collection_name,
+      db_name,
+    });
+    throwErrorFromSDK(res.status);
+    return res;
+  }
+
   async describeCollection(clientId: string, data: DescribeCollectionReq) {
     const { milvusClient } = clientCache.get(clientId);
     const res = (await milvusClient.describeCollection(
@@ -469,7 +483,7 @@ export class CollectionsService {
       id: collectionInfo.collectionID,
       loadedPercentage,
       consistency_level: collectionInfo.consistency_level,
-      replicas: replicas && replicas.replicas || [],
+      replicas: (replicas && replicas.replicas) || [],
       loaded: status === LOADING_STATE.LOADED,
       status,
       properties: collectionInfo.properties,