From 50bcbe998bfbe8f85b0243b857cf2a783038b50b Mon Sep 17 00:00:00 2001 From: Joey Date: Wed, 6 Jan 2021 09:25:48 +0800 Subject: [PATCH 01/11] feat: Improve consumer for i18n (#1212) Signed-off-by: imjoey --- web/src/locales/zh-CN/menu.ts | 2 +- web/src/pages/Consumer/locales/en-US.ts | 4 ++-- web/src/pages/Consumer/locales/zh-CN.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/locales/zh-CN/menu.ts b/web/src/locales/zh-CN/menu.ts index 6a53236041..8533c4d00a 100644 --- a/web/src/locales/zh-CN/menu.ts +++ b/web/src/locales/zh-CN/menu.ts @@ -67,7 +67,7 @@ export default { 'menu.routes': '路由', 'menu.ssl': '证书', 'menu.upstream': '上游', - 'menu.consumer': 'Consumer', + 'menu.consumer': '应用', 'menu.plugin': '插件', 'menu.service': '服务', 'menu.setting': '设置', diff --git a/web/src/pages/Consumer/locales/en-US.ts b/web/src/pages/Consumer/locales/en-US.ts index 5bddf33236..64f6f32820 100644 --- a/web/src/pages/Consumer/locales/en-US.ts +++ b/web/src/pages/Consumer/locales/en-US.ts @@ -17,9 +17,9 @@ export default { 'page.consumer.form.itemRuleMessage.username': 'Maximum length is 100, only letters, numbers and _ are supported, and can only begin with letters', - 'page.consumer.form.itemExtraMessage.username': 'Username should be unique', + 'page.consumer.form.itemExtraMessage.username': 'Name should be unique', 'page.consumer.notification.warning.enableAuthenticationPlugin': 'Please enable one authentication plugin', - 'page.consumer.username': 'Username', + 'page.consumer.username': 'Name', 'page.consumer.updateTime': 'Update Time', }; diff --git a/web/src/pages/Consumer/locales/zh-CN.ts b/web/src/pages/Consumer/locales/zh-CN.ts index c54bcc2f77..ea89de99f4 100644 --- a/web/src/pages/Consumer/locales/zh-CN.ts +++ b/web/src/pages/Consumer/locales/zh-CN.ts @@ -17,8 +17,8 @@ export default { 'page.consumer.form.itemRuleMessage.username': '最大长度100,仅支持字母、数字和 _ ,且只能以字母开头', - 'page.consumer.form.itemExtraMessage.username': '用户名需唯一', + 'page.consumer.form.itemExtraMessage.username': '名称需唯一', 'page.consumer.notification.warning.enableAuthenticationPlugin': '请启用一种身份认证类插件', - 'page.consumer.username': '用户名', + 'page.consumer.username': '名称', 'page.consumer.updateTime': '更新时间', }; From f71acb10d176d05e100588df7761be8f0bac85a0 Mon Sep 17 00:00:00 2001 From: liuxiran Date: Wed, 6 Jan 2021 10:38:56 +0800 Subject: [PATCH 02/11] fix(be): change bodyParams to string to accept any content-type (#1202) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: change bodyParams to string to accept any content-type * fix: ci error * fix: change bodyparam to type string in e2e test case * fix: ci Co-authored-by: 琚致远 --- .../handler/route_online_debug/route_online_debug.go | 6 +++--- api/test/e2e/route_online_debug_test.go | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/api/internal/handler/route_online_debug/route_online_debug.go b/api/internal/handler/route_online_debug/route_online_debug.go index 18241b7d85..ac1c50230a 100644 --- a/api/internal/handler/route_online_debug/route_online_debug.go +++ b/api/internal/handler/route_online_debug/route_online_debug.go @@ -52,7 +52,7 @@ func (h *Handler) ApplyRoute(r *gin.Engine) { type ParamsInput struct { URL string `json:"url,omitempty"` RequestProtocol string `json:"request_protocol,omitempty"` - BodyParams map[string]string `json:"body_params,omitempty"` + BodyParams string `json:"body_params,omitempty"` Method string `json:"method,omitempty"` HeaderParams map[string][]string `json:"header_params,omitempty"` } @@ -88,11 +88,11 @@ type HTTPProtocolSupport struct { func (h *HTTPProtocolSupport) RequestForwarding(c droplet.Context) (interface{}, error) { paramsInput := c.Input().(*ParamsInput) - bodyParams, _ := json.Marshal(paramsInput.BodyParams) + bodyParams := paramsInput.BodyParams client := &http.Client{} client.Timeout = 5 * time.Second - req, err := http.NewRequest(strings.ToUpper(paramsInput.Method), paramsInput.URL, strings.NewReader(string(bodyParams))) + req, err := http.NewRequest(strings.ToUpper(paramsInput.Method), paramsInput.URL, strings.NewReader(bodyParams)) if err != nil { return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err } diff --git a/api/test/e2e/route_online_debug_test.go b/api/test/e2e/route_online_debug_test.go index 4796d20fb6..fba4f03826 100644 --- a/api/test/e2e/route_online_debug_test.go +++ b/api/test/e2e/route_online_debug_test.go @@ -242,10 +242,7 @@ func TestRoute_Online_Debug_Route_With_Body_Params(t *testing.T) { "url": "` + APISIXInternalUrl + `/hello", "request_protocol": "http", "method": "POST", - "body_params": { - "name": "test", - "desc": "online debug route with body params" - } + "body_params": "{\"name\":\"test\",\"desc\":\"online debug route with body params\"}" }`, Headers: map[string]string{"Authorization": token}, ExpectStatus: http.StatusOK, From e3fd02b6a82ec6a1c96c21821b64c375ec556f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=9A=E8=87=B4=E8=BF=9C?= Date: Wed, 6 Jan 2021 11:27:12 +0800 Subject: [PATCH 03/11] chore: use the correct API version (#1215) Co-authored-by: litesun --- api/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/VERSION b/api/VERSION index bb576dbde1..1f7391f92b 100644 --- a/api/VERSION +++ b/api/VERSION @@ -1 +1 @@ -2.3 +master From 6c6e73b8f9b2c975377ec63f3dc6e9eca2bae645 Mon Sep 17 00:00:00 2001 From: liuxiran Date: Wed, 6 Jan 2021 12:32:38 +0800 Subject: [PATCH 04/11] fix: online debug body params support content-type x-www-form-urlencoded (#1201) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: online debug body params support content-type x-www-form-urlencoded * fix: body code mirror support different mode * fix: use enum instead of real string * fix: lint error Co-authored-by: 琚致远 --- .../components/DebugViews/DebugDrawView.tsx | 108 +++++++++++++++--- .../Route/components/DebugViews/index.less | 20 ++-- web/src/pages/Route/constants.ts | 6 + web/src/pages/Route/typing.d.ts | 8 +- 4 files changed, 117 insertions(+), 25 deletions(-) diff --git a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx index 5991fbf0b4..9180fe6d52 100644 --- a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx +++ b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx @@ -14,8 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect, useState } from 'react'; -import { Input, Select, Card, Tabs, Form, Drawer, Spin, notification } from 'antd'; +import React, { useEffect, useState, useRef } from 'react'; +import { Input, Select, Card, Tabs, Form, Drawer, Spin, notification, Radio } from 'antd'; import { useIntl } from 'umi'; import CodeMirror from '@uiw/react-codemirror'; import { PanelSection } from '@api7-dashboard/ui'; @@ -27,6 +27,8 @@ import { DEFAULT_DEBUG_PARAM_FORM_DATA, DEFAULT_DEBUG_AUTH_FORM_DATA, PROTOCOL_SUPPORTED, + DEBUG_BODY_TYPE_SUPPORTED, + DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED, } from '../../constants'; import { DebugParamsView, AuthenticationView } from '.'; import { debugRoute } from '../../service'; @@ -48,7 +50,17 @@ const DebugDrawView: React.FC = (props) => { const [responseCode, setResponseCode] = useState(); const [loading, setLoading] = useState(false); const [codeMirrorHeight, setCodeMirrorHeight] = useState(50); + const bodyCodeMirrorRef = useRef(null); + const [bodyType, setBodyType] = useState('none'); const methodWithoutBody = ['GET', 'HEAD']; + const [bodyCodeMirrorMode, setBodyCodeMirrorMode] = useState(DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED[0].mode) + + enum DebugBodyType { + None = 0, + FormUrlencoded, + Json, + RawInput, + } const resetForms = () => { queryForm.setFieldsValue(DEFAULT_DEBUG_PARAM_FORM_DATA); @@ -56,24 +68,44 @@ const DebugDrawView: React.FC = (props) => { headerForm.setFieldsValue(DEFAULT_DEBUG_PARAM_FORM_DATA); authForm.setFieldsValue(DEFAULT_DEBUG_AUTH_FORM_DATA); setResponseCode(`${formatMessage({ id: 'page.route.debug.showResultAfterSendRequest' })}`); + setBodyType(DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.None]); }; useEffect(() => { resetForms(); }, []); - const transformBodyParamsFormData = (formData: RouteModule.debugRequestParamsFormData[]) => { - let transformData = {}; - (formData || []) - .filter((data) => data.check) - .forEach((data) => { - transformData = { - ...transformData, - [data.key]: data.value, - }; - }); + const transformBodyParamsFormData = () => { + let transformDataForm: string[]; + let transformDataJson: object; + const formData: RouteModule.debugRequestParamsFormData[] = bodyForm.getFieldsValue().params; + switch (bodyType) { + case 'x-www-form-urlencoded': + transformDataForm = (formData || []) + .filter((data) => data.check) + .map((data) => { + return `${data.key}=${data.value}` + }); - return transformData; + return transformDataForm.join('&'); + case 'json': + transformDataJson = {}; + (formData || []) + .filter((data) => data.check) + .forEach((data) => { + transformDataJson = { + ...transformDataJson, + [data.key]: data.value, + }; + }); + + return JSON.stringify(transformDataJson); + case 'raw input': + return bodyCodeMirrorRef.current.editor.getValue(); + case 'none': + default: + return undefined; + } }; const transformHeaderAndQueryParamsFormData = ( @@ -128,7 +160,7 @@ const DebugDrawView: React.FC = (props) => { return; } const queryFormData = transformHeaderAndQueryParamsFormData(queryForm.getFieldsValue().params); - const bodyFormData = transformBodyParamsFormData(bodyForm.getFieldsValue().params); + const bodyFormData = transformBodyParamsFormData(); const pureHeaderFormData = transformHeaderAndQueryParamsFormData( headerForm.getFieldsValue().params, ); @@ -233,7 +265,53 @@ const DebugDrawView: React.FC = (props) => { {showBodyTab && ( - + {setBodyType(e.target.value)}} value={bodyType}> + { + DEBUG_BODY_TYPE_SUPPORTED.map((type) => ( + {type} + )) + } + + {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput] && } +
+ { + (bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.FormUrlencoded] || bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.Json]) && + + } + + { + (bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput]) && +
+ + + +
+ } +
)} diff --git a/web/src/pages/Route/components/DebugViews/index.less b/web/src/pages/Route/components/DebugViews/index.less index f9cb5dcf08..65ee10bef6 100644 --- a/web/src/pages/Route/components/DebugViews/index.less +++ b/web/src/pages/Route/components/DebugViews/index.less @@ -24,21 +24,23 @@ .ant-drawer-body { padding: 16px; } - .ant-radio-wrapper { - display: block; - height: 30px; - line-height: 30px; - } .ant-form-item { margin-bottom: 8px; } - .ant-radio-group { - margin-right: 16px; - border-right: 1px solid #e0e0e0; - } } .authForm { display: flex; margin: 16px; + :global { + .ant-radio-wrapper { + display: block; + height: 30px; + line-height: 30px; + } + .ant-radio-group { + margin-right: 16px; + border-right: 1px solid #e0e0e0; + } + } } } diff --git a/web/src/pages/Route/constants.ts b/web/src/pages/Route/constants.ts index 1a8dc46a3d..22b2e61f7d 100644 --- a/web/src/pages/Route/constants.ts +++ b/web/src/pages/Route/constants.ts @@ -98,8 +98,14 @@ export const DEFAULT_DEBUG_PARAM_FORM_DATA = { value: '', }, ], + type: 'json', }; export const DEFAULT_DEBUG_AUTH_FORM_DATA = { authType: 'none', }; + +export const DEBUG_BODY_TYPE_SUPPORTED: RouteModule.DebugBodyType[]= ['none', 'x-www-form-urlencoded','json','raw input']; + +// Note: codemirror mode: apl for text; javascript for json(need to format); xml for xml; +export const DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED = [{name: 'Json', mode: 'javascript'}, {name: 'Text', mode: 'apl'}, {name: 'XML', mode:'xml'}] diff --git a/web/src/pages/Route/typing.d.ts b/web/src/pages/Route/typing.d.ts index 60afe5f87b..8ae302f813 100644 --- a/web/src/pages/Route/typing.d.ts +++ b/web/src/pages/Route/typing.d.ts @@ -251,7 +251,7 @@ declare namespace RouteModule { // TODO: grpc and websocket type debugRequest = { url: string; - request_protocol: 'http' | 'https' | 'grpc' | 'websocket'; + request_protocol: RequestProtocol | 'grpc'; method: string; body_params?: any; header_params?: any; @@ -271,6 +271,12 @@ declare namespace RouteModule { type DebugViewProps = { form: FormInstance; }; + type DebugBodyType = 'none' | 'x-www-form-urlencoded' | 'json' | 'raw input'; + type DebugDodyViewProps = { + form: FormInstance; + changeBodyParamsType:(type: DebugBodyType) => void; + codeMirrorRef: any; + }; type DebugDrawProps = { visible: boolean; onClose(): void; From 291e32149092cbf86314ef76d93e27481318aeaa Mon Sep 17 00:00:00 2001 From: litesun Date: Wed, 6 Jan 2021 14:17:55 +0800 Subject: [PATCH 05/11] feat: add tips when plugin type is auth and schemaType is not consumer (#1219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 琚致远 --- web/src/components/Plugin/PluginDetail.tsx | 17 +++++++++++------ web/src/components/Plugin/PluginPage.tsx | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/web/src/components/Plugin/PluginDetail.tsx b/web/src/components/Plugin/PluginDetail.tsx index 2ff336c730..b154985919 100644 --- a/web/src/components/Plugin/PluginDetail.tsx +++ b/web/src/components/Plugin/PluginDetail.tsx @@ -15,7 +15,7 @@ * limitations under the License. */ import React, { useEffect, useRef } from 'react'; -import { Button, notification, PageHeader, Switch, Form, Select, Divider, Drawer } from 'antd'; +import { Button, notification, PageHeader, Switch, Form, Select, Divider, Drawer, Alert } from 'antd'; import { useIntl } from 'umi'; import CodeMirror from '@uiw/react-codemirror'; import { js_beautify } from 'js-beautify'; @@ -29,6 +29,7 @@ type Props = { type?: 'global' | 'scoped'; schemaType: PluginComponent.Schema; initialData: object; + pluginList: PluginComponent.Meta[], readonly?: boolean; visible: boolean; onClose?: () => void; @@ -65,15 +66,17 @@ const PluginDetail: React.FC = ({ type = 'scoped', schemaType = 'route', visible, + pluginList = [], readonly = false, initialData = {}, - onClose = () => {}, - onChange = () => {}, + onClose = () => { }, + onChange = () => { }, }) => { const { formatMessage } = useIntl(); const [form] = Form.useForm(); const ref = useRef(null); - const data = initialData[name]; + const data = initialData[name] || {}; + const pluginType = pluginList.find(item => item.name === name)?.type useEffect(() => { form.setFieldsValue({ disable: initialData[name] && !initialData[name].disable }); @@ -149,7 +152,7 @@ const PluginDetail: React.FC = ({ placement="right" closable={false} onClose={onClose} - width={600} + width={700} footer={
{' '} @@ -204,7 +207,9 @@ const PluginDetail: React.FC = ({ Data Editor + : <>Current plugin: {name}} ghost={false} extra={[ , ]} - bodyStyle={{ - height: 151, - display: 'flex', - justifyContent: 'center', - textAlign: 'center', - }} title={[
@@ -123,8 +130,21 @@ const PluginPage: React.FC = ({
, ]} - style={{ height: 258, width: 200 }} - /> + bodyStyle={{ + minHeight: 151, + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }} + style={{ width: 200 }} + > + {Boolean(PLUGIN_ICON_LIST[item.name]) && PLUGIN_ICON_LIST[item.name]} + {Boolean(!PLUGIN_ICON_LIST[item.name]) && pluginImg} + ))} ); diff --git a/web/src/components/Plugin/data.tsx b/web/src/components/Plugin/data.tsx index 40b1d608d7..75baecbf4f 100644 --- a/web/src/components/Plugin/data.tsx +++ b/web/src/components/Plugin/data.tsx @@ -18,149 +18,11 @@ import React from 'react'; import IconFont from '../IconFont'; -export const PLUGIN_MAPPER_SOURCE: Record> = { - 'limit-req': { - category: 'Limit traffic', - priority: 1, - }, - 'limit-count': { - category: 'Limit traffic', - priority: 2, - }, - 'limit-conn': { - category: 'Limit traffic', - priority: 3, - }, - prometheus: { - category: 'Observability', - priority: 1, - avatar: , - }, - skywalking: { - category: 'Observability', - priority: 2, - avatar: , - }, - zipkin: { - category: 'Observability', - priority: 3, - }, - 'request-id': { - category: 'Observability', - priority: 4, - }, - 'key-auth': { - category: 'Authentication', - priority: 1, - }, - 'basic-auth': { - category: 'Authentication', - priority: 3, - }, - 'node-status': { - category: 'Other', - }, - 'jwt-auth': { - category: 'Authentication', - priority: 2, - avatar: , - }, - 'authz-keycloak': { - category: 'Authentication', - priority: 5, - avatar: , - }, - 'ip-restriction': { - category: 'Security', - priority: 1, - }, - 'grpc-transcode': { - category: 'Other', - }, - 'serverless-pre-function': { - category: 'Other', - }, - 'serverless-post-function': { - category: 'Other', - }, - 'openid-connect': { - category: 'Authentication', - priority: 4, - avatar: , - }, - 'proxy-rewrite': { - category: 'Other', - }, - redirect: { - category: 'Other', - hidden: true, - }, - 'response-rewrite': { - category: 'Other', - }, - 'fault-injection': { - category: 'Security', - priority: 4, - }, - 'udp-logger': { - category: 'Log', - priority: 4, - }, - 'wolf-rbac': { - category: 'Other', - }, - 'proxy-cache': { - category: 'Other', - priority: 1, - }, - 'tcp-logger': { - category: 'Log', - priority: 3, - }, - 'proxy-mirror': { - category: 'Other', - priority: 2, - }, - 'kafka-logger': { - category: 'Log', - priority: 1, - avatar: , - }, - cors: { - category: 'Security', - priority: 2, - }, - 'uri-blocker': { - category: 'Security', - priority: 3, - }, - 'request-validator': { - category: 'Security', - priority: 5, - }, - heartbeat: { - category: 'Other', - hidden: true, - }, - 'batch-requests': { - category: 'Other', - }, - 'http-logger': { - category: 'Log', - priority: 2, - }, - 'mqtt-proxy': { - category: 'Other', - }, - oauth: { - category: 'Security', - }, - syslog: { - category: 'Log', - priority: 5, - }, - echo: { - category: 'Other', - priority: 3, - }, +export const PLUGIN_ICON_LIST: Record = { + prometheus: , + skywalking: , + 'jwt-auth': , + 'authz-keycloak': , + 'openid-connect': , + 'kafka-logger': , }; diff --git a/web/src/components/Plugin/index.ts b/web/src/components/Plugin/index.ts index 940c9f0e88..cee617dfc5 100644 --- a/web/src/components/Plugin/index.ts +++ b/web/src/components/Plugin/index.ts @@ -15,4 +15,4 @@ * limitations under the License. */ export { default } from './PluginPage'; -export { PLUGIN_MAPPER_SOURCE } from './data'; +export { PLUGIN_ICON_LIST } from './data'; diff --git a/web/src/pages/Plugin/List.tsx b/web/src/pages/Plugin/List.tsx index 2e9c18f788..3a73cc1423 100644 --- a/web/src/pages/Plugin/List.tsx +++ b/web/src/pages/Plugin/List.tsx @@ -24,15 +24,20 @@ import { omit } from 'lodash'; import PluginDetail from '@/components/Plugin/PluginDetail'; -import { fetchList, createOrUpdate } from './service'; +import { fetchList, fetchPluginList, createOrUpdate } from './service'; const Page: React.FC = () => { const ref = useRef(); const { formatMessage } = useIntl(); const [visible, setVisible] = useState(false); const [initialData, setInitialData] = useState({}); + const [pluginList, setPluginList] = useState([]); const [name, setName] = useState(''); + useEffect(() => { + fetchPluginList().then(setPluginList); + }, []); + useEffect(() => { if (!name) { fetchList().then(({ data }) => { @@ -98,6 +103,7 @@ const Page: React.FC = () => { type="global" schemaType="route" initialData={initialData} + pluginList={pluginList} onClose={() => { setVisible(false); }} diff --git a/web/src/pages/Plugin/service.ts b/web/src/pages/Plugin/service.ts index 7c51d03892..8a030ca84f 100644 --- a/web/src/pages/Plugin/service.ts +++ b/web/src/pages/Plugin/service.ts @@ -46,3 +46,9 @@ export const createOrUpdate = (data: Partial method: 'PUT', data: { id: DEFAULT_GLOBAL_RULE_ID, ...data }, }); + + export const fetchPluginList = () => { + return request>('/plugins?all=true').then((data) => { + return data.data; + }); + }; From 55e04494e6cafde51d60bad0e4ac722d4e2c8735 Mon Sep 17 00:00:00 2001 From: liuxiran Date: Thu, 7 Jan 2021 22:48:57 +0800 Subject: [PATCH 10/11] fix: get and head request did not contain bodyparams (#1239) --- web/src/pages/Route/components/DebugViews/DebugDrawView.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx index 18b3d0e66d..daf516d097 100644 --- a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx +++ b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx @@ -81,6 +81,9 @@ const DebugDrawView: React.FC = (props) => { let transformDataForm: string[]; let transformDataJson: object; const formData: RouteModule.debugRequestParamsFormData[] = bodyForm.getFieldsValue().params; + if (methodWithoutBody.includes(httpMethod)) { + return undefined + } switch (bodyType) { case 'x-www-form-urlencoded': transformDataForm = (formData || []) From 40d8f97de8770187599ff5963617049603020e92 Mon Sep 17 00:00:00 2001 From: Joey Date: Fri, 8 Jan 2021 09:16:30 +0800 Subject: [PATCH 11/11] feat: Add backport type in PR template (#1230) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: imjoey Co-authored-by: 琚致远 --- .github/PULL_REQUEST_TEMPLATE | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 37c9ccb2da..74f8569ce5 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -4,6 +4,7 @@ Please answer these questions before submitting a pull request - [ ] Bugfix - [ ] New feature provided - [ ] Improve performance +- [ ] Backport patches - Related issues @@ -16,3 +17,13 @@ ___ ___ ### New feature or improvement - Describe the details and related test reports. + +___ +### Backport patches +- Why need to backport? + +- Source branch + +- Related commits and pull requests + +- Target branch