You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shenyu.apache.org by li...@apache.org on 2023/01/12 15:07:56 UTC

[shenyu-dashboard] branch master updated: [type: feature] Add support for ApiMockRequest Insert Update Delete. (#262)

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

likeguo 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 19725cf8 [type: feature] Add support for ApiMockRequest Insert Update Delete. (#262)
19725cf8 is described below

commit 19725cf8fa32d87e9d10b52785796730c1b8132f
Author: Shawn Jim <32...@users.noreply.github.com>
AuthorDate: Thu Jan 12 23:07:50 2023 +0800

    [type: feature] Add support for ApiMockRequest Insert Update Delete. (#262)
    
    * Add support for ApiMockRequest Insert Update Delete.
    
    * Remove unnecessary code.
---
 src/locales/en-US.json                          |  14 +-
 src/locales/zh-CN.json                          |  14 +-
 src/routes/Document/ApiDoc.js                   |  13 +-
 src/routes/Document/components/ApiContext.js    |   3 +-
 src/routes/Document/components/ApiDebug.js      | 382 +++++++++++++-----------
 src/routes/Document/components/HeadersEditor.js |  71 +++--
 src/services/api.js                             |  24 ++
 7 files changed, 315 insertions(+), 206 deletions(-)

diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index b4a5d581..75b52a2b 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -328,5 +328,17 @@
   "SHENYU.COMMON.MAX.LENGTH": "Max Length",
   "SHENYU.COMMON.MAX.EXAMPLE": "Example",
   "SHENYU.COMMON.MAX.ENVIRONMENT": "Environment",
-  "SHENYU.COMMON.HTTP.METHOD": "HttpMethod"
+  "SHENYU.COMMON.HTTP.METHOD": "HttpMethod",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.HOST": "Host",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.PORT": "Port",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.URL": "Request Path",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.PATH_VARIABLE": "Path Variable",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.QUERY": "Query",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.HEADER": "Header",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.BODY": "Body",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.SAVE": "Save",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.RESET": "Reset",
+  "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."
 }
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
index 2c5c31c4..a26509a4 100644
--- a/src/locales/zh-CN.json
+++ b/src/locales/zh-CN.json
@@ -316,5 +316,17 @@
   "SHENYU.COMMON.MAX.LENGTH": "最大长度",
   "SHENYU.COMMON.MAX.EXAMPLE": "示例值",
   "SHENYU.COMMON.MAX.ENVIRONMENT": "环境类型",
-  "SHENYU.COMMON.HTTP.METHOD": "请求方法"
+  "SHENYU.COMMON.HTTP.METHOD": "请求方法",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.HOST": "请求域名或IP",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.PORT": "端口",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.URL": "路由地址",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.PATH_VARIABLE": "路由参数",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.QUERY": "请求参数",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.HEADER": "请求头",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.BODY": "请求体",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.SAVE": "保存",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.RESET": "重置",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADD.HEADER": "添加请求头参数",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADD.QUERY": "添加查询参数",
+  "SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADDRESS.VALIDATE": "请求地址格式错误."
 }
diff --git a/src/routes/Document/ApiDoc.js b/src/routes/Document/ApiDoc.js
index bf2aac5c..b06b6486 100644
--- a/src/routes/Document/ApiDoc.js
+++ b/src/routes/Document/ApiDoc.js
@@ -20,12 +20,13 @@ import React, { useEffect, useState } from "react";
 import SearchApi from "./components/SearchApi";
 import AddAndUpdateApiDoc from "./components/AddAndUpdateApiDoc";
 import ApiInfo from "./components/ApiInfo";
-import { getDocMenus, getApiDetail, addApi, updateApi, deleteApi } from "../../services/api";
+import { getDocMenus, getApiDetail, addApi, updateApi, deleteApi, getApiMockRequest} from "../../services/api";
 import ApiContext from "./components/ApiContext";
 
 function ApiDoc() {
   const [apiDetail, setApiDetail] = useState({});
   const [apiData, setApiData] = useState({});
+  const [apiMock, setApiMock] = useState({});
   const [open, setOpen] = useState(false);
   const [flag, setflag] = useState('add');
 
@@ -93,6 +94,13 @@ function ApiDoc() {
       id
     });
     setApiDetail(data);
+
+    const { code: mockCode, message: mockMsg, data: mockData} = await getApiMockRequest(id);
+    if (mockCode !== 200) {
+      message.error(mockMsg);
+      return;
+    }
+    setApiMock(mockData);
   };
   const handleAddApi = (targetId) => {
     setflag('add')
@@ -133,7 +141,8 @@ function ApiDoc() {
     <ApiContext.Provider
       value={{
         apiDetail,
-        apiData
+        apiData,
+        apiMock
       }}
     >
       <Card style={{ margin: 24 }}>
diff --git a/src/routes/Document/components/ApiContext.js b/src/routes/Document/components/ApiContext.js
index cebaf595..58d7d19f 100644
--- a/src/routes/Document/components/ApiContext.js
+++ b/src/routes/Document/components/ApiContext.js
@@ -19,5 +19,6 @@ import { createContext } from "react";
 
 export default createContext({
   apiDetail: {},
-  apiData: {}
+  apiData: {},
+  apiMock: {}
 });
diff --git a/src/routes/Document/components/ApiDebug.js b/src/routes/Document/components/ApiDebug.js
index ebdc2860..bec3b219 100644
--- a/src/routes/Document/components/ApiDebug.js
+++ b/src/routes/Document/components/ApiDebug.js
@@ -15,36 +15,23 @@
  * limitations under the License.
  */
 
-import {
-  Typography,
-  Form,
-  Input,
-  Button,
-  Radio,
-  Row,
-  Col,
-  Tree,
-  Empty,
-  Tabs
-} from "antd";
-import React, {
-  useEffect,
-  useState,
-  forwardRef,
-  useImperativeHandle,
-  createRef,
-  useContext
-} from "react";
+import {Button, Col, Empty, Form, Icon, Input, message, Row, Tabs, Typography} from "antd";
+import React, {createRef, forwardRef, useContext, useEffect, useImperativeHandle, useState} from "react";
 import ReactJson from "react-json-view";
 import fetch from "dva/fetch";
-import { sandboxProxyGateway } from "../../../services/api";
+import {
+  createOrUpdateMockRequest,
+  deleteMockRequest,
+  getApiMockRequest,
+  sandboxProxyGateway
+} from "../../../services/api";
 import ApiContext from "./ApiContext";
 import HeadersEditor from "./HeadersEditor";
-import { getIntlContent } from "../../../utils/IntlUtils";
+import {getIntlContent} from "../../../utils/IntlUtils";
 import AuthButton from "../../../utils/AuthButton";
+import {Method} from "./globalData";
 
 const { Title, Text, Paragraph } = Typography;
-const { TreeNode } = Tree;
 const { TabPane } = Tabs;
 const FormItem = Form.Item;
 
@@ -54,10 +41,62 @@ const FCForm = forwardRef(({ form, onSubmit }, ref) => {
   }));
 
   const {
-    apiDetail: { apiPath, httpMethodList, requestHeaders, requestParameters },
-    apiData: { appKey, gatewayUrl, cookie }
+    apiDetail,
+    apiMock
   } = useContext(ApiContext);
-  const [questJson, setRequestJson] = useState({});
+  const [questJson, setRequestJson] = useState(JSON.parse(apiMock.body || '{}'));
+  const [initialValue, setInitialValue] = useState({
+    id: apiMock.id,
+    apiId: apiDetail.id,
+    host: apiMock.host,
+    port: apiMock.port,
+    url: apiMock.url,
+    pathVariable: apiMock.pathVariable,
+    query: apiMock.query,
+    header: apiMock.header,
+    body: apiMock.body
+  });
+  const [activeKey, setActiveKey] = useState("1");
+
+  useEffect(
+    () => {
+      setInitialValue({
+        id: apiMock.id,
+        apiId: apiDetail.id,
+        host: apiMock.host,
+        port: apiMock.port,
+        url: apiMock.url,
+        pathVariable: apiMock.pathVariable,
+        query: apiMock.query,
+        header: apiMock.header,
+        body: apiMock.body
+      });
+      form.resetFields("requestUrl");
+      setRequestJson(JSON.parse(apiMock.body || '{}'));
+    },
+    [apiMock.id]
+  );
+
+  useEffect(
+    () => {
+      form.setFieldsValue({httpMethod: Method?.[apiDetail.httpMethod]})
+    },
+    [apiDetail.httpMethod]
+  );
+
+  useEffect(
+    () => {
+      form.setFieldsValue({headers: initialValue.header || "{}"})
+    },
+    [initialValue.header]
+  );
+
+  useEffect(
+    () => {
+      form.setFieldsValue({querys: initialValue.query || "{}"})
+    },
+    [initialValue.query]
+  );
 
   const handleSubmit = e => {
     e.preventDefault();
@@ -69,78 +108,79 @@ const FCForm = forwardRef(({ form, onSubmit }, ref) => {
         });
       }
     });
