Browse Source

add csv downloader to query

czhen 3 years ago
parent
commit
9cb10c7b13

+ 3 - 1
client/package.json

@@ -26,6 +26,7 @@
     "@types/react-syntax-highlighter": "^13.5.2",
     "axios": "^0.21.3",
     "dayjs": "^1.10.5",
+    "file-saver": "^2.0.5",
     "i18next": "^20.3.1",
     "papaparse": "^5.3.1",
     "react": "^17.0.2",
@@ -76,8 +77,9 @@
   },
   "devDependencies": {
     "@testing-library/react-hooks": "^7.0.1",
+    "@types/file-saver": "^2.0.4",
     "@types/loadable__component": "^5.13.4",
     "@types/webpack-env": "^1.16.3",
     "prettier": "2.3.2"
   }
-}
+}

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

@@ -23,6 +23,7 @@ import CachedIcon from '@material-ui/icons/Cached';
 import FilterListIcon from '@material-ui/icons/FilterList';
 import AlternateEmailIcon from '@material-ui/icons/AlternateEmail';
 import DatePicker from '@material-ui/icons/Event';
+import GetAppIcon from '@material-ui/icons/GetApp';
 import { SvgIcon } from '@material-ui/core';
 import { ReactComponent as MilvusIcon } from '../../assets/icons/milvus.svg';
 import { ReactComponent as OverviewIcon } from '../../assets/icons/overview.svg';
@@ -62,6 +63,7 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   filter: (props = {}) => <FilterListIcon {...props} />,
   alias: (props = {}) => <AlternateEmailIcon {...props} />,
   datePicker: (props = {}) => <DatePicker {...props} />,
+  download: (props = {}) => <GetAppIcon {...props} />,
 
   milvus: (props = {}) => (
     <SvgIcon viewBox="0 0 44 31" component={MilvusIcon} {...props} />

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

@@ -34,4 +34,5 @@ export type IconsType =
   | 'filter'
   | 'copyExpression'
   | 'alias'
-  | 'datePicker';
+  | 'datePicker'
+  | 'download';

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

@@ -10,6 +10,8 @@ const collectionTrans = {
   deleteTooltip: 'Please select at least one item to delete.',
   alias: 'alias',
   aliasTooltip: 'Please select one collection to create alias',
+  download: 'Download',
+  downloadTooltip: 'Download all query results',
 
   collection: 'Collection',
   entites: 'entites',

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

@@ -10,6 +10,8 @@ const collectionTrans = {
   deleteTooltip: 'Please select at least one item to delete.',
   alias: 'alias',
   aliasTooltip: 'Please select one collection to create alias',
+  download: 'Download',
+  downloadTooltip: 'Download all query results',
 
   collection: 'Collection',
   entites: 'entites',

+ 24 - 1
client/src/pages/query/Query.tsx

@@ -19,6 +19,8 @@ import CopyButton from '../../components/advancedSearch/CopyButton';
 import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
 import CustomToolBar from '../../components/grid/ToolBar';
 // import { CustomDatePicker } from '../../components/customDatePicker/CustomDatePicker';
+import { saveAs } from 'file-saver';
+import { generateCsvData } from '../../utils/Format';
 
 const Query: FC<{
   collectionName: string;
@@ -79,6 +81,14 @@ const Query: FC<{
     [queryResult, classes.vectorTableCell, classes.copyBtn, copyTrans.label]
   );
 
+  const csvDataMemo = useMemo(() => {
+    const headers = fields?.map(i => i.name);
+    if (headers && queryResult) {
+      return generateCsvData(headers, queryResult);
+    }
+    return '';
+  }, [fields, queryResult]);
+
   const {
     pageSize,
     handlePageSize,
@@ -184,12 +194,25 @@ const Query: FC<{
       disabledTooltip: collectionTrans('deleteTooltip'),
       disabled: () => selectedDatas.length === 0,
     },
+    {
+      type: 'iconBtn',
+      onClick: () => {
+        const csvData = new Blob([csvDataMemo.toString()], {
+          type: 'text/csv;charset=utf-8',
+        });
+        saveAs(csvData, 'query_result.csv');
+      },
+      label: collectionTrans('delete'),
+      icon: 'download',
+      tooltip: collectionTrans('download'),
+      disabledTooltip: collectionTrans('downloadTooltip'),
+      disabled: () => !queryResult?.length,
+    },
   ];
 
   return (
     <div className={classes.root}>
       <CustomToolBar toolbarConfigs={toolbarConfigs} />
-
       <div className={classes.toolbar}>
         <div className="left">
           {/* <div className="expression"> */}

+ 28 - 0
client/src/utils/Format.ts

@@ -187,3 +187,31 @@ export const formatUtcToMilvus = (bigNumber: number) => {
   const milvusTimeStamp = BigInt(bigNumber) << BigInt(18);
   return milvusTimeStamp.toString();
 };
+
+/**
+ * Convert headers and rows to csv string.
+ * @param headers csv headers: string[]
+ * @param rows csv data rows: {[key in headers]: any}[]
+ * @returns csv string
+ */
+export const generateCsvData = (headers: string[], rows: any[]) => {
+  const rowsData = rows.reduce((prev, item: any[]) => {
+    headers.forEach((colName: any, idx: number) => {
+      const val = item[colName];
+      if (typeof val === 'string') {
+        prev += val;
+      } else if (typeof val === 'object') {
+        prev += `"[${val}]"`;
+      } else {
+        prev += `${val}`;
+      }
+      if (idx === headers.length - 1) {
+        prev += '\n';
+      } else {
+        prev += ',';
+      }
+    });
+    return prev;
+  }, '');
+  return headers.join(',') + '\n' + rowsData;
+};

+ 10 - 0
client/yarn.lock

@@ -1962,6 +1962,11 @@
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
   integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
 
+"@types/file-saver@^2.0.4":
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.4.tgz#aaf9b96296150d737b2fefa535ced05ed8013d84"
+  integrity sha512-sPZYQEIF/SOnLAvaz9lTuydniP+afBMtElRTdYkeV1QtEgvtJ7qolCPjly6O32QI8CbEmP5O/fztMXEDWfEcrg==
+
 "@types/glob@^7.1.1":
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
@@ -5396,6 +5401,11 @@ file-loader@6.1.1:
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
 
+file-saver@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
+  integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
+
 file-uri-to-path@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"