Collections.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. import { useCallback, useContext, useEffect, useState } from 'react';
  2. import { Link } from 'react-router-dom';
  3. import { useNavigationHook } from '../../hooks/Navigation';
  4. import { ALL_ROUTER_TYPES } from '../../router/Types';
  5. import MilvusGrid from '../../components/grid/Grid';
  6. import CustomToolBar from '../../components/grid/ToolBar';
  7. import { CollectionCreateParam, CollectionView, DataTypeEnum } from './Types';
  8. import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
  9. import { usePaginationHook } from '../../hooks/Pagination';
  10. import icons from '../../components/icons/Icons';
  11. import EmptyCard from '../../components/cards/EmptyCard';
  12. import Status from '../../components/status/Status';
  13. import { useTranslation } from 'react-i18next';
  14. import { ChildrenStatusType, StatusEnum } from '../../components/status/Types';
  15. import { makeStyles, Theme } from '@material-ui/core';
  16. import StatusIcon from '../../components/status/StatusIcon';
  17. import CustomToolTip from '../../components/customToolTip/CustomToolTip';
  18. import { rootContext } from '../../context/Root';
  19. import CreateCollection from './Create';
  20. import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
  21. import { CollectionHttp } from '../../http/Collection';
  22. import {
  23. useInsertDialogHook,
  24. useLoadAndReleaseDialogHook,
  25. } from '../../hooks/Dialog';
  26. import Highlighter from 'react-highlight-words';
  27. import { parseLocationSearch } from '../../utils/Format';
  28. import InsertContainer from '../../components/insert/Container';
  29. const useStyles = makeStyles((theme: Theme) => ({
  30. emptyWrapper: {
  31. marginTop: theme.spacing(2),
  32. },
  33. icon: {
  34. fontSize: '20px',
  35. marginLeft: theme.spacing(0.5),
  36. },
  37. dialogContent: {
  38. lineHeight: '24px',
  39. fontSize: '16px',
  40. },
  41. link: {
  42. color: theme.palette.common.black,
  43. },
  44. highlight: {
  45. color: theme.palette.primary.main,
  46. backgroundColor: 'transparent',
  47. },
  48. }));
  49. let timer: NodeJS.Timeout | null = null;
  50. // get init search value from url
  51. const { search = '' } = parseLocationSearch(window.location.search);
  52. const Collections = () => {
  53. useNavigationHook(ALL_ROUTER_TYPES.COLLECTIONS);
  54. const { handleAction } = useLoadAndReleaseDialogHook({ type: 'collection' });
  55. const { handleInsertDialog } = useInsertDialogHook();
  56. const [collections, setCollections] = useState<CollectionView[]>([]);
  57. const [searchedCollections, setSearchedCollections] = useState<
  58. CollectionView[]
  59. >([]);
  60. const {
  61. pageSize,
  62. handlePageSize,
  63. currentPage,
  64. handleCurrentPage,
  65. total,
  66. data: collectionList,
  67. } = usePaginationHook(searchedCollections);
  68. const [loading, setLoading] = useState<boolean>(true);
  69. const [selectedCollections, setSelectedCollections] = useState<
  70. CollectionView[]
  71. >([]);
  72. const { setDialog, handleCloseDialog, openSnackBar } =
  73. useContext(rootContext);
  74. const { t: collectionTrans } = useTranslation('collection');
  75. const { t: btnTrans } = useTranslation('btn');
  76. const { t: dialogTrans } = useTranslation('dialog');
  77. const { t: successTrans } = useTranslation('success');
  78. const classes = useStyles();
  79. const LoadIcon = icons.load;
  80. const ReleaseIcon = icons.release;
  81. const InfoIcon = icons.info;
  82. const fetchData = useCallback(async () => {
  83. try {
  84. const res = await CollectionHttp.getCollections();
  85. const statusRes = await CollectionHttp.getCollectionsIndexState();
  86. setLoading(false);
  87. const collections = res.map(v => {
  88. const indexStatus = statusRes.find(item => item._name === v._name);
  89. Object.assign(v, {
  90. nameElement: (
  91. <Link to={`/collections/${v._name}`} className={classes.link}>
  92. <Highlighter
  93. textToHighlight={v._name}
  94. searchWords={[search]}
  95. highlightClassName={classes.highlight}
  96. />
  97. </Link>
  98. ),
  99. statusElement: <Status status={v._status} />,
  100. indexCreatingElement: (
  101. <StatusIcon
  102. type={indexStatus?._indexState || ChildrenStatusType.FINISH}
  103. />
  104. ),
  105. });
  106. return v;
  107. });
  108. // filter collection if url contains search param
  109. const filteredCollections = collections.filter(collection =>
  110. collection._name.includes(search)
  111. );
  112. setCollections(collections);
  113. setSearchedCollections(filteredCollections);
  114. } catch (err) {
  115. setLoading(false);
  116. }
  117. }, [classes.link, classes.highlight]);
  118. useEffect(() => {
  119. fetchData();
  120. }, [fetchData]);
  121. const handleCreateCollection = async (param: CollectionCreateParam) => {
  122. const data: CollectionCreateParam = JSON.parse(JSON.stringify(param));
  123. const vectorType = [DataTypeEnum.BinaryVector, DataTypeEnum.FloatVector];
  124. data.fields = data.fields.map(v =>
  125. vectorType.includes(v.data_type)
  126. ? {
  127. ...v,
  128. type_params: [{ key: 'dim', value: v.dimension }],
  129. }
  130. : v
  131. );
  132. await CollectionHttp.createCollection(data);
  133. handleCloseDialog();
  134. openSnackBar(
  135. successTrans('create', { name: collectionTrans('collection') })
  136. );
  137. fetchData();
  138. };
  139. const handleRelease = async (data: CollectionView) => {
  140. const res = await CollectionHttp.releaseCollection(data._name);
  141. openSnackBar(
  142. successTrans('release', { name: collectionTrans('collection') })
  143. );
  144. fetchData();
  145. return res;
  146. };
  147. const handleLoad = async (data: CollectionView) => {
  148. const res = await CollectionHttp.loadCollection(data._name);
  149. openSnackBar(successTrans('load', { name: collectionTrans('collection') }));
  150. fetchData();
  151. return res;
  152. };
  153. const handleDelete = async () => {
  154. for (const item of selectedCollections) {
  155. await CollectionHttp.deleteCollection(item._name);
  156. }
  157. openSnackBar(
  158. successTrans('delete', { name: collectionTrans('collection') })
  159. );
  160. fetchData();
  161. handleCloseDialog();
  162. setSelectedCollections([]);
  163. };
  164. const handleSearch = (value: string) => {
  165. if (timer) {
  166. clearTimeout(timer);
  167. }
  168. // add loading manually
  169. setLoading(true);
  170. timer = setTimeout(() => {
  171. const searchWords = [value];
  172. const list = value
  173. ? collections.filter(c => c._name.includes(value))
  174. : collections;
  175. const highlightList = list.map(c => {
  176. Object.assign(c, {
  177. nameElement: (
  178. <Link to={`/collections/${c._name}`} className={classes.link}>
  179. <Highlighter
  180. textToHighlight={c._name}
  181. searchWords={searchWords}
  182. highlightClassName={classes.highlight}
  183. />
  184. </Link>
  185. ),
  186. });
  187. return c;
  188. });
  189. setLoading(false);
  190. setSearchedCollections(highlightList);
  191. }, 300);
  192. };
  193. const toolbarConfigs: ToolBarConfig[] = [
  194. {
  195. label: collectionTrans('create'),
  196. onClick: () => {
  197. setDialog({
  198. open: true,
  199. type: 'custom',
  200. params: {
  201. component: (
  202. <CreateCollection handleCreate={handleCreateCollection} />
  203. ),
  204. },
  205. });
  206. },
  207. icon: 'add',
  208. },
  209. // {
  210. // label: btnTrans('insert'),
  211. // onClick: () => {
  212. // const component = (
  213. // <InsertContainer
  214. // collections={[]}
  215. // selectedCollection={''}
  216. // partitions={[]}
  217. // selectedPartition={''}
  218. // schema={[]}
  219. // handleInsert={() => {}}
  220. // />
  221. // );
  222. // handleInsertDialog(component);
  223. // },
  224. // /**
  225. // * insert validation:
  226. // * 1. At least 1 available collection
  227. // * 2. selected collections quantity shouldn't over 1
  228. // */
  229. // disabled: () =>
  230. // collectionList.length === 0 || selectedCollections.length > 1,
  231. // icon: 'upload',
  232. // },
  233. {
  234. type: 'iconBtn',
  235. onClick: () => {
  236. setDialog({
  237. open: true,
  238. type: 'custom',
  239. params: {
  240. component: (
  241. <DeleteTemplate
  242. label={btnTrans('delete')}
  243. title={dialogTrans('deleteTitle', {
  244. type: collectionTrans('collection'),
  245. })}
  246. text={collectionTrans('deleteWarning')}
  247. handleDelete={handleDelete}
  248. />
  249. ),
  250. },
  251. });
  252. },
  253. label: collectionTrans('delete'),
  254. icon: 'delete',
  255. disabled: data => data.length === 0,
  256. },
  257. {
  258. label: 'Search',
  259. icon: 'search',
  260. searchText: search,
  261. onSearch: (value: string) => {
  262. handleSearch(value);
  263. },
  264. },
  265. ];
  266. const colDefinitions: ColDefinitionsType[] = [
  267. {
  268. id: '_id',
  269. align: 'left',
  270. disablePadding: true,
  271. label: collectionTrans('id'),
  272. },
  273. {
  274. id: 'nameElement',
  275. align: 'left',
  276. disablePadding: true,
  277. sortBy: '_name',
  278. label: collectionTrans('name'),
  279. },
  280. {
  281. id: 'statusElement',
  282. align: 'left',
  283. disablePadding: false,
  284. sortBy: '_status',
  285. label: collectionTrans('status'),
  286. },
  287. {
  288. id: '_rowCount',
  289. align: 'left',
  290. disablePadding: false,
  291. label: (
  292. <span className="flex-center">
  293. {collectionTrans('rowCount')}
  294. <CustomToolTip title={collectionTrans('tooltip')}>
  295. <InfoIcon classes={{ root: classes.icon }} />
  296. </CustomToolTip>
  297. </span>
  298. ),
  299. },
  300. {
  301. id: '_desc',
  302. align: 'left',
  303. disablePadding: false,
  304. label: collectionTrans('desc'),
  305. },
  306. {
  307. id: 'indexCreatingElement',
  308. align: 'left',
  309. disablePadding: false,
  310. label: '',
  311. },
  312. {
  313. id: 'action',
  314. align: 'center',
  315. disablePadding: false,
  316. label: '',
  317. showActionCell: true,
  318. isHoverAction: true,
  319. actionBarConfigs: [
  320. {
  321. onClick: (e: React.MouseEvent, row: CollectionView) => {
  322. const cb =
  323. row._status === StatusEnum.unloaded ? handleLoad : handleRelease;
  324. handleAction(row, cb);
  325. },
  326. icon: 'load',
  327. label: 'load',
  328. showIconMethod: 'renderFn',
  329. getLabel: (row: CollectionView) =>
  330. row._status === StatusEnum.loaded ? 'release' : 'load',
  331. renderIconFn: (row: CollectionView) =>
  332. row._status === StatusEnum.loaded ? <ReleaseIcon /> : <LoadIcon />,
  333. },
  334. ],
  335. },
  336. ];
  337. const handleSelectChange = (value: any) => {
  338. setSelectedCollections(value);
  339. };
  340. const handlePageChange = (e: any, page: number) => {
  341. handleCurrentPage(page);
  342. setSelectedCollections([]);
  343. };
  344. const CollectionIcon = icons.navCollection;
  345. return (
  346. <section className="page-wrapper">
  347. {collections.length > 0 || loading ? (
  348. <MilvusGrid
  349. toolbarConfigs={toolbarConfigs}
  350. colDefinitions={colDefinitions}
  351. rows={collectionList}
  352. rowCount={total}
  353. primaryKey="_name"
  354. openCheckBox={true}
  355. showHoverStyle={true}
  356. selected={selectedCollections}
  357. setSelected={handleSelectChange}
  358. page={currentPage}
  359. onChangePage={handlePageChange}
  360. rowsPerPage={pageSize}
  361. setRowsPerPage={handlePageSize}
  362. isLoading={loading}
  363. />
  364. ) : (
  365. <>
  366. <CustomToolBar toolbarConfigs={toolbarConfigs} />
  367. <EmptyCard
  368. wrapperClass={`page-empty-card ${classes.emptyWrapper}`}
  369. icon={<CollectionIcon />}
  370. text={collectionTrans('noData')}
  371. />
  372. </>
  373. )}
  374. </section>
  375. );
  376. };
  377. export default Collections;