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, + }, + }); +}