Skip to content

Commit 19912b5

Browse files
committed
Merge branch 'main' of github.com:zilliztech/attu
2 parents 0475c5a + c029278 commit 19912b5

File tree

7 files changed

+211
-4
lines changed

7 files changed

+211
-4
lines changed

client/package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
"@material-ui/lab": "4.0.0-alpha.58",
1414
"@material-ui/pickers": "^3.3.10",
1515
"@mui/x-data-grid": "^4.0.0",
16-
"@vitejs/plugin-react": "^2.2.0",
17-
"@vitejs/plugin-react-refresh": "^1.3.6",
1816
"axios": "^0.21.3",
1917
"dayjs": "^1.10.5",
2018
"file-saver": "^2.0.5",
@@ -26,14 +24,15 @@
2624
"react-i18next": "^12.0.0",
2725
"react-router-dom": "^5.2.0",
2826
"react-syntax-highlighter": "^15.4.4",
29-
"set-value": "^4.1.0",
3027
"socket.io-client": "^4.1.3",
3128
"typescript": "^4.1.2",
3229
"vite": "^3.2.2",
3330
"vite-plugin-svgr": "^0.3.0",
3431
"web-vitals": "^1.0.1"
3532
},
3633
"devDependencies": {
34+
"@vitejs/plugin-react": "^2.2.0",
35+
"@vitejs/plugin-react-refresh": "^1.3.6",
3736
"@testing-library/jest-dom": "^5.16.5",
3837
"@testing-library/react": "12.1.2",
3938
"@testing-library/react-hooks": "^7.0.1",

client/src/components/grid/Table.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const useStyles = makeStyles(theme => ({
5050
},
5151
tableCell: {
5252
background: theme.palette.common.white,
53-
paddingLeft: theme.spacing(2),
53+
padding: `${theme.spacing(1.5)} ${theme.spacing(2)}`,
5454
},
5555
hoverActionCell: {
5656
transition: '0.2s all',

client/src/i18n/en/collection.ts

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const collectionTrans = {
6565
partitionTab: 'Partitions',
6666
schemaTab: 'Schema',
6767
queryTab: 'Data Query',
68+
previewTab: 'Data Preview',
6869
startTip: 'Start your data query',
6970
exprPlaceHolder: 'Please enter your query by using advanced filter ->',
7071
};

client/src/pages/collections/Collection.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useMemo } from 'react';
99
import { parseLocationSearch } from '../../utils/Format';
1010
import Schema from '../schema/Schema';
1111
import Query from '../query/Query';
12+
import Preview from '../preview/Preview';
1213

1314
enum TAB_EMUM {
1415
'schema',
@@ -48,6 +49,10 @@ const Collection = () => {
4849
label: collectionTrans('partitionTab'),
4950
component: <Partitions collectionName={collectionName} />,
5051
},
52+
{
53+
label: collectionTrans('previewTab'),
54+
component: <Preview collectionName={collectionName} />,
55+
},
5156
{
5257
label: collectionTrans('queryTab'),
5358
component: <Query collectionName={collectionName} />,

client/src/pages/preview/Preview.tsx

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import { FC, useEffect, useState, useMemo } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import AttuGrid from '../../components/grid/Grid';
4+
import { getQueryStyles } from '../query/Styles';
5+
import { CollectionHttp } from '../../http/Collection';
6+
import { FieldHttp } from '../../http/Field';
7+
import { IndexHttp } from '../../http/Index';
8+
import { usePaginationHook } from '../../hooks/Pagination';
9+
import CopyButton from '../../components/advancedSearch/CopyButton';
10+
import { ToolBarConfig } from '../../components/grid/Types';
11+
import CustomToolBar from '../../components/grid/ToolBar';
12+
import { DataTypeStringEnum } from '../collections/Types';
13+
import { generateVector } from '../../utils/Common';
14+
15+
import {
16+
FLOAT_INDEX_CONFIG,
17+
BINARY_INDEX_CONFIG,
18+
DEFAULT_SEARCH_PARAM_VALUE_MAP,
19+
} from '../../consts/Milvus';
20+
21+
const Preview: FC<{
22+
collectionName: string;
23+
}> = ({ collectionName }) => {
24+
const [fields, setFields] = useState<any[]>([]);
25+
const [tableLoading, setTableLoading] = useState<any>();
26+
const [queryResult, setQueryResult] = useState<any>();
27+
const [primaryKey, setPrimaryKey] = useState<string>('');
28+
const { t: commonTrans } = useTranslation();
29+
const { t: collectionTrans } = useTranslation('collection');
30+
31+
const copyTrans = commonTrans('copy');
32+
33+
const classes = getQueryStyles();
34+
// Format result list
35+
const queryResultMemo = useMemo(
36+
() =>
37+
queryResult?.map((resultItem: { [key: string]: any }) => {
38+
// Iterate resultItem keys, then format vector(array) items.
39+
const tmp = Object.keys(resultItem).reduce(
40+
(prev: { [key: string]: any }, item: string) => {
41+
if (Array.isArray(resultItem[item])) {
42+
const list2Str = JSON.stringify(resultItem[item]);
43+
prev[item] = (
44+
<div className={classes.vectorTableCell}>
45+
<div>{list2Str}</div>
46+
<CopyButton
47+
label={copyTrans.label}
48+
value={list2Str}
49+
className={classes.copyBtn}
50+
/>
51+
</div>
52+
);
53+
} else {
54+
prev[item] = `${resultItem[item]}`;
55+
}
56+
return prev;
57+
},
58+
{}
59+
);
60+
return tmp;
61+
}),
62+
[queryResult, classes.vectorTableCell, classes.copyBtn, copyTrans.label]
63+
);
64+
65+
const {
66+
pageSize,
67+
handlePageSize,
68+
currentPage,
69+
handleCurrentPage,
70+
total,
71+
data: result,
72+
} = usePaginationHook(queryResultMemo || []);
73+
74+
const handlePageChange = (e: any, page: number) => {
75+
handleCurrentPage(page);
76+
};
77+
78+
const loadData = async (collectionName: string) => {
79+
// get schema list
80+
const schemaList = await FieldHttp.getFields(collectionName);
81+
const nameList = schemaList.map(v => ({
82+
name: v.name,
83+
type: v.data_type,
84+
}));
85+
86+
const id = schemaList.find(v => v._isPrimaryKey === true);
87+
const primaryKey = id?._fieldName || '';
88+
const delimiter = id?.data_type === 'Int64' ? '' : '"';
89+
90+
const vectorField = schemaList.find(
91+
v => v.data_type === 'FloatVector' || v.data_type === 'BinaryVector'
92+
);
93+
94+
const anns_field = vectorField?._fieldName!;
95+
const dim = Number(vectorField?._dimension);
96+
const vectors = [generateVector(dim)];
97+
// get search params
98+
const indexesInfo = await IndexHttp.getIndexInfo(collectionName);
99+
const indexConfig =
100+
BINARY_INDEX_CONFIG[indexesInfo[0]._indexType] ||
101+
FLOAT_INDEX_CONFIG[indexesInfo[0]._indexType];
102+
const metric_type = indexesInfo[0]._metricType;
103+
const searchParamKey = indexConfig.search[0];
104+
const searchParamValue = DEFAULT_SEARCH_PARAM_VALUE_MAP[searchParamKey];
105+
const searchParam = { [searchParamKey]: searchParamValue };
106+
const params = `${JSON.stringify(searchParam)}`;
107+
setPrimaryKey(primaryKey);
108+
// Temporarily hide bool field due to incorrect return from SDK.
109+
const fieldWithoutBool = nameList.filter(
110+
i => i.type !== DataTypeStringEnum.Bool
111+
);
112+
113+
// set fields
114+
setFields(fieldWithoutBool);
115+
116+
// set loading
117+
setTableLoading(true);
118+
119+
try {
120+
// first, search random data to get random id
121+
const searchRes = await CollectionHttp.vectorSearchData(collectionName, {
122+
search_params: {
123+
topk: 100,
124+
anns_field,
125+
metric_type,
126+
params,
127+
},
128+
expr: '',
129+
vectors,
130+
output_fields: [primaryKey],
131+
vector_type: Number(vectorField?._fieldId),
132+
});
133+
134+
// compose random id list expression
135+
const expr = `${primaryKey} in [${searchRes.results
136+
.map((d: any) => `${delimiter}${d.id}${delimiter}`)
137+
.join(',')}]`;
138+
139+
// query by random id
140+
const res = await CollectionHttp.queryData(collectionName, {
141+
expr: expr,
142+
output_fields: fieldWithoutBool.map(i => i.name),
143+
});
144+
145+
const result = res.data;
146+
setQueryResult(result);
147+
} catch (err) {
148+
setQueryResult([]);
149+
} finally {
150+
setTableLoading(false);
151+
}
152+
};
153+
154+
// Get fields at first or collection name changed.
155+
useEffect(() => {
156+
collectionName && loadData(collectionName);
157+
}, [collectionName]);
158+
159+
const toolbarConfigs: ToolBarConfig[] = [
160+
{
161+
type: 'iconBtn',
162+
onClick: () => {
163+
loadData(collectionName);
164+
},
165+
label: collectionTrans('refresh'),
166+
icon: 'refresh',
167+
},
168+
];
169+
170+
return (
171+
<div className={classes.root}>
172+
<CustomToolBar toolbarConfigs={toolbarConfigs} />
173+
174+
<AttuGrid
175+
toolbarConfigs={[]}
176+
colDefinitions={fields.map(i => ({
177+
id: i.name,
178+
align: 'left',
179+
disablePadding: false,
180+
label: i.name,
181+
}))}
182+
primaryKey={primaryKey}
183+
openCheckBox={false}
184+
isLoading={!!tableLoading}
185+
rows={result}
186+
rowCount={total}
187+
page={currentPage}
188+
onChangePage={handlePageChange}
189+
rowsPerPage={pageSize}
190+
setRowsPerPage={handlePageSize}
191+
/>
192+
</div>
193+
);
194+
};
195+
196+
export default Preview;

client/src/pages/query/Types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ export interface QueryParam {
33
partitions_names?: string[];
44
output_fields?: string[];
55
travel_timestamp?: string;
6+
limit?: number;
7+
offset?: number;
68
}

client/src/utils/Common.ts

+4
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,7 @@ export const generateHashCode = (source: string) => {
6161
export const generateIdByHash = (salt?: string) => {
6262
return generateHashCode(`${new Date().getTime().toString()}-${salt}`);
6363
};
64+
65+
export const generateVector = (dim: number) => {
66+
return Array.from({ length: dim }).map(() => Math.random());
67+
};

0 commit comments

Comments
 (0)