CreateCollectionDialog.tsx 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. import {
  2. makeStyles,
  3. Theme,
  4. Checkbox,
  5. FormControlLabel,
  6. } from '@material-ui/core';
  7. import { FC, useContext, useMemo, useState, ChangeEvent } from 'react';
  8. import { useTranslation } from 'react-i18next';
  9. import DialogTemplate from '@/components/customDialog/DialogTemplate';
  10. import CustomInput from '@/components/customInput/CustomInput';
  11. import CustomSelector from '@/components/customSelector/CustomSelector';
  12. import { ITextfieldConfig } from '@/components/customInput/Types';
  13. import { rootContext } from '@/context';
  14. import { useFormValidation } from '@/hooks';
  15. import { formatForm, TypeEnum } from '@/utils';
  16. import { DataTypeEnum, ConsistencyLevelEnum, DEFAULT_ATTU_DIM } from '@/consts';
  17. import CreateFields from '../collections/CreateFields';
  18. import { CollectionHttp } from '@/http';
  19. import {
  20. CollectionCreateParam,
  21. CollectionCreateProps,
  22. Field,
  23. } from '../collections/Types';
  24. import { CONSISTENCY_LEVEL_OPTIONS } from '../collections/Constants';
  25. const useStyles = makeStyles((theme: Theme) => ({
  26. fieldset: {
  27. width: '100%',
  28. display: 'flex',
  29. alignItems: 'center',
  30. marginBottom: '16px',
  31. '&:nth-last-child(3)': {
  32. flexDirection: 'column',
  33. alignItems: 'flex-start',
  34. marginBottom: '0',
  35. },
  36. '& legend': {
  37. marginBottom: theme.spacing(1),
  38. color: theme.palette.attuGrey.dark,
  39. lineHeight: '20px',
  40. fontSize: '14px',
  41. },
  42. },
  43. input: {
  44. width: '100%',
  45. },
  46. select: {
  47. width: '160px',
  48. '&:first-child': {
  49. marginLeft: 0,
  50. },
  51. },
  52. dialog: {
  53. minWidth: '100%',
  54. },
  55. }));
  56. const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
  57. const classes = useStyles();
  58. const { handleCloseDialog } = useContext(rootContext);
  59. const { t: collectionTrans } = useTranslation('collection');
  60. const { t: btnTrans } = useTranslation('btn');
  61. const { t: warningTrans } = useTranslation('warning');
  62. const [form, setForm] = useState({
  63. collection_name: '',
  64. description: '',
  65. autoID: true,
  66. enableDynamicField: false,
  67. });
  68. const [consistencyLevel, setConsistencyLevel] =
  69. useState<ConsistencyLevelEnum>(ConsistencyLevelEnum.Bounded); // Bounded is the default value of consistency level
  70. const [fields, setFields] = useState<Field[]>([
  71. {
  72. data_type: DataTypeEnum.Int64,
  73. is_primary_key: true,
  74. name: null, // we need hide helpertext at first time, so we use null to detect user enter input or not.
  75. description: '',
  76. isDefault: true,
  77. id: '1',
  78. },
  79. {
  80. data_type: DataTypeEnum.FloatVector,
  81. is_primary_key: false,
  82. name: null,
  83. dimension: DEFAULT_ATTU_DIM,
  84. description: '',
  85. isDefault: true,
  86. id: '2',
  87. },
  88. ]);
  89. const [fieldsValidation, setFieldsValidation] = useState<
  90. {
  91. [x: string]: string | boolean;
  92. }[]
  93. >([
  94. { id: '1', name: false },
  95. { id: '2', name: false, dimension: true },
  96. ]);
  97. const allFieldsValid = useMemo(() => {
  98. return fieldsValidation.every(v => Object.keys(v).every(key => !!v[key]));
  99. }, [fieldsValidation]);
  100. const checkedForm = useMemo(() => {
  101. const { collection_name } = form;
  102. return formatForm({ collection_name });
  103. }, [form]);
  104. const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
  105. const changeIsAutoID = (value: boolean) => {
  106. setForm({
  107. ...form,
  108. autoID: value,
  109. });
  110. };
  111. const changeEnableDynamicField = (
  112. event: ChangeEvent<any>,
  113. value: boolean
  114. ) => {
  115. setForm({
  116. ...form,
  117. enableDynamicField: value,
  118. });
  119. };
  120. const handleInputChange = (key: string, value: string) => {
  121. setForm(v => ({ ...v, [key]: value }));
  122. };
  123. const generalInfoConfigs: ITextfieldConfig[] = [
  124. {
  125. label: collectionTrans('name'),
  126. key: 'collection_name',
  127. value: form.collection_name,
  128. onChange: (value: string) => handleInputChange('collection_name', value),
  129. variant: 'filled',
  130. validations: [
  131. // cannot be empty
  132. {
  133. rule: 'require',
  134. errorText: warningTrans('requiredOnly'),
  135. },
  136. // length <= 255
  137. {
  138. rule: 'range',
  139. extraParam: {
  140. max: 255,
  141. type: 'string',
  142. },
  143. errorText: collectionTrans('nameLengthWarning'),
  144. },
  145. // name can only be combined with letters, number or underscores
  146. {
  147. rule: 'collectionName',
  148. errorText: collectionTrans('nameContentWarning'),
  149. },
  150. // name can not start with number
  151. {
  152. rule: 'firstCharacter',
  153. extraParam: {
  154. invalidTypes: [TypeEnum.number],
  155. },
  156. errorText: collectionTrans('nameFirstLetterWarning'),
  157. },
  158. ],
  159. InputLabelProps: {
  160. shrink: true,
  161. },
  162. size: 'small',
  163. className: classes.input,
  164. },
  165. {
  166. label: collectionTrans('description'),
  167. key: 'description',
  168. value: form.description,
  169. onChange: (value: string) => handleInputChange('description', value),
  170. variant: 'filled',
  171. validations: [],
  172. size: 'small',
  173. className: classes.input,
  174. InputLabelProps: {
  175. shrink: true,
  176. },
  177. },
  178. ];
  179. const handleCreateCollection = async () => {
  180. const vectorType = [DataTypeEnum.BinaryVector, DataTypeEnum.FloatVector];
  181. const param: CollectionCreateParam = {
  182. ...form,
  183. fields: fields.map(v => {
  184. let data: Field = {
  185. name: v.name,
  186. description: v.description,
  187. is_primary_key: v.is_primary_key,
  188. is_partition_key: v.is_partition_key,
  189. data_type: v.data_type,
  190. };
  191. // if we need
  192. if (typeof v.dimension !== undefined) {
  193. data.dimension = Number(v.dimension);
  194. }
  195. if (typeof v.max_length === 'number') {
  196. data.max_length = Number(v.max_length);
  197. }
  198. if (typeof v.element_type !== 'undefined') {
  199. data.element_type = Number(v.element_type);
  200. }
  201. if (typeof v.max_capacity !== 'undefined') {
  202. data.max_capacity = Number(v.max_capacity);
  203. }
  204. v.is_primary_key && (data.autoID = form.autoID);
  205. return vectorType.includes(v.data_type)
  206. ? {
  207. ...data,
  208. type_params: {
  209. // if data type is vector, dimension must exist.
  210. dim: Number(data.dimension!),
  211. },
  212. }
  213. : v.data_type === DataTypeEnum.VarChar ||
  214. v.element_type === DataTypeEnum.VarChar
  215. ? {
  216. ...v,
  217. type_params: {
  218. max_length: Number(v.max_length!),
  219. },
  220. }
  221. : { ...data };
  222. }),
  223. consistency_level: consistencyLevel,
  224. };
  225. await CollectionHttp.createCollection({
  226. ...param,
  227. });
  228. onCreate && onCreate();
  229. handleCloseDialog();
  230. };
  231. return (
  232. <DialogTemplate
  233. title={collectionTrans('createTitle')}
  234. handleClose={() => {
  235. handleCloseDialog();
  236. }}
  237. confirmLabel={btnTrans('create')}
  238. handleConfirm={handleCreateCollection}
  239. confirmDisabled={disabled || !allFieldsValid}
  240. dialogClass={classes.dialog}
  241. >
  242. <>
  243. <fieldset className={classes.fieldset}>
  244. {generalInfoConfigs.map(config => (
  245. <CustomInput
  246. key={config.key}
  247. type="text"
  248. textConfig={config}
  249. checkValid={checkIsValid}
  250. validInfo={validation}
  251. />
  252. ))}
  253. </fieldset>
  254. <fieldset className={classes.fieldset}>
  255. <legend>{collectionTrans('schema')}</legend>
  256. <CreateFields
  257. fields={fields}
  258. setFields={setFields}
  259. setFieldsValidation={setFieldsValidation}
  260. autoID={form.autoID}
  261. setAutoID={changeIsAutoID}
  262. />
  263. </fieldset>
  264. <fieldset className={classes.fieldset}>
  265. <FormControlLabel
  266. checked={form.enableDynamicField}
  267. control={<Checkbox />}
  268. onChange={changeEnableDynamicField}
  269. label={collectionTrans('enableDynamicSchema')}
  270. />
  271. </fieldset>
  272. <fieldset className={classes.fieldset}>
  273. <legend>{collectionTrans('consistency')}</legend>
  274. <CustomSelector
  275. wrapperClass={classes.select}
  276. size="small"
  277. options={CONSISTENCY_LEVEL_OPTIONS}
  278. onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
  279. setConsistencyLevel(e.target.value as ConsistencyLevelEnum);
  280. }}
  281. hiddenlabel={true}
  282. value={consistencyLevel}
  283. variant="filled"
  284. />
  285. </fieldset>
  286. </>
  287. </DialogTemplate>
  288. );
  289. };
  290. export default CreateCollectionDialog;