This is an automated email from the ASF dual-hosted git repository.
dengliming 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 4d459f55 Add support for APIDoc Insert Update Delete (#4029) (#259)
4d459f55 is described below
commit 4d459f553e656563eda33e7687bdf075b995ae95
Author: 杨文杰 <[email protected]>
AuthorDate: Mon Dec 26 21:56:22 2022 +0800
Add support for APIDoc Insert Update Delete (#4029) (#259)
* add api doc insert and update
* add update API DOC
* api insert or update
* add or update api
* api insert update
* api insert or update or delete
* change code for lint
* add zh-CN
---
src/locales/en-US.json | 13 +
src/locales/zh-CN.json | 13 +
src/routes/Document/ApiDoc.js | 68 +++-
.../Document/components/AddAndUpdateApiDoc.js | 347 +++++++++++++++++++++
src/routes/Document/components/ApiInfo.js | 7 +-
src/routes/Document/components/SearchApi.js | 12 +-
src/services/api.js | 30 ++
7 files changed, 484 insertions(+), 6 deletions(-)
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 92ac5820..b4a5d581 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -311,6 +311,19 @@
"SHENYU.DOCUMENT.APIDOC.CONTENTS.TO.BE.SIGNED": "Contents To Be Signed",
"SHENYU.DOCUMENT.APIDOC.SIGNATURE": "Signature",
"SHENYU.DOCUMENT.APIDOC.ERROR.CODE.DETAILS": "Error Code Details",
+ "SHENYU.DOCUMENT.APIDOC.CONTEXTPATH": "contextPath",
+ "SHENYU.DOCUMENT.APIDOC.APIPATH": "apiPath",
+ "SHENYU.DOCUMENT.APIDOC.HTTPMETHOD": "httpMethod",
+ "SHENYU.DOCUMENT.APIDOC.CONSUME": "consume",
+ "SHENYU.DOCUMENT.APIDOC.PRODUCE": "produce",
+ "SHENYU.DOCUMENT.APIDOC.VERSION": "version",
+ "SHENYU.DOCUMENT.APIDOC.RPCTYPE": "rpcType",
+ "SHENYU.DOCUMENT.APIDOC.STATE": "state",
+ "SHENYU.DOCUMENT.APIDOC.EXT": "ext",
+ "SHENYU.DOCUMENT.APIDOC.APIOWNER": "apiOwner",
+ "SHENYU.DOCUMENT.APIDOC.APIDESC": "apiDesc",
+ "SHENYU.DOCUMENT.APIDOC.APISOURCE": "apiSource",
+ "SHENYU.DOCUMENT.APIDOC.DOCUMENT": "document",
"SHENYU.COMMON.REQUIRED": "Required",
"SHENYU.COMMON.MAX.LENGTH": "Max Length",
"SHENYU.COMMON.MAX.EXAMPLE": "Example",
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
index bc62cc96..2c5c31c4 100644
--- a/src/locales/zh-CN.json
+++ b/src/locales/zh-CN.json
@@ -299,6 +299,19 @@
"SHENYU.DOCUMENT.APIDOC.CONTENTS.TO.BE.SIGNED": "待签名内容",
"SHENYU.DOCUMENT.APIDOC.SIGNATURE": "签名(sign)",
"SHENYU.DOCUMENT.APIDOC.ERROR.CODE.DETAILS": "错误代码",
+ "SHENYU.DOCUMENT.APIDOC.CONTEXTPATH": "上下文路径",
+ "SHENYU.DOCUMENT.APIDOC.APIPATH": "api路径",
+ "SHENYU.DOCUMENT.APIDOC.HTTPMETHOD": "http方法",
+ "SHENYU.DOCUMENT.APIDOC.CONSUME": "消费",
+ "SHENYU.DOCUMENT.APIDOC.PRODUCE": "生产",
+ "SHENYU.DOCUMENT.APIDOC.VERSION": "版本",
+ "SHENYU.DOCUMENT.APIDOC.RPCTYPE": "rpc类型",
+ "SHENYU.DOCUMENT.APIDOC.STATE": "状态",
+ "SHENYU.DOCUMENT.APIDOC.EXT": "ext",
+ "SHENYU.DOCUMENT.APIDOC.APIOWNER": "api持有者",
+ "SHENYU.DOCUMENT.APIDOC.APIDESC": "api描述",
+ "SHENYU.DOCUMENT.APIDOC.APISOURCE": "api来源",
+ "SHENYU.DOCUMENT.APIDOC.DOCUMENT": "文档说明",
"SHENYU.COMMON.REQUIRED": "必填",
"SHENYU.COMMON.MAX.LENGTH": "最大长度",
"SHENYU.COMMON.MAX.EXAMPLE": "示例值",
diff --git a/src/routes/Document/ApiDoc.js b/src/routes/Document/ApiDoc.js
index 4b24490e..bf2aac5c 100644
--- a/src/routes/Document/ApiDoc.js
+++ b/src/routes/Document/ApiDoc.js
@@ -18,13 +18,34 @@
import { Col, Row, Card, BackTop, Empty, message } from "antd";
import React, { useEffect, useState } from "react";
import SearchApi from "./components/SearchApi";
+import AddAndUpdateApiDoc from "./components/AddAndUpdateApiDoc";
import ApiInfo from "./components/ApiInfo";
-import { getDocMenus, getApiDetail } from "../../services/api";
+import { getDocMenus, getApiDetail, addApi, updateApi, deleteApi } from
"../../services/api";
import ApiContext from "./components/ApiContext";
function ApiDoc() {
const [apiDetail, setApiDetail] = useState({});
const [apiData, setApiData] = useState({});
+ const [open, setOpen] = useState(false);
+ const [flag, setflag] = useState('add');
+
+ const [initialValue, setInitialValue] = useState({
+ id: '',
+ contextPath: '',
+ apiPath: '',
+ httpMethod: '',
+ consume: '',
+ produce: '',
+ version: '',
+ rpcType: '',
+ state: '',
+ ext: '',
+ apiOwner: '',
+ apiDesc: '',
+ apiSource: '',
+ document: '',
+ tagIds: []
+ })
const initData = async () => {
const { code, data = {} } = await getDocMenus();
@@ -47,7 +68,6 @@ function ApiDoc() {
setApiData(data);
}
};
-
const handleSelectNode = async (_, e) => {
const {
node: {
@@ -59,13 +79,51 @@ function ApiDoc() {
if (!isLeaf) {
return;
}
+ if (!id) {
+ const targetId = _
+ handleAddApi(targetId)
+ return;
+ }
const { code, message: msg, data } = await getApiDetail(id);
if (code !== 200) {
message.error(msg);
return;
}
+ setInitialValue({
+ id
+ });
setApiDetail(data);
};
+ const handleAddApi = (targetId) => {
+ setflag('add')
+ setInitialValue({
+ tagIds: [targetId]
+ });
+ setOpen(true)
+ };
+ const callSaveOrUpdateApi = async (params) => {
+ let rs = (flag === 'add' ? await addApi({ ...params, tagIds:
initialValue.tagIds[0] }) : await updateApi({ ...params, id: initialValue.id,
tagIds: initialValue.tagIds }));
+ if (rs.code !== 200) {
+ message.error(rs.msg);
+ } else {
+ setOpen(false)
+ location.reload()
+ }
+ };
+ const handleDeleteApi = async () => {
+ const { code, message: msg } = await deleteApi([initialValue.id]);
+ if (code !== 200) {
+ message.error(msg);
+ } else {
+ location.reload()
+ }
+ };
+ const handleUpdateApi = async () => {
+ let queryData = await getApiDetail(initialValue.id)
+ setInitialValue(queryData.data);
+ setOpen(true)
+ setflag('update')
+ }
useEffect(() => {
initData();
@@ -79,13 +137,17 @@ function ApiDoc() {
}}
>
<Card style={{ margin: 24 }}>
+ {open && <AddAndUpdateApiDoc onCancel={() => setOpen(false)}
handleOk={callSaveOrUpdateApi} {...initialValue} />
+ }
<Row gutter={24}>
<Col span={6}>
<SearchApi onSelect={handleSelectNode} />
</Col>
<Col span={18}>
{apiDetail.id ? (
- <ApiInfo />
+ <>
+ <ApiInfo handleUpdateApi={handleUpdateApi}
handleDeleteApi={handleDeleteApi} />
+ </>
) : (
<Empty description={false} style={{ padding: "160px 0" }} />
)}
diff --git a/src/routes/Document/components/AddAndUpdateApiDoc.js
b/src/routes/Document/components/AddAndUpdateApiDoc.js
new file mode 100644
index 00000000..b8b5485e
--- /dev/null
+++ b/src/routes/Document/components/AddAndUpdateApiDoc.js
@@ -0,0 +1,347 @@
+/*
+ * 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.
+ */
+
+import { Modal, Form, Input, Select } from "antd";
+import React, { Component } from "react";
+import { Method } from "./globalData";
+import { getIntlContent } from "../../../utils/IntlUtils";
+
+class AddAndUpdateApiDoc extends Component {
+ render() {
+ const RPCTYPE = [
+ "http",
+ "dubbo",
+ "sofa",
+ "tars",
+ "websocket",
+ "springCloud",
+ "motan",
+ "grpc"
+ ];
+ const API_SOURCE_TYPE = [
+ "swagger",
+ "annotation generation",
+ "create manuallym",
+ "import swagger",
+ "import yapi"
+ ];
+ const {
+ onCancel,
+ form,
+ contextPath = "",
+ apiPath = "",
+ httpMethod = "",
+ consume = "",
+ produce = "",
+ version = "",
+ rpcType = "",
+ state = "",
+ ext = "",
+ apiOwner = "",
+ apiDesc = "",
+ apiSource = "",
+ document = ""
+ } = this.props;
+ const { getFieldDecorator } = form;
+ const formItemLayout = {
+ labelCol: {
+ sm: { span: 5 }
+ },
+ wrapperCol: {
+ sm: { span: 19 }
+ }
+ };
+ const handleSubmit = () => {
+ const { handleOk } = this.props;
+ let newValues = "";
+ form.validateFieldsAndScroll((err, values) => {
+ if (!err) {
+ // eslint-disable-next-line radix
+ values.state = parseInt(values.state);
+ // eslint-disable-next-line radix
+ values.apiSource = parseInt(values.apiSource);
+ // eslint-disable-next-line radix
+ values.httpMethod = parseInt(values.httpMethod);
+ newValues = values;
+ }
+ });
+ handleOk(newValues);
+ };
+ return (
+ <Modal visible onCancel={onCancel} onOk={handleSubmit}>
+ <Form onSubmit={handleSubmit} className="login-form">
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.CONTEXTPATH")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("contextPath", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.CONTEXTPATH")
+ }
+ ],
+ initialValue: contextPath
+ })(
+ <Input
+ placeholder={getIntlContent(
+ "SHENYU.DOCUMENT.APIDOC.CONTEXTPATH"
+ )}
+ />
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.APIPATH")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("apiPath", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.APIPATH")
+ }
+ ],
+ initialValue: apiPath
+ })(
+ <Input
+ placeholder={getIntlContent("SHENYU.DOCUMENT.APIDOC.APIPATH")}
+ />
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.HTTPMETHOD")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("httpMethod", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.HTTPMETHOD")
+ }
+ ],
+ initialValue: httpMethod
+ })(
+ <Select>
+ {Object.values(Method).map((e, i) => {
+ return (
+ <Select.Option key={`${e} ${i}`} value={i}>
+ {e}
+ </Select.Option>
+ );
+ })}
+ </Select>
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.CONSUME")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("consume", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.CONSUME")
+ }
+ ],
+ initialValue: consume
+ })(
+ <Input
+ placeholder={getIntlContent("SHENYU.DOCUMENT.APIDOC.CONSUME")}
+ />
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.PRODUCE")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("produce", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.PRODUCE")
+ }
+ ],
+ initialValue: produce
+ })(
+ <Input
+ placeholder={getIntlContent("SHENYU.DOCUMENT.APIDOC.PRODUCE")}
+ />
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.VERSION")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("version", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.VERSION")
+ }
+ ],
+ initialValue: version
+ })(
+ <Input
+ placeholder={getIntlContent("SHENYU.DOCUMENT.APIDOC.VERSION")}
+ />
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.RPCTYPE")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("rpcType", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.RPCTYPE")
+ }
+ ],
+ initialValue: rpcType
+ })(
+ <Select>
+ {Object.values(RPCTYPE).map((e, i) => {
+ return (
+ <Select.Option key={`${e} ${i}`} value={e}>
+ {e}
+ </Select.Option>
+ );
+ })}
+ </Select>
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.STATE")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("state", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.STATE")
+ }
+ ],
+ initialValue: state
+ })(
+ <Input
+ placeholder={getIntlContent("SHENYU.DOCUMENT.APIDOC.STATE")}
+ />
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.EXT")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("ext", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.EXT")
+ }
+ ],
+ initialValue: ext
+ })(
+ <Input
+ placeholder={getIntlContent("SHENYU.DOCUMENT.APIDOC.EXT")}
+ />
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.APIOWNER")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("apiOwner", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.APIOWNER")
+ }
+ ],
+ initialValue: apiOwner
+ })(
+ <Input
+ placeholder={getIntlContent("SHENYU.DOCUMENT.APIDOC.APIOWNER")}
+ />
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.APIDESC")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("apiDesc", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.APIDESC")
+ }
+ ],
+ initialValue: apiDesc
+ })(
+ <Input
+ placeholder={getIntlContent("SHENYU.DOCUMENT.APIDOC.APIDESC")}
+ />
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.APISOURCE")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("apiSource", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.APISOURCE")
+ }
+ ],
+ initialValue: apiSource
+ })(
+ <Select>
+ {Object.values(API_SOURCE_TYPE).map((e, i) => {
+ return (
+ <Select.Option key={`${e} ${i}`} value={i}>
+ {e}
+ </Select.Option>
+ );
+ })}
+ </Select>
+ )}
+ </Form.Item>
+ <Form.Item
+ label={`${getIntlContent("SHENYU.DOCUMENT.APIDOC.DOCUMENT")}`}
+ {...formItemLayout}
+ >
+ {getFieldDecorator("document", {
+ rules: [
+ {
+ required: true,
+ message: getIntlContent("SHENYU.DOCUMENT.APIDOC.DOCUMENT")
+ }
+ ],
+ initialValue: document
+ })(
+ <Input
+ placeholder={getIntlContent("SHENYU.DOCUMENT.APIDOC.DOCUMENT")}
+ />
+ )}
+ </Form.Item>
+ </Form>
+ </Modal>
+ );
+ }
+}
+
+export default Form.create()(AddAndUpdateApiDoc);
diff --git a/src/routes/Document/components/ApiInfo.js
b/src/routes/Document/components/ApiInfo.js
index a9591a3f..8ca7e49e 100644
--- a/src/routes/Document/components/ApiInfo.js
+++ b/src/routes/Document/components/ApiInfo.js
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { Typography, Table, Tabs, Icon, Row, Col } from "antd";
+import { Typography, Table, Tabs, Icon, Row, Col , Button } from "antd";
import React, { useContext } from "react";
import ApiDebug from "./ApiDebug";
import ApiContext from "./ApiContext";
@@ -24,13 +24,14 @@ import { Method } from "./globalData";
const { Title, Text, Paragraph } = Typography;
-function ApiInfo() {
+function ApiInfo(props) {
const {
apiData: { envProps = [] },
apiDetail,
apiDetail: { document, responseParameters, requestHeaders }
} = useContext(ApiContext);
let documentJSON = {};
+ let {handleUpdateApi,handleDeleteApi} = props
try {
documentJSON = JSON.parse(document);
documentJSON.errorCode = [];
@@ -148,6 +149,8 @@ function ApiInfo() {
>
<Title level={2}>
{apiDetail.tags[apiDetail.tags.length - 1].name}
+ <Button style={{float:'right'}}
onClick={handleDeleteApi}>delete</Button>
+ <Button style={{float:'right'}}
onClick={handleUpdateApi}>edit</Button>
</Title>
<Paragraph>
<Title level={4}>
diff --git a/src/routes/Document/components/SearchApi.js
b/src/routes/Document/components/SearchApi.js
index 6e167acf..132e0334 100644
--- a/src/routes/Document/components/SearchApi.js
+++ b/src/routes/Document/components/SearchApi.js
@@ -114,7 +114,17 @@ function SearchApi(props) {
),
key: `${treeNode.props.eventKey}-${index}`,
isLeaf: true
- }));
+ }))
+ treeNode.props.dataRef.children.push({
+ title: (
+ <>
+ <Text code> + </Text>
+ </>
+ ),
+ key: treeNode.props.dataRef.id,
+ isLeaf: true
+ })
+ ;
}
setApiTree([...apiTree]);
return Promise.resolve();
diff --git a/src/services/api.js b/src/services/api.js
index 10d59aef..d2682234 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -822,3 +822,33 @@ export function getApiDetail(id) {
method: `GET`
});
}
+
+/** addApi */
+export function addApi(params) {
+ return request(`${baseUrl}/api`, {
+ method: `POST`,
+ body: {
+ ...params
+ }
+ });
+}
+
+/** addApi */
+export function updateApi(params) {
+ return request(`${baseUrl}/api/${params.id}`, {
+ method: `PUT`,
+ body: {
+ ...params
+ }
+ });
+}
+
+/** delete Api */
+export function deleteApi(params) {
+ return request(`${baseUrl}/api/batch`, {
+ method: `DELETE`,
+ body: params
+ });
+}
+
+