Query.tsx 10 KB

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