Browse Source

Merge pull request #239 from zilliztech/data_preview

fix data preview on search/data preview/data query
ryjiang 1 year ago
parent
commit
b5790194c3

+ 49 - 0
client/src/hooks/Result.tsx

@@ -0,0 +1,49 @@
+import { useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { ClassNameMap } from '@material-ui/styles/withStyles';
+import { detectItemType } from '@/utils/Common';
+import CopyButton from '@/components/advancedSearch/CopyButton';
+
+export const useSearchResult = (searchResult: any[], classes: ClassNameMap) => {
+  const { t: commonTrans } = useTranslation();
+  const copyTrans = commonTrans('copy');
+
+  return useMemo(
+    () =>
+      searchResult?.map((resultItem: { [key: string]: any }) => {
+        // Iterate resultItem keys, then format vector(array) items.
+
+        const tmp = Object.keys(resultItem).reduce(
+          (prev: { [key: string]: any }, item: string) => {
+            const itemType = detectItemType(resultItem[item]);
+
+            switch (itemType) {
+              case 'json':
+                prev[item] = <div>{JSON.stringify(resultItem[item])}</div>;
+                break;
+              case 'array':
+                const list2Str = JSON.stringify(resultItem[item]);
+                prev[item] = (
+                  <div className={classes.vectorTableCell}>
+                    <div>{list2Str}</div>
+                    <CopyButton
+                      label={copyTrans.label}
+                      value={list2Str}
+                      className={classes.copyBtn}
+                    />
+                  </div>
+                );
+                break;
+              default:
+                prev[item] = `${resultItem[item]}`;
+            }
+
+            return prev;
+          },
+          {}
+        );
+        return tmp;
+      }),
+    [searchResult, classes.vectorTableCell, classes.copyBtn, copyTrans.label]
+  );
+};

+ 4 - 41
client/src/pages/preview/Preview.tsx

@@ -1,4 +1,4 @@
-import { FC, useEffect, useState, useMemo } from 'react';
+import { FC, useEffect, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import AttuGrid from '@/components/grid/Grid';
 import { getQueryStyles } from '../query/Styles';
@@ -6,12 +6,11 @@ import { CollectionHttp } from '@/http/Collection';
 import { FieldHttp } from '@/http/Field';
 import { IndexHttp } from '@/http/Index';
 import { usePaginationHook } from '@/hooks/Pagination';
-import CopyButton from '@/components/advancedSearch/CopyButton';
 import { ToolBarConfig } from '@/components/grid/Types';
 import CustomToolBar from '@/components/grid/ToolBar';
 import { generateVector } from '@/utils/Common';
 import { DataTypeEnum } from '../../pages/collections/Types';
-
+import { useSearchResult } from '@/hooks/Result';
 import {
   INDEX_CONFIG,
   DEFAULT_SEARCH_PARAM_VALUE_MAP,
@@ -24,48 +23,12 @@ const Preview: FC<{
   const [tableLoading, setTableLoading] = useState<any>();
   const [queryResult, setQueryResult] = useState<any>();
   const [primaryKey, setPrimaryKey] = useState<string>('');
-  const { t: commonTrans } = useTranslation();
   const { t: collectionTrans } = useTranslation('collection');
 
-  const copyTrans = commonTrans('copy');
-
   const classes = getQueryStyles();
+
   // Format result list
-  const queryResultMemo = useMemo(
-    () =>
-      queryResult?.map((resultItem: { [key: string]: any }) => {
-        // Iterate resultItem keys, then format vector(array) items.
-        const tmp = Object.keys(resultItem).reduce(
-          (prev: { [key: string]: any }, item: string) => {
-            switch (item) {
-              case 'json':
-                prev[item] = <div>{JSON.stringify(resultItem[item])}</div>;
-                break;
-              case 'vector':
-                const list2Str = JSON.stringify(resultItem[item]);
-                prev[item] = (
-                  <div className={classes.vectorTableCell}>
-                    <div>{list2Str}</div>
-                    <CopyButton
-                      label={copyTrans.label}
-                      value={list2Str}
-                      className={classes.copyBtn}
-                    />
-                  </div>
-                );
-                break;
-              default:
-                prev[item] = `${resultItem[item]}`;
-            }
-
-            return prev;
-          },
-          {}
-        );
-        return tmp;
-      }),
-    [queryResult, classes.vectorTableCell, classes.copyBtn, copyTrans.label]
-  );
+  const queryResultMemo = useSearchResult(queryResult, classes);
 
   const {
     pageSize,

+ 3 - 39
client/src/pages/query/Query.tsx

@@ -1,4 +1,4 @@
-import { FC, useEffect, useState, useRef, useMemo, useContext } from 'react';
+import { FC, useEffect, useState, useRef, useContext } from 'react';
 import { TextField } from '@material-ui/core';
 import { useTranslation } from 'react-i18next';
 import { Parser } from '@json2csv/plainjs';
@@ -14,13 +14,13 @@ import { CollectionHttp } from '@/http/Collection';
 import { FieldHttp } from '@/http/Field';
 import { usePaginationHook } from '@/hooks/Pagination';
 // import { useTimeTravelHook } from '@/hooks/TimeTravel';
-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 { DataTypeStringEnum } from '../collections/Types';
 import { getLabelDisplayedRows } from '../search/Utils';
+import { useSearchResult } from '@/hooks/Result';
 
 const Query: FC<{
   collectionName: string;
@@ -47,47 +47,11 @@ const Query: FC<{
   const { t: searchTrans } = useTranslation('search');
   const { t: collectionTrans } = useTranslation('collection');
   const { t: btnTrans } = useTranslation('btn');
-  const { t: commonTrans } = useTranslation();
-  const copyTrans = commonTrans('copy');
 
   const classes = getQueryStyles();
 
   // Format result list
-  const queryResultMemo = useMemo(
-    () =>
-      queryResult?.map((resultItem: { [key: string]: any }) => {
-        // Iterate resultItem keys, then format vector(array) items.
-        const tmp = Object.keys(resultItem).reduce(
-          (prev: { [key: string]: any }, item: string) => {
-            switch (item) {
-              case 'json':
-                prev[item] = <div>{JSON.stringify(resultItem[item])}</div>;
-                break;
-              case 'vector':
-                const list2Str = JSON.stringify(resultItem[item]);
-                prev[item] = (
-                  <div className={classes.vectorTableCell}>
-                    <div>{list2Str}</div>
-                    <CopyButton
-                      label={copyTrans.label}
-                      value={list2Str}
-                      className={classes.copyBtn}
-                    />
-                  </div>
-                );
-                break;
-              default:
-                prev[item] = `${resultItem[item]}`;
-            }
-
-            return prev;
-          },
-          {}
-        );
-        return tmp;
-      }),
-    [queryResult, classes.vectorTableCell, classes.copyBtn, copyTrans.label]
-  );
+  const queryResultMemo = useSearchResult(queryResult, classes);
 
   const {
     pageSize,

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

@@ -28,7 +28,7 @@ export interface SearchParamsProps {
 
 export interface SearchResultView {
   // dynamic field names
-  [key: string]: string | number;
+  [key: string]: any;
   rank: number;
   distance: number;
 }

+ 16 - 9
client/src/pages/search/VectorSearch.tsx

@@ -26,7 +26,6 @@ import {
   getEmbeddingType,
   getNonVectorFieldsForFilter,
   getVectorFieldOptions,
-  transferSearchResult,
 } from '@/utils/search';
 import { ColDefinitionsType } from '@/components/grid/Types';
 import Filter from '@/components/advancedSearch';
@@ -36,8 +35,9 @@ import { parseLocationSearch } from '@/utils/Format';
 import { cloneObj, generateVector } from '@/utils/Common';
 import { CustomDatePicker } from '@/components/customDatePicker/CustomDatePicker';
 import { useTimeTravelHook } from '@/hooks/TimeTravel';
-import { LOADING_STATE } from '../../consts/Milvus';
+import { LOADING_STATE } from '@/consts/Milvus';
 import { getLabelDisplayedRows } from './Utils';
+import { useSearchResult } from '@/hooks/Result';
 
 const VectorSearch = () => {
   useNavigationHook(ALL_ROUTER_TYPES.SEARCH);
@@ -76,6 +76,8 @@ const VectorSearch = () => {
   // latency
   const [latency, setLatency] = useState<number>(0);
 
+  const searchResultMemo = useSearchResult(searchResult as any, classes);
+
   const {
     pageSize,
     handlePageSize,
@@ -86,7 +88,7 @@ const VectorSearch = () => {
     order,
     orderBy,
     handleGridSort,
-  } = usePaginationHook(searchResult || []);
+  } = usePaginationHook(searchResultMemo || []);
 
   const { timeTravel, setTimeTravel, timeTravelInfo, handleDateTimeChange } =
     useTimeTravelHook();
@@ -119,6 +121,8 @@ const VectorSearch = () => {
     return fields.find(f => f._isPrimaryKey)?._fieldName;
   }, [selectedCollection, collections]);
 
+  const orderArray = ['id', 'distance', ...outputFields];
+
   const colDefinitions: ColDefinitionsType[] = useMemo(() => {
     /**
      * id represents primary key, score represents distance
@@ -127,6 +131,11 @@ const VectorSearch = () => {
      */
     return searchResult && searchResult.length > 0
       ? Object.keys(searchResult[0])
+          .sort((a, b) => {
+            const indexA = orderArray.indexOf(a);
+            const indexB = orderArray.indexOf(b);
+            return indexA - indexB;
+          })
           .filter(item => {
             // if primary key field name is id, don't filter it
             const invalidItems =
@@ -141,7 +150,7 @@ const VectorSearch = () => {
             needCopy: primaryKeyField === key,
           }))
       : [];
-  }, [searchResult, primaryKeyField]);
+  }, [searchResult, primaryKeyField, orderArray]);
 
   const [selectedMetricType, setSelectedMetricType] = useState<string>('');
 
@@ -308,7 +317,7 @@ const VectorSearch = () => {
   const handleSearch = async (topK: number, expr = expression) => {
     const clonedSearchParams = cloneObj(searchParam);
     delete clonedSearchParams.round_decimal;
-    const searhParamPairs = {
+    const searchParamPairs = {
       params: JSON.stringify(clonedSearchParams),
       anns_field: selectedField,
       topk: topK,
@@ -319,7 +328,7 @@ const VectorSearch = () => {
     const params: VectorSearchParam = {
       output_fields: outputFields,
       expr,
-      search_params: searhParamPairs,
+      search_params: searchParamPairs,
       vectors: [parseValue(vectors)],
       vector_type: fieldType,
       travel_timestamp: timeTravelInfo.timestamp,
@@ -332,9 +341,7 @@ const VectorSearch = () => {
         params
       );
       setTableLoading(false);
-
-      const result = transferSearchResult(res.results);
-      setSearchResult(result);
+      setSearchResult(res.results);
       setLatency(res.latency);
     } catch (err) {
       setTableLoading(false);

+ 10 - 0
client/src/utils/Common.ts

@@ -73,3 +73,13 @@ export const cloneObj = (obj: any) => {
 export const sleep = (ms: number) => {
   return new Promise(resolve => setTimeout(resolve, ms));
 };
+
+export const detectItemType = (item: unknown) => {
+  if (Array.isArray(item)) {
+    return 'array';
+  } else if (typeof item === 'object' && item !== null) {
+    return 'json';
+  } else {
+    return 'unknown';
+  }
+};

+ 1 - 38
client/src/utils/search.ts

@@ -6,44 +6,7 @@ import {
   IndexView,
   INDEX_TYPES_ENUM,
 } from '../pages/schema/Types';
-import {
-  FieldOption,
-  SearchResult,
-  SearchResultView,
-} from '../types/SearchTypes';
-
-/**
- * Do not change  vector search result default sort  by ourself.
- * Different index may has different sort in milvus.
- * @param result
- * @returns
- */
-export const transferSearchResult = (
-  result: SearchResult[]
-): SearchResultView[] => {
-  const resultView = result.map((r, index) => {
-    const { rank, distance, ...others } = r;
-    const data: any = {
-      rank: index + 1,
-      distance: r.score,
-    };
-    // When value is boolean ,table will not render bool value.
-    // So we need to use toString() here.
-    Object.keys(others).forEach(v => {
-      switch (v) {
-        case 'json':
-          data[v] = JSON.stringify(others[v]);
-          break;
-        default:
-          data[v] = others[v].toString();
-          break;
-      }
-    });
-    return data;
-  });
-
-  return resultView;
-};
+import { FieldOption } from '../types/SearchTypes';
 
 /**
  * function to get EmbeddingType