Query.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import { FC, useEffect, useState, useRef, useMemo, useContext } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { rootContext } from '../../context/Root';
  4. import EmptyCard from '../../components/cards/EmptyCard';
  5. import icons from '../../components/icons/Icons';
  6. import CustomButton from '../../components/customButton/CustomButton';
  7. import AttuGrid from '../../components/grid/Grid';
  8. import { ToolBarConfig } from '../../components/grid/Types';
  9. import { getQueryStyles } from './Styles';
  10. import Filter from '../../components/advancedSearch';
  11. import { CollectionHttp } from '../../http/Collection';
  12. import { FieldHttp } from '../../http/Field';
  13. import { usePaginationHook } from '../../hooks/Pagination';
  14. // import { useTimeTravelHook } from '../../hooks/TimeTravel';
  15. import CopyButton from '../../components/advancedSearch/CopyButton';
  16. import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
  17. import CustomToolBar from '../../components/grid/ToolBar';
  18. // import { CustomDatePicker } from '../../components/customDatePicker/CustomDatePicker';
  19. import { saveAs } from 'file-saver';
  20. import { generateCsvData } from '../../utils/Format';
  21. import { DataTypeStringEnum } from '../collections/Types';
  22. const Query: FC<{
  23. collectionName: string;
  24. }> = ({ collectionName }) => {
  25. const [fields, setFields] = useState<any[]>([]);
  26. const [expression, setExpression] = useState('');
  27. const [tableLoading, setTableLoading] = useState<any>();
  28. const [queryResult, setQueryResult] = useState<any>();
  29. const [selectedData, setSelectedData] = useState<any[]>([]);
  30. const [primaryKey, setPrimaryKey] = useState<string>('');
  31. const { setDialog, handleCloseDialog, openSnackBar } =
  32. useContext(rootContext);
  33. const VectorSearchIcon = icons.vectorSearch;
  34. const ResetIcon = icons.refresh;
  35. const { t: dialogTrans } = useTranslation('dialog');
  36. const { t: successTrans } = useTranslation('success');
  37. const { t: searchTrans } = useTranslation('search');
  38. const { t: collectionTrans } = useTranslation('collection');
  39. const { t: btnTrans } = useTranslation('btn');
  40. const { t: commonTrans } = useTranslation();
  41. const copyTrans = commonTrans('copy');
  42. const classes = getQueryStyles();
  43. // const { timeTravel, setTimeTravel, timeTravelInfo, handleDateTimeChange } =
  44. // useTimeTravelHook();
  45. // Format result list
  46. const queryResultMemo = useMemo(
  47. () =>
  48. queryResult?.map((resultItem: { [key: string]: any }) => {
  49. // Iterate resultItem keys, then format vector(array) items.
  50. const tmp = Object.keys(resultItem).reduce(
  51. (prev: { [key: string]: any }, item: string) => {
  52. switch (item) {
  53. case 'json':
  54. prev[item] = <div>{JSON.stringify(resultItem[item])}</div>;
  55. break;
  56. case 'vector':
  57. const list2Str = JSON.stringify(resultItem[item]);
  58. prev[item] = (
  59. <div className={classes.vectorTableCell}>
  60. <div>{list2Str}</div>
  61. <CopyButton
  62. label={copyTrans.label}
  63. value={list2Str}
  64. className={classes.copyBtn}
  65. />
  66. </div>
  67. );
  68. break;
  69. default:
  70. prev[item] = `${resultItem[item]}`;
  71. }
  72. return prev;
  73. },
  74. {}
  75. );
  76. return tmp;
  77. }),
  78. [queryResult, classes.vectorTableCell, classes.copyBtn, copyTrans.label]
  79. );
  80. const csvDataMemo = useMemo(() => {
  81. const headers: string[] = fields?.map(i => i.name);
  82. if (headers?.length && queryResult?.length) {
  83. return generateCsvData(headers, queryResult);
  84. }
  85. return '';
  86. }, [fields, queryResult]);
  87. const {
  88. pageSize,
  89. handlePageSize,
  90. currentPage,
  91. handleCurrentPage,
  92. total,
  93. data: result,
  94. order,
  95. orderBy,
  96. handleGridSort,
  97. } = usePaginationHook(queryResultMemo || []);
  98. const handlePageChange = (e: any, page: number) => {
  99. handleCurrentPage(page);
  100. };
  101. const getFields = async (collectionName: string) => {
  102. const schemaList = await FieldHttp.getFields(collectionName);
  103. const nameList = schemaList.map(v => ({
  104. name: v.name,
  105. type: v.data_type,
  106. }));
  107. const primaryKey =
  108. schemaList.find(v => v._isPrimaryKey === true)?._fieldName || '';
  109. setPrimaryKey(primaryKey);
  110. setFields(nameList);
  111. };
  112. // Get fields at first or collection name changed.
  113. useEffect(() => {
  114. collectionName && getFields(collectionName);
  115. }, [collectionName]);
  116. const filterRef = useRef();
  117. const handleFilterReset = () => {
  118. const currentFilter: any = filterRef.current;
  119. currentFilter?.getReset();
  120. setExpression('');
  121. setTableLoading(null);
  122. setQueryResult(null);
  123. handleCurrentPage(0);
  124. };
  125. const handleFilterSubmit = (expression: string) => {
  126. setExpression(expression);
  127. setQueryResult(null);
  128. };
  129. const handleQuery = async () => {
  130. setTableLoading(true);
  131. try {
  132. const res = await CollectionHttp.queryData(collectionName, {
  133. expr: expression,
  134. output_fields: fields.map(i => i.name),
  135. // travel_timestamp: timeTravelInfo.timestamp,
  136. });
  137. const result = res.data;
  138. setQueryResult(result);
  139. } catch (err) {
  140. setQueryResult([]);
  141. } finally {
  142. setTableLoading(false);
  143. }
  144. };
  145. const handleSelectChange = (value: any) => {
  146. setSelectedData(value);
  147. };
  148. const handleDelete = async () => {
  149. await CollectionHttp.deleteEntities(collectionName, {
  150. expr: `${primaryKey} in [${selectedData
  151. .map(v => v[primaryKey])
  152. .join(',')}]`,
  153. });
  154. handleCloseDialog();
  155. openSnackBar(successTrans('delete', { name: collectionTrans('entites') }));
  156. handleQuery();
  157. };
  158. const toolbarConfigs: ToolBarConfig[] = [
  159. {
  160. type: 'iconBtn',
  161. onClick: () => {
  162. setDialog({
  163. open: true,
  164. type: 'custom',
  165. params: {
  166. component: (
  167. <DeleteTemplate
  168. label={btnTrans('drop')}
  169. title={dialogTrans('deleteTitle', {
  170. type: collectionTrans('entites'),
  171. })}
  172. text={collectionTrans('deleteDataWarning')}
  173. handleDelete={handleDelete}
  174. />
  175. ),
  176. },
  177. });
  178. },
  179. label: collectionTrans('delete'),
  180. icon: 'delete',
  181. // tooltip: collectionTrans('deleteTooltip'),
  182. disabledTooltip: collectionTrans('deleteTooltip'),
  183. disabled: () => selectedData.length === 0,
  184. },
  185. {
  186. type: 'iconBtn',
  187. onClick: () => {
  188. const csvData = new Blob([csvDataMemo.toString()], {
  189. type: 'text/csv;charset=utf-8',
  190. });
  191. saveAs(csvData, 'query_result.csv');
  192. },
  193. label: collectionTrans('delete'),
  194. icon: 'download',
  195. tooltip: collectionTrans('download'),
  196. disabledTooltip: collectionTrans('downloadTooltip'),
  197. disabled: () => !queryResult?.length,
  198. },
  199. ];
  200. return (
  201. <div className={classes.root}>
  202. <CustomToolBar toolbarConfigs={toolbarConfigs} />
  203. <div className={classes.toolbar}>
  204. <div className="left">
  205. {/* <div className="expression"> */}
  206. <div>{`${expression || collectionTrans('exprPlaceHolder')}`}</div>
  207. <Filter
  208. ref={filterRef}
  209. title="Advanced Filter"
  210. fields={fields.filter(
  211. i =>
  212. i.type !== DataTypeStringEnum.FloatVector &&
  213. i.type !== DataTypeStringEnum.BinaryVector
  214. )}
  215. filterDisabled={false}
  216. onSubmit={handleFilterSubmit}
  217. showTitle={false}
  218. showTooltip={false}
  219. />
  220. {/* </div> */}
  221. {/* <CustomDatePicker
  222. label={timeTravelInfo.label}
  223. onChange={handleDateTimeChange}
  224. date={timeTravel}
  225. setDate={setTimeTravel}
  226. /> */}
  227. </div>
  228. <div className="right">
  229. <CustomButton className="btn" onClick={handleFilterReset}>
  230. <ResetIcon classes={{ root: 'icon' }} />
  231. {btnTrans('reset')}
  232. </CustomButton>
  233. <CustomButton
  234. variant="contained"
  235. disabled={!expression}
  236. onClick={() => handleQuery()}
  237. >
  238. {btnTrans('query')}
  239. </CustomButton>
  240. </div>
  241. </div>
  242. {tableLoading || queryResult?.length ? (
  243. <AttuGrid
  244. toolbarConfigs={[]}
  245. colDefinitions={fields.map(i => ({
  246. id: i.name,
  247. align: 'left',
  248. disablePadding: false,
  249. label: i.name,
  250. }))}
  251. primaryKey={primaryKey}
  252. openCheckBox={true}
  253. isLoading={!!tableLoading}
  254. rows={result}
  255. rowCount={total}
  256. selected={selectedData}
  257. setSelected={handleSelectChange}
  258. page={currentPage}
  259. onChangePage={handlePageChange}
  260. rowsPerPage={pageSize}
  261. setRowsPerPage={handlePageSize}
  262. orderBy={orderBy}
  263. order={order}
  264. handleSort={handleGridSort}
  265. />
  266. ) : (
  267. <EmptyCard
  268. wrapperClass={`page-empty-card ${classes.emptyCard}`}
  269. icon={<VectorSearchIcon />}
  270. text={
  271. queryResult?.length === 0
  272. ? searchTrans('empty')
  273. : collectionTrans('startTip')
  274. }
  275. />
  276. )}
  277. </div>
  278. );
  279. };
  280. export default Query;