Schema.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. import { Typography, Tooltip, Box } from '@mui/material';
  2. import { useContext } from 'react';
  3. import { useParams, useNavigate } from 'react-router-dom';
  4. import AttuGrid from '@/components/grid/Grid';
  5. import { ColDefinitionsType } from '@/components/grid/Types';
  6. import { useTranslation } from 'react-i18next';
  7. import Icons from '@/components/icons/Icons';
  8. import { formatFieldType, formatNumber, findKeyValue } from '@/utils';
  9. import { dataContext, rootContext, systemContext } from '@/context';
  10. import IndexTypeElement from './IndexTypeElement';
  11. import { getLabelDisplayedRows } from '@/pages/search/Utils';
  12. import StatusAction from '@/pages/databases/collections/StatusAction';
  13. import CustomToolTip from '@/components/customToolTip/CustomToolTip';
  14. import {
  15. Wrapper,
  16. InfoWrapper,
  17. Card,
  18. InfoRow,
  19. InfoLabel,
  20. InfoValue,
  21. ActionWrapper,
  22. StyledChip,
  23. DataTypeChip,
  24. NameWrapper,
  25. ParamWrapper,
  26. GridWrapper,
  27. } from './StyledComponents';
  28. import LoadCollectionDialog from '@/pages/dialogs/LoadCollectionDialog';
  29. import RenameCollectionDialog from '@/pages/dialogs/RenameCollectionDialog';
  30. import EditMmapDialog from '@/pages/dialogs/EditMmapDialog';
  31. import DropCollectionDialog from '@/pages/dialogs/DropCollectionDialog';
  32. import CopyButton from '@/components/advancedSearch/CopyButton';
  33. import RefreshButton from '@/components/customButton/RefreshButton';
  34. import { CollectionService } from '@/http';
  35. import type { FieldObject } from '@server/types';
  36. const Overview = () => {
  37. const { fetchCollection, collections, loading, database } =
  38. useContext(dataContext);
  39. const { data } = useContext(systemContext);
  40. const { setDialog } = useContext(rootContext);
  41. const { collectionName = '' } = useParams<{ collectionName: string }>();
  42. const navigate = useNavigate();
  43. const { t: collectionTrans } = useTranslation('collection');
  44. const { t: indexTrans } = useTranslation('index');
  45. const { t: btnTrans } = useTranslation('btn');
  46. const { t: commonTrans } = useTranslation();
  47. const consistencyTooltipsMap: Record<string, string> = {
  48. Strong: collectionTrans('consistencyStrongTooltip'),
  49. Bounded: collectionTrans('consistencyBoundedTooltip'),
  50. Session: collectionTrans('consistencySessionTooltip'),
  51. Eventually: collectionTrans('consistencyEventuallyTooltip'),
  52. };
  53. // get collection
  54. const collection = collections.find(
  55. c => c.collection_name === collectionName
  56. );
  57. // fetch collection if not loaded
  58. if (collection && !collection.schema) {
  59. fetchCollection(collectionName);
  60. }
  61. // get fields
  62. const fields = collection?.schema?.fields || [];
  63. const colDefinitions: ColDefinitionsType[] = [
  64. {
  65. id: 'name',
  66. align: 'left',
  67. disablePadding: true,
  68. formatter(f: FieldObject) {
  69. return (
  70. <NameWrapper>
  71. <Typography
  72. variant="body1"
  73. sx={{
  74. color: f.name === '$meta' ? 'secondary.dark' : 'inherit',
  75. fontStyle: f.name === '$meta' ? 'italic' : 'inherit',
  76. }}
  77. >
  78. {f.name}
  79. </Typography>
  80. {f.name === '$meta' && (
  81. <StyledChip size="small" label="Dynamic field" />
  82. )}
  83. {f.is_primary_key && (
  84. <StyledChip
  85. size="small"
  86. label="PK"
  87. sx={{
  88. backgroundColor: theme => theme.palette.secondary.light,
  89. fontWeight: 600,
  90. fontSize: '12px',
  91. height: '20px',
  92. '& .MuiChip-label': {
  93. px: 1,
  94. },
  95. border: theme => `1px solid ${theme.palette.secondary.main}`,
  96. }}
  97. />
  98. )}
  99. {f.is_partition_key && (
  100. <StyledChip size="small" label="Partition key" />
  101. )}
  102. {findKeyValue(f.type_params, 'enable_match') && (
  103. <StyledChip size="small" label={collectionTrans('enableMatch')} />
  104. )}
  105. {findKeyValue(f.type_params, 'enable_analyzer') === 'true' && (
  106. <Tooltip
  107. title={findKeyValue(f.type_params, 'analyzer_params') as string}
  108. arrow
  109. >
  110. <StyledChip
  111. size="small"
  112. label={collectionTrans('analyzer')}
  113. onClick={() => {
  114. const textToCopy = findKeyValue(
  115. f.type_params,
  116. 'analyzer_params'
  117. );
  118. navigator.clipboard.writeText(textToCopy as string);
  119. }}
  120. />
  121. </Tooltip>
  122. )}
  123. {(findKeyValue(f.type_params, 'mmap.enabled') === 'true' ||
  124. isCollectionMmapEnabled) && (
  125. <Tooltip title={collectionTrans('mmapTooltip')} arrow>
  126. <StyledChip
  127. size="small"
  128. label={collectionTrans('mmapEnabled')}
  129. onClick={() => {
  130. setDialog({
  131. open: true,
  132. type: 'custom',
  133. params: {
  134. component: (
  135. <EditMmapDialog
  136. collection={collection!}
  137. cb={async () => {
  138. fetchCollection(collectionName);
  139. }}
  140. />
  141. ),
  142. },
  143. });
  144. }}
  145. />
  146. </Tooltip>
  147. )}
  148. {f.function && (
  149. <Tooltip title={JSON.stringify(f.function)} arrow>
  150. <StyledChip
  151. size="small"
  152. label={`
  153. ${
  154. f.is_function_output
  155. ? `<- ${f.function.type}(${f.function.input_field_names})`
  156. : ` ${collectionTrans('function')}: ${f.function.type}`
  157. }`}
  158. onClick={() => {
  159. const textToCopy = JSON.stringify(f.function);
  160. navigator.clipboard.writeText(textToCopy as string);
  161. }}
  162. />
  163. </Tooltip>
  164. )}
  165. </NameWrapper>
  166. );
  167. },
  168. label: collectionTrans('fieldName'),
  169. sortBy: 'name',
  170. },
  171. {
  172. id: 'data_type',
  173. align: 'left',
  174. disablePadding: false,
  175. formatter(f) {
  176. return (
  177. <Typography variant="body1" component="div">
  178. <DataTypeChip size="small" label={formatFieldType(f)} />
  179. </Typography>
  180. );
  181. },
  182. label: collectionTrans('fieldType'),
  183. },
  184. {
  185. id: 'nullable',
  186. align: 'left',
  187. disablePadding: false,
  188. label: collectionTrans('nullable'),
  189. formatter(f) {
  190. return (
  191. <Typography variant="body1">
  192. {f.nullable ? (
  193. <Icons.check sx={{ fontSize: '11px', ml: 0.5 }} />
  194. ) : (
  195. <Icons.cross2 sx={{ fontSize: '11px', ml: 0.5 }} />
  196. )}
  197. </Typography>
  198. );
  199. },
  200. },
  201. {
  202. id: 'default_value',
  203. align: 'left',
  204. disablePadding: false,
  205. label: collectionTrans('defaultValue'),
  206. formatter(f) {
  207. return (
  208. <Typography variant="body1">{f.default_value || '--'}</Typography>
  209. );
  210. },
  211. },
  212. {
  213. id: 'name',
  214. align: 'left',
  215. disablePadding: true,
  216. label: indexTrans('indexName'),
  217. formatter(f) {
  218. return <Typography variant="body1">{f.index?.index_name}</Typography>;
  219. },
  220. },
  221. {
  222. id: 'name',
  223. align: 'left',
  224. disablePadding: true,
  225. label: indexTrans('type'),
  226. notSort: true,
  227. formatter(f) {
  228. return (
  229. <Typography variant="body1" component="div">
  230. <IndexTypeElement
  231. field={f}
  232. collectionName={collectionName}
  233. cb={async () => {
  234. await fetchCollection(collectionName);
  235. }}
  236. />
  237. </Typography>
  238. );
  239. },
  240. },
  241. {
  242. id: 'name',
  243. align: 'left',
  244. disablePadding: false,
  245. label: indexTrans('param'),
  246. notSort: true,
  247. formatter(f) {
  248. return (
  249. <Typography variant="body1" component="div">
  250. {f.index ? (
  251. <ParamWrapper>
  252. {f.index.indexParameterPairs.length > 0 ? (
  253. f.index.indexParameterPairs.map((p: any) =>
  254. p.value ? (
  255. <div key={p.key + p.value}>
  256. <span className="param">
  257. <Typography variant="body1" className="key">
  258. {`${p.key}:`}
  259. </Typography>
  260. <Typography variant="body1" className="value">
  261. {p.value}
  262. </Typography>
  263. </span>
  264. </div>
  265. ) : (
  266. ''
  267. )
  268. )
  269. ) : (
  270. <>--</>
  271. )}
  272. </ParamWrapper>
  273. ) : (
  274. <>--</>
  275. )}
  276. </Typography>
  277. );
  278. },
  279. },
  280. {
  281. id: 'description',
  282. align: 'left',
  283. disablePadding: false,
  284. label: indexTrans('desc'),
  285. formatter(f) {
  286. return <Typography variant="body1">{f.description || '--'}</Typography>;
  287. },
  288. },
  289. ];
  290. // only show create index element when there is only one vector field
  291. let CreateIndexElement = null;
  292. if (
  293. collection &&
  294. collection.schema &&
  295. collection.schema.vectorFields.length === 1
  296. ) {
  297. CreateIndexElement = (
  298. <IndexTypeElement
  299. field={
  300. (collection.schema && collection.schema.vectorFields[0]) ||
  301. ({} as FieldObject)
  302. }
  303. collectionName={collectionName}
  304. cb={async () => {
  305. await fetchCollection(collectionName);
  306. }}
  307. />
  308. );
  309. }
  310. // enable modify replica if there are more than one query node
  311. const enableModifyReplica =
  312. data && data.queryNodes && data.queryNodes.length > 1;
  313. // if is autoID enabled
  314. const isAutoIDEnabled = collection?.schema?.fields.some(
  315. f => f.autoID === true
  316. );
  317. // check if collection is mmap enabled
  318. const isCollectionMmapEnabled = collection?.properties?.some((p: any) => {
  319. return p.key === 'mmap.enabled' && p.value === 'true';
  320. });
  321. // get loading state label
  322. return (
  323. <Wrapper>
  324. {collection && (
  325. <InfoWrapper>
  326. <Card>
  327. <InfoRow>
  328. <InfoLabel>{collectionTrans('name')}</InfoLabel>
  329. <InfoValue>
  330. <Tooltip title={collection.collection_name} arrow>
  331. <Typography
  332. variant="body1"
  333. sx={{ fontWeight: 500 }}
  334. className="truncate"
  335. >
  336. {collection.collection_name}
  337. </Typography>
  338. </Tooltip>
  339. <ActionWrapper>
  340. <RefreshButton
  341. sx={{
  342. '& svg': {
  343. width: 16,
  344. height: 16,
  345. },
  346. }}
  347. onClick={async () => {
  348. setDialog({
  349. open: true,
  350. type: 'custom',
  351. params: {
  352. component: (
  353. <RenameCollectionDialog
  354. collection={collection}
  355. cb={async newName => {
  356. await fetchCollection(newName);
  357. navigate(
  358. `/databases/${database}/${newName}/schema`
  359. );
  360. }}
  361. />
  362. ),
  363. },
  364. });
  365. }}
  366. tooltip={btnTrans('rename')}
  367. icon={<Icons.edit />}
  368. />
  369. <CopyButton
  370. sx={{
  371. '& svg': {
  372. width: 16,
  373. height: 16,
  374. },
  375. }}
  376. copyValue={collection.collection_name}
  377. />
  378. <RefreshButton
  379. sx={{
  380. '& svg': {
  381. width: 16,
  382. height: 16,
  383. },
  384. }}
  385. onClick={async () => {
  386. const res =
  387. await CollectionService.describeCollectionUnformatted(
  388. collection.collection_name
  389. );
  390. const json = JSON.stringify(res, null, 2);
  391. const blob = new Blob([json], {
  392. type: 'application/json',
  393. });
  394. const url = URL.createObjectURL(blob);
  395. const a = document.createElement('a');
  396. a.href = url;
  397. a.download = `${collection.collection_name}.json`;
  398. a.click();
  399. }}
  400. tooltip={btnTrans('downloadSchema')}
  401. icon={<Icons.download />}
  402. />
  403. <RefreshButton
  404. sx={{
  405. '& svg': {
  406. width: 16,
  407. height: 16,
  408. },
  409. }}
  410. onClick={() => {
  411. setDialog({
  412. open: true,
  413. type: 'custom',
  414. params: {
  415. component: (
  416. <DropCollectionDialog
  417. collections={[collection]}
  418. onDelete={() => {
  419. navigate(`/databases/${database}`);
  420. }}
  421. />
  422. ),
  423. },
  424. });
  425. }}
  426. tooltip={btnTrans('drop')}
  427. icon={<Icons.cross />}
  428. />
  429. <RefreshButton
  430. sx={{
  431. '& svg': {
  432. width: 16,
  433. height: 16,
  434. },
  435. }}
  436. tooltip={btnTrans('refresh')}
  437. onClick={async () => {
  438. await fetchCollection(collectionName);
  439. }}
  440. icon={<Icons.refresh />}
  441. />
  442. </ActionWrapper>
  443. </InfoValue>
  444. </InfoRow>
  445. <InfoRow>
  446. <InfoLabel>{collectionTrans('description')}</InfoLabel>
  447. <InfoValue>
  448. <Typography variant="body1">
  449. {collection?.description || '--'}
  450. </Typography>
  451. </InfoValue>
  452. </InfoRow>
  453. <InfoRow>
  454. <InfoLabel>{collectionTrans('createdTime')}</InfoLabel>
  455. <InfoValue>
  456. <Typography variant="body1">
  457. {new Date(collection.createdTime).toLocaleString()}
  458. </Typography>
  459. </InfoValue>
  460. </InfoRow>
  461. </Card>
  462. <Card>
  463. <InfoRow>
  464. <InfoLabel>{collectionTrans('status')}</InfoLabel>
  465. <InfoValue>
  466. <StatusAction
  467. status={collection.status}
  468. percentage={collection.loadedPercentage}
  469. collection={collection}
  470. showExtraAction={false}
  471. showLoadButton={true}
  472. createIndexElement={CreateIndexElement}
  473. />
  474. </InfoValue>
  475. </InfoRow>
  476. <InfoRow>
  477. <InfoLabel>
  478. {collectionTrans('replica')}
  479. <CustomToolTip title={collectionTrans('replicaTooltip')}>
  480. <Icons.question
  481. sx={{
  482. width: 12,
  483. height: 12,
  484. position: 'relative',
  485. top: '2px',
  486. right: '-4px',
  487. }}
  488. />
  489. </CustomToolTip>
  490. </InfoLabel>
  491. <InfoValue>
  492. <Typography variant="body1">
  493. {collection.loaded ? collection.replicas?.length : '...'}
  494. </Typography>
  495. {collection.loaded && enableModifyReplica && (
  496. <RefreshButton
  497. sx={{
  498. '& svg': {
  499. width: 12,
  500. height: 12,
  501. },
  502. }}
  503. tooltip={collectionTrans('modifyReplicaTooltip')}
  504. onClick={() => {
  505. setDialog({
  506. open: true,
  507. type: 'custom',
  508. params: {
  509. component: (
  510. <LoadCollectionDialog
  511. collection={collection}
  512. isModifyReplica={true}
  513. />
  514. ),
  515. },
  516. });
  517. }}
  518. icon={<Icons.settings />}
  519. />
  520. )}
  521. </InfoValue>
  522. </InfoRow>
  523. <InfoRow>
  524. <InfoLabel>
  525. {collection.loaded ? (
  526. collectionTrans('count')
  527. ) : (
  528. <>
  529. {collectionTrans('rowCount')}
  530. <CustomToolTip title={collectionTrans('entityCountInfo')}>
  531. <Icons.question
  532. sx={{
  533. width: 12,
  534. height: 12,
  535. position: 'relative',
  536. top: '2px',
  537. right: '-4px',
  538. }}
  539. />
  540. </CustomToolTip>
  541. </>
  542. )}
  543. </InfoLabel>
  544. <InfoValue>
  545. <Typography variant="body1">
  546. {formatNumber(Number(collection?.rowCount || '0'))}
  547. </Typography>
  548. </InfoValue>
  549. </InfoRow>
  550. </Card>
  551. <Card>
  552. <InfoRow>
  553. <InfoLabel>{collectionTrans('features')}</InfoLabel>
  554. <InfoValue>
  555. <Box className="features-wrapper">
  556. {isAutoIDEnabled && (
  557. <StyledChip
  558. sx={{ border: 'none' }}
  559. label={collectionTrans('autoId')}
  560. size="small"
  561. />
  562. )}
  563. <Tooltip
  564. title={
  565. collection.consistency_level
  566. ? consistencyTooltipsMap[
  567. collection.consistency_level
  568. ] || ''
  569. : ''
  570. }
  571. arrow
  572. >
  573. <StyledChip
  574. sx={{ border: 'none' }}
  575. label={`${collectionTrans('consistency')}: ${collection.consistency_level}`}
  576. size="small"
  577. />
  578. </Tooltip>
  579. <Tooltip title={collectionTrans('mmapTooltip')} arrow>
  580. <StyledChip
  581. label={collectionTrans('mmapSettings')}
  582. size="small"
  583. onDelete={async () => {
  584. setDialog({
  585. open: true,
  586. type: 'custom',
  587. params: {
  588. component: (
  589. <EditMmapDialog
  590. collection={collection}
  591. cb={async () => {
  592. fetchCollection(collectionName);
  593. }}
  594. />
  595. ),
  596. },
  597. });
  598. }}
  599. deleteIcon={
  600. <Icons.settings
  601. sx={{
  602. width: 12,
  603. height: 12,
  604. }}
  605. />
  606. }
  607. />
  608. </Tooltip>
  609. </Box>
  610. </InfoValue>
  611. </InfoRow>
  612. </Card>
  613. </InfoWrapper>
  614. )}
  615. <GridWrapper>
  616. <AttuGrid
  617. toolbarConfigs={[]}
  618. colDefinitions={colDefinitions}
  619. rows={fields}
  620. rowCount={fields.length}
  621. primaryKey="fieldID"
  622. showHoverStyle={false}
  623. isLoading={loading}
  624. openCheckBox={false}
  625. showPagination={false}
  626. labelDisplayedRows={getLabelDisplayedRows(
  627. commonTrans(`grid.${fields.length > 1 ? 'fields' : 'field'}`)
  628. )}
  629. />
  630. </GridWrapper>
  631. </Wrapper>
  632. );
  633. };
  634. export default Overview;