From 14166d9ee4ae630c78ebacb78693239478985cdc Mon Sep 17 00:00:00 2001 From: litesun Date: Wed, 14 Apr 2021 22:56:49 +0800 Subject: [PATCH] feat: add proxy-mirror plugin form (#1725) --- ...te-consumer-with-proxy-mirror-form.spec.js | 103 +++++++++++++++++ ...reate-route-with-proxy-mirror-form.spec.js | 105 ++++++++++++++++++ web/cypress/support/commands.js | 11 ++ web/src/components/Plugin/UI/plugin.tsx | 5 +- web/src/components/Plugin/UI/proxy-mirror.tsx | 61 ++++++++++ web/src/components/Plugin/locales/en-US.ts | 5 + web/src/components/Plugin/locales/zh-CN.ts | 5 + 7 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 web/cypress/integration/consumer/create-consumer-with-proxy-mirror-form.spec.js create mode 100644 web/cypress/integration/route/create-route-with-proxy-mirror-form.spec.js create mode 100644 web/src/components/Plugin/UI/proxy-mirror.tsx diff --git a/web/cypress/integration/consumer/create-consumer-with-proxy-mirror-form.spec.js b/web/cypress/integration/consumer/create-consumer-with-proxy-mirror-form.spec.js new file mode 100644 index 0000000000..2325ec43b6 --- /dev/null +++ b/web/cypress/integration/consumer/create-consumer-with-proxy-mirror-form.spec.js @@ -0,0 +1,103 @@ +/* + * 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-undef */ + +context('Create and delete consumer with proxy-mirror plugin form', () => { + beforeEach(() => { + cy.login(); + + cy.fixture('selector.json').as('domSelector'); + cy.fixture('data.json').as('data'); + }); + + const selector = { + host: "#host", + alert: "[role=alert]" + } + + it('should create consumer with proxy-mirror form', function () { + cy.visit('/'); + cy.contains('Consumer').click(); + cy.get(this.domSelector.empty).should('be.visible'); + cy.contains('Create').click(); + // basic information + cy.get(this.domSelector.username).type(this.data.consumerName); + cy.get(this.domSelector.description).type(this.data.description); + cy.contains('Next').click(); + + // config auth plugin + cy.contains(this.domSelector.pluginCard, 'key-auth').within(() => { + cy.contains('Enable').click({ + force: true, + }); + }); + cy.focused(this.domSelector.drawer).should('exist'); + cy.get(this.domSelector.disabledSwitcher).click(); + // edit codemirror + cy.get(this.domSelector.codeMirror) + .first() + .then((editor) => { + editor[0].CodeMirror.setValue( + JSON.stringify({ + key: 'test', + }), + ); + cy.contains('button', 'Submit').click(); + }); + + cy.contains(this.domSelector.pluginCard, 'proxy-mirror').within(() => { + cy.contains('Enable').click({ + force: true, + }); + }); + + cy.focused(this.domSelector.drawer).should('exist'); + + // config proxy-mirror form with wrong host + cy.get(selector.host).type('127.0.0.1:1999'); + cy.get(selector.alert).contains('address needs to contain schema: http or https, not URI part'); + cy.get(this.domSelector.drawer).within(() => { + cy.contains('Submit').click({ + force: true, + }); + }); + cy.get(this.domSelector.notification).should('contain', 'Invalid plugin data'); + cy.get(this.domSelector.notificationCloseIcon).click(); + + // config proxy-mirror form with correct host + cy.get(selector.host).clear().type('http://127.0.0.1:1999'); + cy.get(selector.alert).should('not.exist'); + cy.get(this.domSelector.disabledSwitcher).click(); + cy.get(this.domSelector.drawer).within(() => { + cy.contains('Submit').click({ + force: true, + }); + }); + cy.get(this.domSelector.drawer).should('not.exist'); + + cy.contains('button', 'Next').click(); + cy.contains('button', 'Submit').click(); + cy.get(this.domSelector.notification).should('contain', this.data.createConsumerSuccess); + }); + + it('should delete the consumer', function () { + cy.visit('/consumer/list'); + cy.contains(this.data.consumerName).should('be.visible').siblings().contains('Delete').click(); + cy.contains('button', 'Confirm').click(); + cy.get(this.domSelector.notification).should('contain', this.data.deleteConsumerSuccess); + }); +}); diff --git a/web/cypress/integration/route/create-route-with-proxy-mirror-form.spec.js b/web/cypress/integration/route/create-route-with-proxy-mirror-form.spec.js new file mode 100644 index 0000000000..7637568731 --- /dev/null +++ b/web/cypress/integration/route/create-route-with-proxy-mirror-form.spec.js @@ -0,0 +1,105 @@ +/* + * 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-undef */ + +context('Create and delete route with proxy-mirror form', () => { + const selector = { + host: "#host", + alert: ".ant-form-item-explain" + } + + beforeEach(() => { + cy.login(); + + cy.fixture('selector.json').as('domSelector'); + cy.fixture('data.json').as('data'); + }); + + it('should create route with proxy-mirror form', function () { + cy.visit('/'); + cy.contains('Route').click(); + cy.get(this.domSelector.empty).should('be.visible'); + cy.contains('Create').click(); + cy.contains('Next').click().click(); + cy.get(this.domSelector.name).type('routeName'); + cy.get(this.domSelector.description).type('desc'); + cy.contains('Next').click(); + + cy.get(this.domSelector.nodes_0_host).type('127.0.0.1'); + cy.contains('Next').click(); + + // config proxy-mirror plugin + cy.contains('proxy-mirror').parents(this.domSelector.pluginCardBordered).within(() => { + cy.get('button').click({ + force: true + }); + }); + + cy.get(this.domSelector.drawer).should('be.visible').within(() => { + cy.get(this.domSelector.disabledSwitcher).click(); + cy.get(this.domSelector.checkedSwitcher).should('exist'); + }); + + // config proxy-mirror form with wrong host + cy.get(selector.host).type('127.0.0.1:1999'); + cy.get(selector.alert).contains('address needs to contain schema: http or https, not URI part'); + cy.get(this.domSelector.drawer).within(() => { + cy.contains('Submit').click({ + force: true, + }); + }); + cy.get(this.domSelector.notification).should('contain', 'Invalid plugin data'); + cy.get(this.domSelector.notificationCloseIcon).click(); + + // config proxy-mirror form with correct host + cy.get(selector.host).clear().type('http://127.0.0.1:1999'); + cy.get(selector.alert).should('not.exist'); + cy.get(this.domSelector.disabledSwitcher).click(); + cy.get(this.domSelector.drawer).within(() => { + cy.contains('Submit').click({ + force: true, + }); + }); + cy.get(this.domSelector.drawer).should('not.exist'); + + cy.contains('button', 'Next').click(); + cy.contains('button', 'Submit').click(); + cy.contains(this.data.submitSuccess); + + // back to route list page + cy.contains('Goto List').click(); + cy.url().should('contains', 'routes/list'); + }); + + it('should delete the route', function () { + cy.visit('/routes/list'); + const { + domSelector, + data + } = this; + + cy.get(domSelector.name).clear().type('routeName'); + cy.contains('Search').click(); + cy.contains('routeName').siblings().contains('More').click(); + cy.contains('Delete').click(); + cy.get(domSelector.deleteAlert).should('be.visible').within(() => { + cy.contains('OK').click(); + }); + cy.get(domSelector.notification).should('contain', data.deleteRouteSuccess); + cy.get(domSelector.notificationCloseIcon).click(); + }); +}); diff --git a/web/cypress/support/commands.js b/web/cypress/support/commands.js index a55a8d0648..a4b08e4145 100644 --- a/web/cypress/support/commands.js +++ b/web/cypress/support/commands.js @@ -66,6 +66,17 @@ Cypress.Commands.add('configurePlugins', (cases) => { // NOTE: wait for the Drawer to appear on the DOM cy.focused(domSelector.drawer).should('exist'); + + cy.get(domSelector.codeMirrorMode).invoke('text').then(text => { + if (text === 'Form') { + cy.wait(5000); + cy.get(domSelector.codeMirrorMode).should('be.visible'); + cy.get(domSelector.codeMirrorMode).click(); + cy.get(domSelector.selectDropdown).should('be.visible'); + cy.get(domSelector.selectJSON).click(); + } + }); + cy.get(domSelector.drawer, { timeout }).within(() => { cy.get(domSelector.switch).click({ force: true, diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Plugin/UI/plugin.tsx index 79a2df5754..c045750121 100644 --- a/web/src/components/Plugin/UI/plugin.tsx +++ b/web/src/components/Plugin/UI/plugin.tsx @@ -20,6 +20,7 @@ import { Empty } from 'antd'; import { useIntl } from 'umi'; import BasicAuth from './basic-auth'; +import ProxyMirror from './proxy-mirror'; import LimitConn from './limit-conn'; type Props = { @@ -28,7 +29,7 @@ type Props = { renderForm: boolean } -export const PLUGIN_UI_LIST = ['basic-auth', 'limit-conn']; +export const PLUGIN_UI_LIST = ['basic-auth', 'limit-conn', 'proxy-mirror']; export const PluginForm: React.FC = ({ name, renderForm, form }) => { @@ -39,6 +40,8 @@ export const PluginForm: React.FC = ({ name, renderForm, form }) => { switch (name) { case 'basic-auth': return + case 'proxy-mirror': + return case 'limit-conn': return default: diff --git a/web/src/components/Plugin/UI/proxy-mirror.tsx b/web/src/components/Plugin/UI/proxy-mirror.tsx new file mode 100644 index 0000000000..aa5db8f7a9 --- /dev/null +++ b/web/src/components/Plugin/UI/proxy-mirror.tsx @@ -0,0 +1,61 @@ +/* + * 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 React from 'react'; +import type { FormInstance } from 'antd/es/form'; +import { Form, Input } from 'antd'; +import { useIntl } from 'umi'; + +type Props = { + form: FormInstance; +}; + +const FORM_ITEM_LAYOUT = { + labelCol: { + span: 4, + }, + wrapperCol: { + span: 10 + }, +}; + +const ProxyMirror: React.FC = ({ form }) => { + const { formatMessage } = useIntl(); + + return ( +
+ + + +
+ ); +} + +export default ProxyMirror; diff --git a/web/src/components/Plugin/locales/en-US.ts b/web/src/components/Plugin/locales/en-US.ts index 2b63c63573..f235b95cd9 100644 --- a/web/src/components/Plugin/locales/en-US.ts +++ b/web/src/components/Plugin/locales/en-US.ts @@ -22,6 +22,11 @@ export default { 'component.plugin.pluginTemplate.tip1': '1. When a route already have plugins field configured, the plugins in the plugin template will be merged into it.', 'component.plugin.pluginTemplate.tip2': '2. The same plugin in the plugin template will override one in the plugins', + // proxy-mirror + 'component.pluginForm.proxy-mirror.host.tooltip': 'Specify a mirror service address, e.g. http://127.0.0.1:9797 (address needs to contain schema: http or https, not URI part)', + 'component.pluginForm.proxy-mirror.host.extra': 'e.g. http://127.0.0.1:9797 (address needs to contain schema: http or https, not URI part)', + 'component.pluginForm.proxy-mirror.host.ruletip': 'address needs to contain schema: http or https, not URI part', + // limit-conn 'component.pluginForm.limit-conn.conn.tooltip': 'the maximum number of concurrent requests allowed. Requests exceeding this ratio (and below conn + burst) will get delayed(the latency seconds is configured by default_conn_delay) to conform to this threshold.', 'component.pluginForm.limit-conn.burst.tooltip': 'the number of excessive concurrent requests (or connections) allowed to be delayed.', diff --git a/web/src/components/Plugin/locales/zh-CN.ts b/web/src/components/Plugin/locales/zh-CN.ts index c106b2e0dc..2f2d5ba14b 100644 --- a/web/src/components/Plugin/locales/zh-CN.ts +++ b/web/src/components/Plugin/locales/zh-CN.ts @@ -22,6 +22,11 @@ export default { 'component.plugin.pluginTemplate.tip1': '1. 若路由已配置插件,则插件模板数据将与已配置的插件数据合并。', 'component.plugin.pluginTemplate.tip2': '2. 插件模板相同的插件会覆盖掉原有的插件。', + // proxy-mirror + 'component.pluginForm.proxy-mirror.host.tooltip': '指定镜像服务地址,例如:http://127.0.0.1:9797(地址中需要包含 schema :http或https,不能包含 URI 部分)', + 'component.pluginForm.proxy-mirror.host.extra': '例如:http://127.0.0.1:9797(地址中需要包含 schema:http或https,不能包含 URI 部分)', + 'component.pluginForm.proxy-mirror.host.ruletip': '地址中需要包含 schema :http或https,不能包含 URI 部分', + // limit-conn 'component.pluginForm.limit-conn.conn.tooltip': '允许的最大并发请求数。超过 conn 的限制、但是低于 conn + burst 的请求,将被延迟处理。', 'component.pluginForm.limit-conn.burst.tooltip': '允许被延迟处理的并发请求数。',