setState({ activeItem: item })}
From 6d10007d59ccb0e4873787f3e365857b0f6acc7a Mon Sep 17 00:00:00 2001
From: hetao92 <18328704+hetao92@users.noreply.github.com>
Date: Thu, 30 Mar 2023 03:45:03 +0000
Subject: [PATCH 15/17] feat: add template validate tips
---
app/config/locale/en-US.json | 13 +-
app/config/locale/zh-CN.json | 13 +-
.../Import/TaskList/TemplateModal/index.tsx | 125 +++++++++++++++---
3 files changed, 129 insertions(+), 22 deletions(-)
diff --git a/app/config/locale/en-US.json b/app/config/locale/en-US.json
index eae07564..65a94a55 100644
--- a/app/config/locale/en-US.json
+++ b/app/config/locale/en-US.json
@@ -218,7 +218,7 @@
"reUpload": "Re-upload",
"fileNotExist": "{name} file does not exist!",
"importYaml": "Import the YAML file",
- "templateMatchError": "The {type} in the configuration does not match the current login account/host",
+ "templateMatchError": "The user in the configuration does not match the current login account",
"uploadSuccessfully": "Upload files successfully.",
"fileSizeLimit": "The file is too large and exceeds the upload limit({size}), please upload the file to the data/upload directory under the installation directory via scp",
"noHttp": "The address in the configuration file does not support http protocol, please remove http(s)://",
@@ -306,7 +306,16 @@
"awsTip": "https://s3.us-east-2.amazonaws.com",
"ossTip": "https://oss-cn-hangzhou.aliyuncs.com",
"cosTip": "https://cos.ap-shanghai.myqcloud.com",
- "customizeTip": "http://127.0.0.1:9000"
+ "customizeTip": "http://127.0.0.1:9000",
+ "addressRequired": "Please enter the import address in the configuration file",
+ "usernameRequired": "Please enter the username in the configuration file",
+ "passwordRequired": "Please enter the password in the configuration file",
+ "s3AccessKeyRequired": "Please enter the s3 accessKey in the configuration file",
+ "s3SecretKeyRequired": "Please enter the s3 secretKey in the configuration file",
+ "sftpUsernameRequired": "Please enter the sftp username in the configuration file",
+ "sftpPasswordRequired": "Please enter the sftp password in the configuration file",
+ "ossAccessKeyRequired": "Please enter the oss accessKey in the configuration file",
+ "ossSecretKeyRequired": "Please enter the oss secretKey in the configuration file"
},
"schema": {
"spaceList": "Graph Space List",
diff --git a/app/config/locale/zh-CN.json b/app/config/locale/zh-CN.json
index e03943ce..4ef27324 100644
--- a/app/config/locale/zh-CN.json
+++ b/app/config/locale/zh-CN.json
@@ -218,7 +218,7 @@
"reUpload": "重新上传",
"fileNotExist": "文件 {name} 不存在",
"importYaml": "导入 YAML 文件",
- "templateMatchError": "配置中的{type}与当前登录账号/地址不匹配",
+ "templateMatchError": "配置中的用户姓名与当前登录账号不一致",
"uploadSuccessfully": "上传文件成功",
"fileSizeLimit": "文件过大,超过上传限制({size}),请将文件通过 scp 的方式上传到安装目录下的 data/upload 目录",
"noHttp": "配置文件中的 address 不支持携带 http 协议,请去除 http(s)://",
@@ -306,7 +306,16 @@
"awsTip": "https://s3.us-east-2.amazonaws.com",
"ossTip": "https://oss-cn-hangzhou.aliyuncs.com",
"cosTip": "https://cos.ap-shanghai.myqcloud.com",
- "customizeTip": "http://127.0.0.1:9000"
+ "customizeTip": "http://127.0.0.1:9000",
+ "addressRequired": "请在配置文件中输入导入地址",
+ "usernameRequired": "请在配置文件中输入用户名",
+ "passwordRequired": "请在配置文件中输入密码",
+ "s3AccessKeyRequired": "请在配置文件中输入 s3 的 accessKey",
+ "s3SecretKeyRequired": "请在配置文件中输入 s3 的 secretKey",
+ "sftpUsernameRequired": "请在配置文件中输入 sftp 的用户名",
+ "sftpPasswordRequired": "请在配置文件中输入 sftp 的密码",
+ "ossAccessKeyRequired": "请在配置文件中输入 oss 的 accessKey",
+ "ossSecretKeyRequired": "请在配置文件中输入 oss 的 secretKey"
},
"schema": {
"spaceList": "图空间列表",
diff --git a/app/pages/Import/TaskList/TemplateModal/index.tsx b/app/pages/Import/TaskList/TemplateModal/index.tsx
index f3109ecd..09025242 100644
--- a/app/pages/Import/TaskList/TemplateModal/index.tsx
+++ b/app/pages/Import/TaskList/TemplateModal/index.tsx
@@ -1,5 +1,6 @@
+/* eslint-disable no-template-curly-in-string */
import { Button, Form, Input, Modal, Spin, Upload, message } from 'antd';
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
import Icon from '@app/components/Icon';
import { Link } from 'react-router-dom';
import { observer } from 'mobx-react-lite';
@@ -26,34 +27,122 @@ const TemplateModal = (props: IProps) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [config, setConfig] = useState('');
- const { intl } = useI18n();
+ const { intl, currentLocale } = useI18n();
const { dataImport: { importTask }, files: { getFiles, fileList } } = useStore();
+ const files = useMemo(() => fileList.map(item => item.name), [fileList]);
useEffect(() => {
getFiles();
}, []);
+ const emptyTextMap = useMemo(() => ({
+ address: {
+ text: '${YOUR_NEBULA_ADDRESS}',
+ message: intl.get('import.addressRequired'),
+ },
+ user: {
+ text: '${YOUR_NEBULA_NAME}',
+ message: intl.get('import.usernameRequired'),
+ },
+ password: {
+ text: '${YOUR_NEBULA_PASSWORD}',
+ message: intl.get('import.passwordRequired'),
+ },
+ s3AccessKey: {
+ text: '${YOUR_S3_ACCESS_KEY}',
+ message: intl.get('import.s3AccessKeyRequired'),
+ },
+ s3SecretKey: {
+ text: '${YOUR_S3_SECRET_KEY}',
+ message: intl.get('import.s3SecretKeyRequired'),
+ },
+ sftpUsername: {
+ text: '${YOUR_SFTP_USER}',
+ message: intl.get('import.sftpUsernameRequired'),
+ },
+ sftpPassword: {
+ text: '${YOUR_SFTP_PASSWORD}',
+ message: intl.get('import.sftpPasswordRequired'),
+ },
+ ossAccessKey: {
+ text: '${YOUR_OSS_ACCESS_KEY}',
+ message: intl.get('import.ossAccessKeyRequired'),
+ },
+ ossSecretKey: {
+ text: '${YOUR_OSS_SECRET_KEY}',
+ message: intl.get('import.ossSecretKeyRequired'),
+ },
+ }), [currentLocale]);
+ const validateEmpty = useCallback((key, value) => {
+ if(!value || value === emptyTextMap[key].text) {
+ return emptyTextMap[key].message;
+ }
+ }, []);
+
+ const validateAddress = useCallback((client) => {
+ const msg = validateEmpty('address', client.address);
+ console.log('msg', msg);
+ if(msg) return msg;
+ const address = client.address.split(',');
+ if(address.some(i => i.startsWith('http'))) {
+ return intl.get('import.noHttp');
+ }
+ if (!address.includes(host)) {
+ return intl.get('import.addressMatch');
+ }
+ }, [currentLocale]);
+
+ const validateUser = useCallback((client) => {
+ const msg = validateEmpty('user', client.user);
+ if(msg) return msg;
+ if (client.user !== username) {
+ return intl.get('import.templateMatchError');
+ }
+ }, [currentLocale, username]);
+ const validatePassword = useCallback((client) => validateEmpty('password', client.password), []);
+ const validateS3 = useCallback((s3) => validateEmpty('s3AccessKey', s3.accessKey) || validateEmpty('s3SecretKey', s3.secretKey), []);
+ const validateSftp = useCallback((sftp) => validateEmpty('sftpUsername', sftp.username) || validateEmpty('sftpPassword', sftp.password), []);
+ const validateOSS = useCallback((oss) => validateEmpty('ossAccessKey', oss.accessKey) || validateEmpty('ossSecretKey', oss.secretKey), []);
+ const validateClient = useCallback((content) => {
+ const client = content.client || {};
+ return validateAddress(client) || validateUser(client) || validatePassword(client);
+ }, [validateAddress, validateUser, validatePassword]);
+ const validateSources = useCallback((content) => {
+ const sources = content.sources || [];
+ let err;
+ sources.some((file) => {
+ if(file.path && !files.includes(file.path)) {
+ err = intl.get('import.fileNotExist', { name: file.path });
+ return !!err;
+ }
+ if(file.s3) {
+ err = validateS3(file.s3);
+ return !!err;
+ }
+ if(file.sftp) {
+ err = validateSftp(file.sftp);
+ return !!err;
+ }
+ if(file.oss) {
+ err = validateOSS(file.oss);
+ return !!err;
+ }
+ });
+ return err;
+ }, [validateS3, validateSftp, validateOSS, files]);
+
const handleFileImport = async ({ file }) => {
await setLoading(true);
- const files = fileList.map(item => item.name);
const content = await readFileContent(file);
try {
const parseContent = yaml.load(content);
if(typeof parseContent === 'object') {
- const client = parseContent.client || {};
- const address = client.address.split(',');
- if(client.address.startsWith('http')) {
- throw new Error(intl.get('import.noHttp'));
- }
- if(!address.includes(host)) {
- throw new Error(intl.get('import.addressMatch'));
- }
- if(client.user !== username) {
- throw new Error(intl.get('import.templateMatchError', { type: 'username' }));
- }
- parseContent.sources?.forEach(file => {
- if(file.path && !files.includes(file.path)) {
- throw new Error(intl.get('import.fileNotExist', { name: file.path }));
- }
+ let err;
+ [validateClient, validateSources].some((strategy) => {
+ err = strategy(parseContent);
+ return !!err;
});
+ if(err) {
+ throw new Error(err);
+ }
// empty props in yaml will converted to null, but its required in nebula-importer
parseContent.sources.forEach(file => {
if(file.edges) {
From 793b82e13f5ff9b921bc538266e43bbac6818eaa Mon Sep 17 00:00:00 2001
From: hetao92 <18328704+hetao92@users.noreply.github.com>
Date: Thu, 30 Mar 2023 10:32:55 +0000
Subject: [PATCH 16/17] mod: code review
---
app/components/FileConfigSetting/index.tsx | 2 +-
app/interfaces/import.ts | 12 ++++----
.../SchemaConfig/FileMapping/FileSelect.tsx | 20 ++++++-------
.../FileMapping/FileSelectModal.tsx | 10 +++----
.../SchemaConfig/FileMapping/index.tsx | 14 ++++++---
.../TaskList/TaskItem/LogModal/index.tsx | 4 +--
app/pages/Import/TaskList/TaskItem/index.tsx | 30 ++++++++-----------
app/pages/Import/TaskList/index.tsx | 2 +-
8 files changed, 47 insertions(+), 47 deletions(-)
diff --git a/app/components/FileConfigSetting/index.tsx b/app/components/FileConfigSetting/index.tsx
index 5a5ac0d2..211c31ff 100644
--- a/app/components/FileConfigSetting/index.tsx
+++ b/app/components/FileConfigSetting/index.tsx
@@ -60,7 +60,7 @@ const FileConfigSetting = (props: IProps) => {
if(!activeItem) return;
setState({ loading: true });
let content = [];
- if(activeItem.sample) {
+ if(activeItem.sample !== undefined) {
readString(activeItem.sample, {
delimiter: activeItem.delimiter || ',',
worker: true,
diff --git a/app/interfaces/import.ts b/app/interfaces/import.ts
index 7ef3de23..9fd4b861 100644
--- a/app/interfaces/import.ts
+++ b/app/interfaces/import.ts
@@ -1,12 +1,12 @@
import { RcFile } from 'antd/lib/upload';
export enum ITaskStatus {
- 'StatusFinished' = 'Success',
- 'StatusStoped' = 'Stopped',
- 'StatusProcessing' = 'Running',
- 'StatusNotExisted' = 'NotExisted',
- 'StatusAborted' = 'Failed',
- 'StatusPending' = 'Pending',
+ 'Finished' = 'Success',
+ 'Stoped' = 'Stopped',
+ 'Processing' = 'Running',
+ 'NotExisted' = 'NotExisted',
+ 'Aborted' = 'Failed',
+ 'Pending' = 'Pending',
}
export interface ITaskStats {
diff --git a/app/pages/Import/TaskCreate/SchemaConfig/FileMapping/FileSelect.tsx b/app/pages/Import/TaskCreate/SchemaConfig/FileMapping/FileSelect.tsx
index 3223f0a3..92fae6a1 100644
--- a/app/pages/Import/TaskCreate/SchemaConfig/FileMapping/FileSelect.tsx
+++ b/app/pages/Import/TaskCreate/SchemaConfig/FileMapping/FileSelect.tsx
@@ -1,5 +1,5 @@
import { ArrowLeftOutlined, FileTextFilled, FolderFilled, SyncOutlined } from '@ant-design/icons';
-import { IDatasourceType } from '@app/interfaces/datasource';
+import { IDatasourceType, IS3Platform } from '@app/interfaces/datasource';
import { useStore } from '@app/stores';
import { ICachedStore } from '@app/stores/datasource';
import { useBatchState } from '@app/utils';
@@ -133,19 +133,19 @@ const FileSelect = observer((props: IFileSelect) => {
dropdownMatchSelectWidth={false}>
{options.map((item) => {
- let endpoint = '';
let label = '';
+ let platform = '';
if (item.type === IDatasourceType.S3) {
- endpoint = item.s3Config.endpoint;
- label = intl.get('import.s3');
+ label = item.s3Config.bucket;
+ platform = item.platform !== IS3Platform.Customize ? item.platform.toUpperCase() : intl.get('import.s3');
} else {
- endpoint = item.sftpConfig.host + ':' + item.sftpConfig.port;
- label = intl.get('import.sftp');
+ label = item.sftpConfig.host + ':' + item.sftpConfig.port;
+ platform = intl.get('import.sftp');
}
- return