This is an automated email from the ASF dual-hosted git repository.

liuhongyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 6028d9c5 feat(document): add swagger import functionality (#535)
6028d9c5 is described below

commit 6028d9c56deb7fa9860798021c84adf80b02c73a
Author: Jesen Kwan <guan.zhenx...@foxmail.com>
AuthorDate: Sat Jul 19 17:25:21 2025 +0800

    feat(document): add swagger import functionality (#535)
    
    * feat(document): add swagger import functionality
    
    - Add ImportSwaggerModal component for importing swagger documents
    - Add swagger import button and modal in API documentation page
    - Support i18n for swagger import feature
    - Add importSwagger API function
    
    (cherry picked from commit 5913459ce18883be047cc534d45b8f57b8ea932c)
    
    * fix: resolve ESLint errors and code formatting issues
    
    - Fix PropTypes definition errors in ImportSwaggerModal.js
      - Move PropTypes validators from defaultProps to propTypes
      - Add corresponding default values for all PropTypes
      - Remove unused formLoaded prop
    
    - Fix code formatting issues
      - Standardize line breaks for long strings
      - Fix object property formatting
      - Use consistent double quotes
    
    - Clean up unused variables and parameters
      - Remove unused swaggerForm variable in SearchApi.js
      - Remove unused tagId and values parameters in functions
    
    - Improve code quality
      - Replace console statements with appropriate comments
      - Fix console.log and console.error in McpServer components
    
    All ESLint checks now pass and code meets project standards.
---
 src/locales/en-US.json                             |  11 ++
 src/locales/zh-CN.json                             |  11 ++
 .../Document/components/ImportSwaggerModal.js      | 159 +++++++++++++++++++++
 src/routes/Document/components/SearchApi.js        |  52 ++++++-
 src/services/api.js                                |  11 ++
 5 files changed, 239 insertions(+), 5 deletions(-)

diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 70880969..538211ed 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -460,6 +460,17 @@
   "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADD.HEADER": "Add header",
   "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADD.QUERY": "Add Query",
   "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADDRESS.VALIDATE": "Request Address 
format error.",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER": "+ Import",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.TITLE": "Import Swagger Document",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.PROJECT.NAME": "Project Name",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.PROJECT.NAME.PLACEHOLDER": "Please 
enter project name",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL": "Swagger URL",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL.PLACEHOLDER": "Please enter 
Swagger URL, e.g: http://localhost:8080/v2/api-docs";,
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL.REQUIRED": "Please enter Swagger 
URL!",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL.INVALID": "Please enter a valid 
URL!",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.SUCCESS": "Swagger imported 
successfully!",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.FAILED": "Swagger import failed",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.NETWORK.ERROR": "Import failed, 
please check network connection",
   "SHENYU.PLUGIN.SELECTOR.LIST.CONFIGURATION": "Discovery Configuration",
   "SHENYU.COMMON.RESPONSE.CONFIGURATION.SUCCESS": "Set Discovery Configuration 
Success",
   "SHENYU.DISCOVERY.CONFIGURATION.TYPE": "Type",
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
index 27fe04ac..997ab6db 100644
--- a/src/locales/zh-CN.json
+++ b/src/locales/zh-CN.json
@@ -465,6 +465,17 @@
   "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADD.HEADER": "添加请求头参数",
   "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADD.QUERY": "添加查询参数",
   "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADDRESS.VALIDATE": "请求地址格式错误.",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER": "导入Swagger",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.TITLE": "导入Swagger文档",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.PROJECT.NAME": "项目名称",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.PROJECT.NAME.PLACEHOLDER": "请输入项目名称",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL": "Swagger URL",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL.PLACEHOLDER": "请输入Swagger 
URL,例如:http://localhost:8080/v2/api-docs";,
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL.REQUIRED": "请输入Swagger URL!",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL.INVALID": "请输入有效的URL!",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.SUCCESS": "Swagger导入成功!",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.FAILED": "Swagger导入失败",
+  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.NETWORK.ERROR": "导入失败,请检查网络连接",
   "SHENYU.PLUGIN.SELECTOR.LIST.CONFIGURATION": "服务发现配置",
   "SHENYU.COMMON.RESPONSE.CONFIGURATION.SUCCESS": "配置成功",
   "SHENYU.DISCOVERY.CONFIGURATION.TYPE": "类型",
diff --git a/src/routes/Document/components/ImportSwaggerModal.js 
b/src/routes/Document/components/ImportSwaggerModal.js
new file mode 100644
index 00000000..83ad5f92
--- /dev/null
+++ b/src/routes/Document/components/ImportSwaggerModal.js
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable no-unused-expressions */
+/* eslint-disable react/static-property-placement */
+import { Modal, Form, Input, message } from "antd";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { getIntlContent } from "../../../utils/IntlUtils";
+import { importSwagger } from "../../../services/api";
+
+class ImportSwaggerModal extends Component {
+  static propTypes = {
+    form: PropTypes.object,
+    visible: PropTypes.bool,
+    onOk: PropTypes.func,
+    onCancel: PropTypes.func,
+    currentProjectName: PropTypes.string,
+  };
+
+  static defaultProps = {
+    visible: false,
+    currentProjectName: "",
+    form: null,
+    onOk: null,
+    onCancel: null,
+  };
+
+  componentDidMount() {
+    // Component mounted
+  }
+
+  handleSubmit = () => {
+    const {
+      onOk,
+      form: { validateFieldsAndScroll },
+    } = this.props;
+    validateFieldsAndScroll(async (err, values) => {
+      if (!err) {
+        try {
+          const res = await importSwagger(values);
+          if (res.code !== 200) {
+            message.error(
+              res.message ||
+                getIntlContent("SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.FAILED"),
+            );
+          } else {
+            message.success(
+              res.message ||
+                
getIntlContent("SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.SUCCESS"),
+            );
+            onOk?.(values);
+          }
+        } catch (error) {
+          message.error(
+            getIntlContent(
+              "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.NETWORK.ERROR",
+            ),
+          );
+        }
+      }
+    });
+  };
+
+  render() {
+    const { onCancel, form, visible, currentProjectName } = this.props;
+    const { getFieldDecorator } = form;
+    const formItemLayout = {
+      labelCol: {
+        sm: { span: 6 },
+      },
+      wrapperCol: {
+        sm: { span: 18 },
+      },
+    };
+
+    return (
+      <Modal
+        visible={visible}
+        onCancel={onCancel}
+        onOk={this.handleSubmit}
+        forceRender
+        title={getIntlContent("SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.TITLE")}
+        width={600}
+      >
+        <Form className="login-form" {...formItemLayout}>
+          <Form.Item
+            label={getIntlContent(
+              "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.PROJECT.NAME",
+            )}
+          >
+            {getFieldDecorator("projectName", {
+              rules: [
+                {
+                  required: true,
+                  message: getIntlContent(
+                    
"SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.PROJECT.NAME.PLACEHOLDER",
+                  ),
+                },
+              ],
+              initialValue: currentProjectName,
+            })(
+              <Input
+                allowClear
+                placeholder={getIntlContent(
+                  
"SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.PROJECT.NAME.PLACEHOLDER",
+                )}
+              />,
+            )}
+          </Form.Item>
+
+          <Form.Item
+            label={getIntlContent("SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL")}
+          >
+            {getFieldDecorator("swaggerUrl", {
+              rules: [
+                {
+                  required: true,
+                  message: getIntlContent(
+                    "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL.REQUIRED",
+                  ),
+                },
+                {
+                  type: "url",
+                  message: getIntlContent(
+                    "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL.INVALID",
+                  ),
+                },
+              ],
+            })(
+              <Input
+                allowClear
+                placeholder={getIntlContent(
+                  "SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER.URL.PLACEHOLDER",
+                )}
+              />,
+            )}
+          </Form.Item>
+        </Form>
+      </Modal>
+    );
+  }
+}
+
+export default Form.create()(ImportSwaggerModal);
diff --git a/src/routes/Document/components/SearchApi.js 
b/src/routes/Document/components/SearchApi.js
index 3a9a78a6..e51e2c8c 100644
--- a/src/routes/Document/components/SearchApi.js
+++ b/src/routes/Document/components/SearchApi.js
@@ -33,6 +33,7 @@ import { getRootTag, getParentTagId, getApi } from 
"../../../services/api";
 import { Method } from "./globalData";
 import AddAndUpdateTag from "./AddAndUpdateTag";
 import AddAndUpdateApiDoc from "./AddAndUpdateApiDoc";
+import ImportSwaggerModal from "./ImportSwaggerModal";
 import { getIntlContent } from "../../../utils/IntlUtils";
 
 const { Text } = Typography;
@@ -45,6 +46,9 @@ const SearchApi = React.forwardRef((props, ref) => {
   const [selectedKeys, setSelectedKeys] = useState([]);
   const [document, setDocument] = useState("{}");
   const [ext, setExt] = useState("{}");
+  // Import Swagger related state
+  const [swaggerModalVisible, setSwaggerModalVisible] = useState(false);
+  const [currentProjectName, setCurrentProjectName] = useState("");
 
   const queryRootTag = async () => {
     setExpandedKeys([]);
@@ -75,7 +79,7 @@ const SearchApi = React.forwardRef((props, ref) => {
       const { dataList: apiDataList } = apiDataRecords;
       data[0].apiDataList = apiDataList;
       setTreeData(arr);
-      // 默认选中第一个
+      // Select the first one by default
       setSelectedKeys(["0"]);
       onSelect(["0"], { node: { props: arr[0] } });
     } else {
@@ -146,9 +150,9 @@ const SearchApi = React.forwardRef((props, ref) => {
     curNode.children.push({
       selectable: false,
       title: (
-        <Row gutter={18}>
+        <Row gutter={2}>
           {showAddTag && (
-            <Col span={12}>
+            <Col span={10}>
               <Button
                 type="primary"
                 ghost
@@ -163,8 +167,7 @@ const SearchApi = React.forwardRef((props, ref) => {
               </Button>
             </Col>
           )}
-
-          <Col span={12}>
+          <Col span={showAddTag ? 6 : 12}>
             <Button
               type="primary"
               ghost
@@ -178,6 +181,16 @@ const SearchApi = React.forwardRef((props, ref) => {
               + Api
             </Button>
           </Col>
+          <Col span={showAddTag ? 8 : 12}>
+            <Button
+              type="primary"
+              ghost
+              size="small"
+              onClick={() => handleImportSwagger(id)}
+            >
+              {getIntlContent("SHENYU.DOCUMENT.APIDOC.IMPORT.SWAGGER")}
+            </Button>
+          </Col>
         </Row>
       ),
       key: `${eventKey}-operator`,
@@ -259,6 +272,29 @@ const SearchApi = React.forwardRef((props, ref) => {
     afterUpdate(data, refType);
   };
 
+  // Handle Import Swagger button click
+  const handleImportSwagger = () => {
+    // Get current project name, preferably from the root node of the tree 
structure
+    let projectName = "default_project";
+    if (treeData && treeData.length > 0) {
+      projectName = treeData[0].name || "default_project";
+    }
+    setCurrentProjectName(projectName);
+    setSwaggerModalVisible(true);
+  };
+
+  // Handle Import Swagger modal cancel
+  const handleSwaggerCancel = () => {
+    setSwaggerModalVisible(false);
+  };
+
+  // Handle Import Swagger modal confirm
+  const handleSwaggerOk = () => {
+    setSwaggerModalVisible(false);
+    // Refresh tree structure
+    queryRootTag();
+  };
+
   useImperativeHandle(ref, () => ({
     addOrUpdateApi,
     addOrUpdateTag,
@@ -311,6 +347,12 @@ const SearchApi = React.forwardRef((props, ref) => {
         onOk={handleApiOk}
         onCancel={handleApiCancel}
       />
+      <ImportSwaggerModal
+        visible={swaggerModalVisible}
+        onOk={handleSwaggerOk}
+        onCancel={handleSwaggerCancel}
+        currentProjectName={currentProjectName}
+      />
     </div>
   );
 });
diff --git a/src/services/api.js b/src/services/api.js
index e3b34dd4..8dcd2f03 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -1426,3 +1426,14 @@ export async function deleteInstance(params) {
     body: [...params.list],
   });
 }
+
+/* import swagger */
+export async function importSwagger(params) {
+  return request(`${baseUrl}/swagger/import`, {
+    method: `POST`,
+    body: {
+      swaggerUrl: params.swaggerUrl,
+      projectName: params.projectName,
+    },
+  });
+}

Reply via email to