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 <[email protected]>
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,
+ },
+ });
+}