Partitions.tsx 11 KB

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