-  };
-
-  const createRequestJson = (params = []) => {
-    const exampleJSON = {};
-    const key = [];
-    const loopExample = (data, obj) => {
-      data.forEach(item => {
-        const { name, refs, example, type } = item;
-        key.push(name);
-        switch (type) {
-          case "array":
-            if (Array.isArray(refs)) {
-              obj[name] = [{}];
-              key.push(0);
-              loopExample(refs, obj[name][0]);
-              key.pop();
-            } else {
-              obj[name] = [];
-            }
-            break;
-          case "object":
-            obj[name] = {};
-            if (Array.isArray(refs)) {
-              loopExample(refs, obj[name]);
-            }
-            break;
-          default:
-            obj[name] = example;
-            break;
-        }
-        key.pop();
-      });
-    };
+  }
 
-    loopExample(params, exampleJSON);
-    setRequestJson(exampleJSON);
+  const updateJson = obj => {
+    setRequestJson(obj.updated_src);
   };
 
-  const renderTreeNode = (data, indexArr = []) => {
-    return data.map((item, index) => {
-      const { name, type, required, description } = item;
-      const TreeTitle = (
-        <>
-          <Text strong>{name}</Text>
-          &nbsp;<Text code>{type}</Text>
-          &nbsp;
-          {required ? (
-            <Text type="danger">required</Text>
-          ) : (
-            <Text type="warning">optional</Text>
-          )}
-          &nbsp;<Text type="secondary">{description}</Text>
-        </>
-      );
-      return (
-        <TreeNode key={[...indexArr, index].join("-")} title={TreeTitle}>
-          {item.refs && renderTreeNode(item.refs, [...indexArr, index])}
-        </TreeNode>
-      );
+  const handlerSaveOrUpdate  = async () => {
+    ref.current.form.validateFieldsAndScroll( async (errors) => {
+      if (!errors) {
+        const fields = form.getFieldsValue();
+        let requestUrl = fields.requestUrl;
+        const url = new URL(requestUrl);
+        const params = {
+          ...initialValue,
+          apiId: apiDetail.id,
+          host: url.hostname,
+          port: url.port,
+          url: requestUrl,
+          header: fields.headers,
+          query: fields.querys,
+          body: JSON.stringify(questJson),
+          pathVariable: url.search || ''
+        }
+        let rs = await createOrUpdateMockRequest(params);
+        if (rs.code !== 200) {
+          message.error(rs.msg);
+        } else {
+          const { code, message: msg, data } = await getApiMockRequest(apiDetail.id);
+          if (code !== 200) {
+            message.error(msg);
+            return;
+          }
+          message.success(rs.message);
+          setInitialValue({...initialValue, id: data.id});
+        }
+      }
     });
   };
 
-  const updateJson = obj => {
-    setRequestJson(obj.updated_src);
+  const handlerDelete = async () => {
+    if (initialValue.id) {
+      let rs = await deleteMockRequest(initialValue.id);
+      if (rs.code !== 200) {
+        message.error(rs.msg);
+      } else {
+        message.success(rs.message);
+      }
+    }
+    resetContext()
   };
 
-  useEffect(
-    () => {
-      createRequestJson(requestParameters);
-    },
-    [requestParameters]
-  );
+  const resetContext = () => {
+    setInitialValue({
+      id: null,
+      apiId: apiDetail.id,
+      host: undefined,
+      port: null,
+      url: null,
+      pathVariable: null,
+      query: null,
+      header: null,
+      body: null
+    });
+    setRequestJson({});
+    form.resetFields("requestUrl");
+  }
+
+  const changeParamTab = (key) => {
+    setActiveKey(key);
+    let header = form.getFieldsValue().headers;
+    let headerJson = {...JSON.parse(header), "Content-type": key === '1' ? "application/json" : "application/x-www-form-urlencoded"};
+    setInitialValue({...initialValue, header: JSON.stringify(headerJson)})
+  }
 
   return (
     <Form onSubmit={handleSubmit}>
@@ -149,60 +189,33 @@ const FCForm = forwardRef(({ form, onSubmit }, ref) => {
       </Title>
       <FormItem label={getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.ADDRESS")}>
         {form.getFieldDecorator("requestUrl", {
-          initialValue: gatewayUrl + apiPath,
-          rules: [{ type: "string", required: true }]
+          initialValue: initialValue.url || '',
+          rules: [
+            {
+              type: "string",
+              required: true,
+              pattern: /^https?:\/\/([^:]+):(\d+)(\/.+)$/
+            }
+          ]
         })(<Input allowClear />)}
       </FormItem>
-      <FormItem label="AppKey">
-        {form.getFieldDecorator("appKey", {
-          initialValue: appKey,
-          rules: [{ type: "string" }]
-        })(
-          <Input
-            allowClear
-            placeholder=" If the current API requires signature authentication, this parameter is required"
-          />
-        )}
-      </FormItem>
-      <FormItem label="Cookie">
-        {form.getFieldDecorator("cookie", {
-          initialValue: cookie,
-          rules: [{ type: "string" }]
-        })(
-          <Input
-            allowClear
-            placeholder="Fill in the real cookie value.(signature authentication and login free API ignore this item)"
-          />
-        )}
-      </FormItem>
 
       <FormItem label="Headers">
         {form.getFieldDecorator("headers", {
-          initialValue: requestHeaders || [],
-          rules: [
-            {
-              validator: (rule, value, callback) => {
-                const errorRow = value.find(
-                  item => item.required && item.example === ""
-                );
-                if (errorRow) {
-                  callback(`${errorRow.name} is required`);
-                } else {
-                  callback();
-                }
-              }
-            }
-          ]
-        })(<HeadersEditor />)}
+          initialValue: initialValue.header,
+          rules: []
+        })(<HeadersEditor buttonText={getIntlContent("SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADD.HEADER")} mockId={apiMock.id} />)}
       </FormItem>
 
       <FormItem label={getIntlContent("SHENYU.COMMON.HTTP.METHOD")}>
         {form.getFieldDecorator("httpMethod", {
-          initialValue: httpMethodList?.[0]?.toLocaleUpperCase(),
+          initialValue: Method?.[apiDetail.httpMethod],
           rules: [{ type: "string", required: true }]
-        })(
-          <Radio.Group
-            options={httpMethodList?.map(v => v.toLocaleUpperCase())}
+        })
+        (
+          <Input
+            allowClear
+            readOnly={true}
           />
         )}
       </FormItem>
@@ -210,41 +223,69 @@ const FCForm = forwardRef(({ form, onSubmit }, ref) => {
         label={getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.PARAMETERS")}
         required
       />
-      <Row gutter={16}>
-        <Col span={14}>
-          <ReactJson
-            src={questJson}
-            theme="monokai"
-            displayDataTypes={false}
-            name={false}
-            onAdd={updateJson}
-            onEdit={updateJson}
-            onDelete={updateJson}
-            style={{ borderRadius: 4, padding: 16 }}
-          />
-        </Col>
-        <Col span={10}>
-          <div
-            style={{
-              borderRadius: 4,
-              border: "1px solid #e8e8e8",
-              overflow: "auto",
-              padding: 8
-            }}
-          >
-            {requestParameters && (
-              <Tree showLine defaultExpandAll>
-                {renderTreeNode(requestParameters)}
-              </Tree>
-            )}
-          </div>
-        </Col>
-      </Row>
+
+      <Tabs
+        activeKey={activeKey}
+        onChange={key => changeParamTab(key)}
+      >
+        <Tabs.TabPane
+          tab={
+            <>
+              <Icon type="file-text" />
+              BODY
+            </>
+          }
+          key="1"
+        >
+          <Row gutter={16}>
+            <Col span={14}>
+              <ReactJson
+                src={questJson}
+                theme="monokai"
+                displayDataTypes={false}
+                name={false}
+                onAdd={updateJson}
+                onEdit={updateJson}
+                onDelete={updateJson}
+                style={{ borderRadius: 4, padding: 16 }}
+              />
+            </Col>
+          </Row>
+        </Tabs.TabPane>
+
+        <Tabs.TabPane
+          tab={
+            <>
+              <Icon type="file-text" />
+              QUERY
+            </>
+          }
+          key="2"
+        >
+
+          <FormItem>
+            {form.getFieldDecorator("querys", {
+              initialValue: initialValue.query || "{}",
+              rules: []
+            })(<HeadersEditor buttonText={getIntlContent("SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.ADD.QUERY")} mockId={apiMock.id} />)}
+          </FormItem>
+        </Tabs.TabPane>
+      </Tabs>
+
+
       <AuthButton perms="document:apirun:send">
         <FormItem label=" " colon={false}>
           <Button htmlType="submit" type="primary">
             {getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.SEND.REQUEST")}
           </Button>
+
+          <Button onClick={handlerSaveOrUpdate}>
+            {getIntlContent("SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.SAVE")}
+          </Button>
+
+          <Button onClick={handlerDelete}>
+            {getIntlContent("SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.RESET")}
+          </Button>
         </FormItem>
       </AuthButton>
     </Form>
@@ -259,16 +300,12 @@ function ApiDebug() {
   } = useContext(ApiContext);
   const [responseInfo, setResponseInfo] = useState({});
   const [activeKey, setActiveKey] = useState("2");
-
   const formRef = createRef();
 
   const handleSubmit = async values => {
-    const { headers, ...params } = values;
-    const headersObj = {};
-    headers.forEach(item => {
-      headersObj[item.name] = item.example;
-    });
-    params.headers = headersObj;
+    const { headers, requestUrl, ...params } = values;
+    params.headers = JSON.parse(headers);
+    params.requestUrl = requestUrl;
     fetch(sandboxProxyGateway(), {
       method: "POST",
       headers: {
@@ -290,13 +327,12 @@ function ApiDebug() {
   useEffect(
     () => {
       setResponseInfo({});
-      // eslint-disable-next-line no-unused-expressions
-      formRef.current?.form.resetFields(["method", "headers"]);
       setActiveKey("2");
     },
     [id]
   );
 
+
   return (
     <>
       <EnhancedFCForm wrappedComponentRef={formRef} onSubmit={handleSubmit} />
diff --git a/src/routes/Document/components/HeadersEditor.js b/src/routes/Document/components/HeadersEditor.js
index 34a93ae7..c2bd9485 100644
--- a/src/routes/Document/components/HeadersEditor.js
+++ b/src/routes/Document/components/HeadersEditor.js
@@ -16,56 +16,71 @@
  */
 
 import { Col, Input, Row, Button, Icon, Typography } from "antd";
-import React, { Fragment } from "react";
+import React, {Fragment, useEffect, useState} from "react";
 
 const { Text } = Typography;
 
 function HeadersEditor(props) {
-  const { value, onChange } = props;
-  const onChangeItem = (e, key, id) => {
-    onChange(
-      value.map(
-        item => (item.id === id ? { ...item, [key]: e.target.value } : item)
-      )
+  const {value: propsValue, onChange, buttonText} = props;
+  const jsonObj = JSON.parse(propsValue || '{}');
+  const [value, onChangeValue] =useState(Object.keys(jsonObj).map((key, index) => ({index, key, value: jsonObj[key]})));
+
+  useEffect(
+    () => {
+      onChangeValue(Object.keys(jsonObj).map((key, index) => ({index, key, value: jsonObj[key]})))
+    },
+    [propsValue]
+  );
+
+  const onChangeItem = (e, key, index) => {
+    let newValue = value.map(
+      item => (item.index === index ? { ...item, [key]: e } : item)
     );
+    onChangeValue(newValue);
+    onChange(format(newValue));
   };
 
-  const onDeleteItem = id => {
-    onChange(value.filter(item => item.id !== id));
+  const onDeleteItem = key => {
+    let newValue = value.filter(item => item.key !== key);
+    onChangeValue(newValue);
+    onChange(format(newValue));
   };
 
   const onAddItem = () => {
-    onChange([
+    let newValue = [
       ...value,
-      { id: `${Date.now()}`, name: "", example: "", required: false }
-    ]);
+      {index: value.length, key: "", value: ""}
+    ];
+    onChangeValue(newValue);
+    onChange(format(newValue));
   };
 
+  const format = (param) => {
+    const newObject = param.reduce((acc, curr) => {
+      acc[curr.key] = curr.value;
+      return acc;
+    }, {});
+    return JSON.stringify(newObject);
+  }
+
   return (
     <Row gutter={16}>
       {value.map(item => (
-        <Fragment key={item.id}>
+        <Fragment key={item.index}>
           <Col span={6}>
             <Input
               allowClear
-              value={item.name}
-              readOnly={item.required}
-              onChange={e => onChangeItem(e, "name", item.id)}
+              value={item.key}
+              readOnly={false}
+              onChange={e => onChangeItem(e.target.value, "key", item.index)}
             />
           </Col>
           <Col span={16}>
             <Input
               allowClear
-              value={item.example}
-              placeholder={item.description}
-              prefix={
-                item.required && (
-                  <Text type="danger" strong>
-                    *
-                  </Text>
-                )
-              }
-              onChange={e => onChangeItem(e, "example", item.id)}
+              value={item.value}
+              readOnly={false}
+              onChange={e => onChangeItem(e.target.value, "value", item.index)}
             />
           </Col>
           <Col span={2} style={{ textAlign: "center" }}>
@@ -74,7 +89,7 @@ function HeadersEditor(props) {
                 <Icon
                   style={{ fontSize: "16px" }}
                   type="minus-circle-o"
-                  onClick={() => onDeleteItem(item.id)}
+                  onClick={() => onDeleteItem(item.key)}
                 />
               </Text>
             )}
@@ -84,7 +99,7 @@ function HeadersEditor(props) {
 
       <Col span={24}>
         <Button block type="dashed" onClick={onAddItem}>
-          <Icon type="plus" /> Add header
+          <Icon type="plus" /> {buttonText}
         </Button>
       </Col>
     </Row>
diff --git a/src/services/api.js b/src/services/api.js
index d2682234..22234ff4 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -823,6 +823,30 @@ export function getApiDetail(id) {
   });
 }
 
+/* queryMockRequest */
+export function getApiMockRequest(apiId) {
+  return request(`${baseUrl}/mock/${apiId}`, {
+    method: `GET`
+  });
+}
+
+/* createOrUpdateMockRequest */
+export function createOrUpdateMockRequest(params) {
+  return request(`${baseUrl}/mock/insertOrUpdate`, {
+    method: `POST`,
+    body: {
+      ...params
+    }
+  })
+}
+
+/* createOrUpdateMockRequest */
+export function deleteMockRequest(id) {
+  return request(`${baseUrl}/mock/${id}`, {
+    method: `DELETE`
+  })
+}
+
 /** addApi */
 export function addApi(params) {
   return request(`${baseUrl}/api`, {