CreateCollectionDialog.tsx 8.3 KB

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