Browse Source

feat: use can modify sample data size input, restrict to numeric and max 10000 (#881)

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 1 month ago
parent
commit
504db27f0c

+ 1 - 1
client/src/i18n/cn/insert.ts

@@ -28,7 +28,7 @@ const insertTrans = {
   statusError: '数据导入失败!',
 
   importSampleData: '将样本数据导入到 {{collection}}',
-  sampleDataSize: '选择样本数据大小',
+  sampleDataSize: '选择或者输入样本数据大小, 最大值为 10000',
   importSampleDataDesc: `此功能导入与 Collection Schema 匹配的随机生成的数据。对于测试和开发很有用。点击下载按钮获取数据。`,
   downloadSampleDataCSV: `下载样本 CSV 数据`,
   downloadSampleDataJSON: `下载样本 JSON 数据`,

+ 1 - 1
client/src/i18n/en/insert.ts

@@ -31,7 +31,7 @@ const insertTrans = {
   statusError: 'Import File Failed!',
 
   importSampleData: 'Insert sample data into {{collection}}',
-  sampleDataSize: 'Choose sample data size',
+  sampleDataSize: 'Choose or enter sample data size, max 10000',
   importSampleDataDesc: `This function inserts randomly generated data matching the collection schema. Useful for testing and development. Click the download button to get the data.`,
   downloadSampleDataCSV: `Download Sample CSV Data`,
   downloadSampleDataJSON: `Download Sample JSON Data`,

+ 233 - 202
client/src/pages/dialogs/ImportSampleDialog.tsx

@@ -1,63 +1,22 @@
-import { Theme, Typography, Chip } from '@mui/material';
+import { Typography, Chip, Autocomplete, TextField } from '@mui/material';
 import { FC, useState, useContext } from 'react';
 import { useTranslation } from 'react-i18next';
 import { saveAs } from 'file-saver';
 import DialogTemplate from '@/components/customDialog/DialogTemplate';
-import CustomSelector from '@/components/customSelector/CustomSelector';
 import { rootContext } from '@/context';
 import { InsertStatusEnum } from './insert/consts';
 import { DataService } from '@/http';
 import { LoadSampleParam } from './Types';
 import icons from '@/components/icons/Icons';
-import { makeStyles } from '@mui/styles';
 import type { CollectionObject } from '@server/types';
 
 const DownloadIcon = icons.download;
 
-const getStyles = makeStyles((theme: Theme) => {
-  return {
-    icon: {
-      fontSize: '16px',
-    },
-    downloadBtn: {
-      maxWidth: '240px',
-      margin: theme.spacing(1.5, 1.5, 0, 0),
-    },
-    selectors: {
-      '& .selectorWrapper': {
-        display: 'flex',
-        flexDirection: 'column',
-        marginBottom: theme.spacing(2),
-
-        '& .selectLabel': {
-          fontSize: '14px',
-          lineHeight: '20px',
-
-          color: theme.palette.text.secondary,
-        },
-
-        '& .description': {
-          color: theme.palette.text.secondary,
-          marginBottom: theme.spacing(2),
-          fontSize: 13,
-          lineHeight: 1.5,
-          width: '35vw',
-        },
-      },
-      '& .actions': {
-        display: 'flex',
-        flexDirection: 'column',
-      },
-      '& .download-actions': {
-        display: 'flex',
-        flexDirection: 'row',
-      },
-      '& .selector': {},
-    },
-  };
-});
-
 const sizeOptions = [
+  {
+    label: '10',
+    value: '10',
+  },
   {
     label: '100',
     value: '100',
@@ -76,178 +35,250 @@ const sizeOptions = [
   },
 ];
 
-const ImportSampleDialog: FC<{ collection: CollectionObject; cb?: Function }> =
-  props => {
-    const classes = getStyles();
-    const { collection } = props;
-    const [size, setSize] = useState<string>(sizeOptions[0].value);
-    const [csvFileName, setCsvFileName] = useState<string>(
-      `${collection.collection_name}.sample.${size}.csv`
-    );
-    const [jsonFileName, setJsonFileName] = useState<string>(
-      `${collection.collection_name}.sample.${size}.json`
-    );
-    const [insertStatus, setInsertStatus] = useState<InsertStatusEnum>(
-      InsertStatusEnum.init
-    );
-
-    const { t: insertTrans } = useTranslation('insert');
-    const { t: btnTrans } = useTranslation('btn');
-    const { handleCloseDialog, openSnackBar } = useContext(rootContext);
-    // selected collection name
+const ImportSampleDialog: FC<{
+  collection: CollectionObject;
+  cb?: Function;
+}> = props => {
+  const { collection } = props;
+  const [size, setSize] = useState<string>(sizeOptions[0].value);
+  const [csvFileName, setCsvFileName] = useState<string>(
+    `${collection.collection_name}.sample.${size}.csv`
+  );
+  const [jsonFileName, setJsonFileName] = useState<string>(
+    `${collection.collection_name}.sample.${size}.json`
+  );
+  const [insertStatus, setInsertStatus] = useState<InsertStatusEnum>(
+    InsertStatusEnum.init
+  );
 
-    const handleImportSample = async (
-      collectionName: string,
-      size: string,
-      download: boolean = false,
-      format: 'csv' | 'json' = 'csv'
-    ): Promise<{ result: string | boolean; msg: string }> => {
-      const param: LoadSampleParam = {
-        collection_name: collectionName,
-        size: size,
-        download,
-        format: format,
-      };
-      try {
-        const res = await DataService.importSample(collectionName, param);
-        if (download) {
-          const fileName = format === 'csv' ? csvFileName : jsonFileName;
-          const type =
-            format === 'csv' ? 'text/csv;charset=utf-8;' : 'application/json';
-          const blob = new Blob([res.sampleFile], { type });
-          saveAs(blob, fileName);
-          return { result: res.sampleFile, msg: '' };
-        }
-        await DataService.flush(collectionName);
-        if (props.cb) {
-          await props.cb(collectionName);
-        }
-        return { result: true, msg: '' };
-      } catch (err: any) {
-        const {
-          response: {
-            data: { message },
-          },
-        } = err;
-        return { result: false, msg: message || '' };
-      }
-    };
+  const { t: insertTrans } = useTranslation('insert');
+  const { t: btnTrans } = useTranslation('btn');
+  const { handleCloseDialog, openSnackBar } = useContext(rootContext);
 
-    const onDownloadCSVClicked = async () => {
-      return await handleImportSample(
-        collection.collection_name,
-        size,
-        true,
-        'csv'
-      );
+  const handleImportSample = async (
+    collectionName: string,
+    size: string,
+    download: boolean = false,
+    format: 'csv' | 'json' = 'csv'
+  ): Promise<{ result: string | boolean; msg: string }> => {
+    const param: LoadSampleParam = {
+      collection_name: collectionName,
+      size: size,
+      download,
+      format: format,
     };
+    try {
+      const res = await DataService.importSample(collectionName, param);
+      if (download) {
+        const fileName = format === 'csv' ? csvFileName : jsonFileName;
+        const type =
+          format === 'csv' ? 'text/csv;charset=utf-8;' : 'application/json';
+        const blob = new Blob([res.sampleFile], { type });
+        saveAs(blob, fileName);
+        return { result: res.sampleFile, msg: '' };
+      }
+      await DataService.flush(collectionName);
+      if (props.cb) {
+        await props.cb(collectionName);
+      }
+      return { result: true, msg: '' };
+    } catch (err: any) {
+      const {
+        response: {
+          data: { message },
+        },
+      } = err;
+      return { result: false, msg: message || '' };
+    }
+  };
 
-    const onDownloadJSONClicked = async () => {
-      return await handleImportSample(
-        collection.collection_name,
-        size,
-        true,
-        'json'
-      );
-    };
+  const onDownloadCSVClicked = async () => {
+    return await handleImportSample(
+      collection.collection_name,
+      size,
+      true,
+      'csv'
+    );
+  };
 
-    const importData = async () => {
-      if (insertStatus === InsertStatusEnum.success) {
-        handleCloseDialog();
-        return;
-      }
-      // start loading
-      setInsertStatus(InsertStatusEnum.loading);
-      const { result, msg } = await handleImportSample(
-        collection.collection_name,
-        size
-      );
+  const onDownloadJSONClicked = async () => {
+    return await handleImportSample(
+      collection.collection_name,
+      size,
+      true,
+      'json'
+    );
+  };
 
-      if (!result) {
-        openSnackBar(msg, 'error');
-        setInsertStatus(InsertStatusEnum.init);
-        return;
-      }
-      setInsertStatus(InsertStatusEnum.success);
-      // hide dialog
+  const importData = async () => {
+    if (insertStatus === InsertStatusEnum.success) {
       handleCloseDialog();
-    };
+      return;
+    }
+    // start loading
+    setInsertStatus(InsertStatusEnum.loading);
+    const { result, msg } = await handleImportSample(
+      collection.collection_name,
+      size
+    );
+
+    if (!result) {
+      openSnackBar(msg, 'error');
+      setInsertStatus(InsertStatusEnum.init);
+      return;
+    }
+    setInsertStatus(InsertStatusEnum.success);
+    // hide dialog
+    handleCloseDialog();
+  };
 
-    return (
-      <DialogTemplate
-        title={insertTrans('importSampleData', {
-          collection: collection.collection_name,
-        })}
-        handleClose={handleCloseDialog}
-        confirmLabel={
-          insertStatus === InsertStatusEnum.init
-            ? btnTrans('import')
-            : insertStatus === InsertStatusEnum.loading
+  return (
+    <DialogTemplate
+      title={insertTrans('importSampleData', {
+        collection: collection.collection_name,
+      })}
+      handleClose={handleCloseDialog}
+      confirmLabel={
+        insertStatus === InsertStatusEnum.init
+          ? btnTrans('import')
+          : insertStatus === InsertStatusEnum.loading
             ? btnTrans('importing')
             : insertStatus === InsertStatusEnum.success
-            ? btnTrans('done')
-            : insertStatus
-        }
-        handleConfirm={importData}
-        confirmDisabled={insertStatus === InsertStatusEnum.loading}
-        showActions={true}
-        showCancel={false}
-        // don't show close icon when insert not finish
-        // showCloseIcon={insertStatus !== InsertStatusEnum.loading}
-      >
-        <section className={classes.selectors}>
-          <div className="selectorWrapper">
-            <div className="description">
-              <Typography variant="inherit" component="p">
-                {insertTrans('importSampleDataDesc')}
-              </Typography>
-            </div>
+              ? btnTrans('done')
+              : insertStatus
+      }
+      handleConfirm={importData}
+      confirmDisabled={insertStatus === InsertStatusEnum.loading}
+      showActions={true}
+      showCancel={false}
+    >
+      <section style={{}}>
+        <div
+          className="selectorWrapper"
+          style={{
+            display: 'flex',
+            flexDirection: 'column',
+            marginBottom: 16,
+          }}
+        >
+          <div
+            className="description"
+            style={{
+              color: '#6b7280', // theme.palette.text.secondary
+              marginBottom: 16,
+              fontSize: 13,
+              lineHeight: 1.5,
+              width: '35vw',
+            }}
+          >
+            <Typography variant="inherit" component="p">
+              {insertTrans('importSampleDataDesc')}
+            </Typography>
+          </div>
 
-            <div className="actions">
-              <CustomSelector
-                label={insertTrans('sampleDataSize')}
-                options={sizeOptions}
-                wrapperClass="selector"
-                labelClass="selectLabel"
-                value={size}
-                variant="filled"
-                onChange={(e: { target: { value: unknown } }) => {
-                  const size = e.target.value;
-                  setSize(size as string);
+          <div
+            className="actions"
+            style={{
+              display: 'flex',
+              flexDirection: 'column',
+            }}
+          >
+            <Autocomplete
+              freeSolo
+              options={sizeOptions.map(option => option.value)}
+              value={size}
+              onChange={(event: any, newValue: string | null) => {
+                if (newValue && /^\d+$/.test(newValue)) {
+                  const val = Math.min(Number(newValue), 10000).toString();
+                  setSize(val);
                   setCsvFileName(
-                    `${collection.collection_name}.sample.${size}.csv`
+                    `${collection.collection_name}.sample.${val}.csv`
                   );
                   setJsonFileName(
-                    `${collection.collection_name}.sample.${size}.json`
+                    `${collection.collection_name}.sample.${val}.json`
                   );
-                }}
-              />
-            </div>
+                }
+              }}
+              onInputChange={(event, newInputValue) => {
+                if (/^\d*$/.test(newInputValue)) {
+                  let val = newInputValue;
+                  if (val) {
+                    val = Math.min(Number(val), 10000).toString();
+                  }
+                  setSize(val);
+                  setCsvFileName(
+                    `${collection.collection_name}.sample.${val}.csv`
+                  );
+                  setJsonFileName(
+                    `${collection.collection_name}.sample.${val}.json`
+                  );
+                }
+              }}
+              renderInput={params => (
+                <TextField
+                  {...params}
+                  label={insertTrans('sampleDataSize')}
+                  variant="filled"
+                  inputProps={{
+                    ...params.inputProps,
+                    inputMode: 'numeric',
+                    pattern: '[0-9]*',
+                    max: 10000,
+                  }}
+                  onInput={e => {
+                    const input = e.target as HTMLInputElement;
+                    let val = input.value.replace(/[^0-9]/g, '');
+                    if (val) {
+                      val = Math.min(Number(val), 10000).toString();
+                    }
+                    input.value = val;
+                  }}
+                />
+              )}
+              sx={{ marginBottom: 2 }}
+            />
+          </div>
 
-            <div className="download-actions">
-              <Chip
-                className={classes.downloadBtn}
-                icon={<DownloadIcon />}
-                label={csvFileName}
-                title={csvFileName}
-                variant="outlined"
-                size="small"
-                onClick={onDownloadCSVClicked}
-              />
-              <Chip
-                className={classes.downloadBtn}
-                icon={<DownloadIcon />}
-                label={jsonFileName}
-                title={jsonFileName}
-                variant="outlined"
-                size="small"
-                onClick={onDownloadJSONClicked}
-              />
-            </div>
+          <div
+            className="download-actions"
+            style={{
+              display: 'flex',
+              flexDirection: 'row',
+            }}
+          >
+            <Chip
+              // className={classes.downloadBtn}
+              sx={{
+                maxWidth: '240px',
+                margin: theme => theme.spacing(1.5, 1.5, 0, 0),
+                fontSize: '14px',
+              }}
+              icon={<DownloadIcon sx={{ fontSize: '16px' }} />}
+              label={csvFileName}
+              title={csvFileName}
+              variant="outlined"
+              size="small"
+              onClick={onDownloadCSVClicked}
+            />
+            <Chip
+              // className={classes.downloadBtn}
+              sx={{
+                maxWidth: '240px',
+                margin: theme => theme.spacing(1.5, 1.5, 0, 0),
+                fontSize: '14px',
+              }}
+              icon={<DownloadIcon sx={{ fontSize: '16px' }} />}
+              label={jsonFileName}
+              title={jsonFileName}
+              variant="outlined"
+              size="small"
+              onClick={onDownloadJSONClicked}
+            />
           </div>
-        </section>
-      </DialogTemplate>
-    );
-  };
+        </div>
+      </section>
+    </DialogTemplate>
+  );
+};
 
 export default ImportSampleDialog;