From 17ff973a66b96547c2668b91178f3976710d2eed Mon Sep 17 00:00:00 2001 From: nic-chen <33000667+nic-chen@users.noreply.github.com> Date: Sun, 18 Apr 2021 00:40:54 +0800 Subject: [PATCH 1/7] feat: support auto build rpm package for dashboard (#1766) --- .github/workflows/auto-build-rpm.yml | 70 ++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/auto-build-rpm.yml diff --git a/.github/workflows/auto-build-rpm.yml b/.github/workflows/auto-build-rpm.yml new file mode 100644 index 0000000000..7feaeb0480 --- /dev/null +++ b/.github/workflows/auto-build-rpm.yml @@ -0,0 +1,70 @@ +name: Auto Build RPM + +on: + push: + branches: [master, 'release/**'] + paths-ignore: + - 'docs/**' + pull_request: + branches: [master] + paths-ignore: + - 'docs/**' + +jobs: + auto_build_rpm: + name: auto build rpm package + runs-on: ubuntu-latest + + services: + etcd: + image: bitnami/etcd:3.4.0 + ports: + - 2379:2379 + - 2380:2380 + env: + ALLOW_NONE_AUTHENTICATION: yes + ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Extract branch name + id: branch_env + shell: bash + run: | + echo "##[set-output name=version;]$(echo ${GITHUB_REF##*/})" + + - name: Build rpm package + run: | + export VERSION=${{ steps.branch_env.outputs.version }} + sudo gem install --no-document fpm + git clone https://github.com/api7/apisix-build-tools.git + cd apisix-build-tools + export checkout=master + if [ "$VERSION" != "merge" && "$VERSION" != "master" ];then + export checkout=release/${VERSION} + fi + make package type=rpm app=dashboard version=${VERSION} checkout=${checkout} + + - name: Run centos7 docker and mapping apisix into container + run: | + docker run -itd -v $PWD:/apisix-dashboard --name centos7Instance --net="host" docker.io/centos:7 /bin/bash + + - name: Install rpm package + run: | + export VERSION=${{ steps.branch_env.outputs.version }} + docker exec centos7Instance bash -c "cd apisix-dashboard && yum install -y ./apisix-build-tools/output/apisix-dashboard-${VERSION}-0.x86_64.rpm" + docker logs centos7Instance + # Dependencies are attached with rpm, so revert `make deps` + docker exec centos7Instance bash -c "cd /usr/local/apisix/dashboard/ && nohup ./manager-api &" + + - name: Run test cases + run: | + api/test/shell/manager_smoking.sh -s true + + - name: Publish Artifact + uses: actions/upload-artifact@v2.2.2 + with: + name: "rpm" + path: "./apisix-build-tools/output/apisix-dashboard-${{ steps.branch_env.outputs.version }}-0.x86_64.rpm" From c9d2bd328a5c7f48f1a7be27d7f0ec0af3748b64 Mon Sep 17 00:00:00 2001 From: Cliff Su Date: Sun, 18 Apr 2021 18:43:36 +0800 Subject: [PATCH 2/7] feat: improve the config plugin button enable disable status (#1610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 琚致远 --- .../create-edit-delete-plugin-template.spec.js | 2 +- ...reate-route-with-limit-count-plugin-form.spec.js | 6 +++--- .../service/edit-service-with-upstream.spec.js | 1 + web/cypress/support/commands.js | 2 +- web/src/components/Plugin/PluginPage.tsx | 13 +++++++------ web/src/components/Plugin/locales/en-US.ts | 2 ++ web/src/components/Plugin/locales/zh-CN.ts | 2 ++ 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/web/cypress/integration/pluginTemplate/create-edit-delete-plugin-template.spec.js b/web/cypress/integration/pluginTemplate/create-edit-delete-plugin-template.spec.js index cd3a2a4857..255740c233 100644 --- a/web/cypress/integration/pluginTemplate/create-edit-delete-plugin-template.spec.js +++ b/web/cypress/integration/pluginTemplate/create-edit-delete-plugin-template.spec.js @@ -41,7 +41,7 @@ context('Create Configure and Delete PluginTemplate', () => { cy.contains('proxy-rewrite').should('not.exist'); cy.contains(this.domSelector.pluginCard, 'basic-auth').within(() => { - cy.contains('Enable').click({ + cy.get('button').click({ force: true, }); }); diff --git a/web/cypress/integration/route/create-route-with-limit-count-plugin-form.spec.js b/web/cypress/integration/route/create-route-with-limit-count-plugin-form.spec.js index f9db28bb56..b1df99e0e9 100644 --- a/web/cypress/integration/route/create-route-with-limit-count-plugin-form.spec.js +++ b/web/cypress/integration/route/create-route-with-limit-count-plugin-form.spec.js @@ -57,7 +57,7 @@ context('Create and delete route with limit-count form', () => { // config limit-count form with local policy cy.contains(this.domSelector.pluginCard, 'limit-count').within(() => { - cy.contains('Enable').click({ + cy.get('button').click({ force: true, }); }); @@ -75,7 +75,7 @@ context('Create and delete route with limit-count form', () => { // config limit-count form with redis policy cy.contains(this.domSelector.pluginCard, 'limit-count').within(() => { - cy.contains('Enable').click({ + cy.get('button').click({ force: true, }); }); @@ -97,7 +97,7 @@ context('Create and delete route with limit-count form', () => { // config limit-count form with redis policy cy.contains(this.domSelector.pluginCard, 'limit-count').within(() => { - cy.contains('Enable').click({ + cy.get('button').click({ force: true, }); }); diff --git a/web/cypress/integration/service/edit-service-with-upstream.spec.js b/web/cypress/integration/service/edit-service-with-upstream.spec.js index 87be509c63..66e9c2f94e 100644 --- a/web/cypress/integration/service/edit-service-with-upstream.spec.js +++ b/web/cypress/integration/service/edit-service-with-upstream.spec.js @@ -40,6 +40,7 @@ context('Edit Service with Upstream', () => { it('should create a test service', function () { cy.visit('/'); + cy.get('.ant-empty').should('be.visible'); cy.contains('Service').click(); cy.get(this.domSelector.empty).should('be.visible'); cy.contains('Create').click(); diff --git a/web/cypress/support/commands.js b/web/cypress/support/commands.js index 590243cb5f..4592efbe30 100644 --- a/web/cypress/support/commands.js +++ b/web/cypress/support/commands.js @@ -59,7 +59,7 @@ Cypress.Commands.add('configurePlugins', (cases) => { cy.contains(name) .parents(domSelector.parents) .within(() => { - cy.contains('Enable').click({ + cy.get('button').click({ force: true, }); }); diff --git a/web/src/components/Plugin/PluginPage.tsx b/web/src/components/Plugin/PluginPage.tsx index 61e9bee6d0..f47f9fd538 100644 --- a/web/src/components/Plugin/PluginPage.tsx +++ b/web/src/components/Plugin/PluginPage.tsx @@ -183,16 +183,17 @@ const PluginPage: React.FC = ({ key={item.name} actions={[ , ]} title={[ diff --git a/web/src/components/Plugin/locales/en-US.ts b/web/src/components/Plugin/locales/en-US.ts index 26fa357e80..96d2b2b6e0 100644 --- a/web/src/components/Plugin/locales/en-US.ts +++ b/web/src/components/Plugin/locales/en-US.ts @@ -21,6 +21,8 @@ export default { 'component.step.select.pluginTemplate.select.option': 'Custom', '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', + 'component.plugin.enable': 'Enable', + 'component.plugin.disable': 'Disable', 'component.plugin.authentication': 'Authentication', 'component.plugin.security': 'Security', 'component.plugin.traffic': 'Traffic Control', diff --git a/web/src/components/Plugin/locales/zh-CN.ts b/web/src/components/Plugin/locales/zh-CN.ts index b88f6e80d1..b46a4137d2 100644 --- a/web/src/components/Plugin/locales/zh-CN.ts +++ b/web/src/components/Plugin/locales/zh-CN.ts @@ -21,6 +21,8 @@ export default { 'component.step.select.pluginTemplate.select.option': '手动配置', 'component.plugin.pluginTemplate.tip1': '1. 若路由已配置插件,则插件模板数据将与已配置的插件数据合并。', 'component.plugin.pluginTemplate.tip2': '2. 插件模板相同的插件会覆盖掉原有的插件。', + 'component.plugin.enable': '启用', + 'component.plugin.disable': '禁用', 'component.plugin.authentication': '身份验证', 'component.plugin.security': '安全防护', 'component.plugin.traffic': '流量控制', From c35203aa524d4ccf1e51990ae0855ad4cbd7608a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=9A=E8=87=B4=E8=BF=9C?= Date: Sun, 18 Apr 2021 20:50:55 +0800 Subject: [PATCH 3/7] chore: keep using English as comments & update plugin type (#1785) --- web/package.json | 1 - web/src/components/PanelSection/index.tsx | 39 +++++++++++++++++++ web/src/components/Plugin/PluginPage.tsx | 2 +- web/src/components/Plugin/data.tsx | 17 ++++---- web/src/components/Plugin/locales/en-US.ts | 1 - web/src/components/Plugin/locales/zh-CN.ts | 1 - .../RightContent/AvatarDropdown.tsx | 3 -- web/src/components/Upstream/UpstreamForm.tsx | 4 +- .../components/Upstream/components/TLS.tsx | 4 +- .../components/passive-check/Type.tsx | 4 +- web/src/components/Upstream/locales/en-US.ts | 2 + web/src/components/Upstream/locales/zh-CN.ts | 2 + web/src/global.less | 1 - web/src/helpers.tsx | 3 -- .../components/DebugViews/DebugDrawView.tsx | 3 +- .../components/Step1/MatchingRulesView.tsx | 24 +++++++++--- .../pages/Route/components/Step1/MetaView.tsx | 8 ++-- .../Route/components/Step1/ProxyRewrite.tsx | 3 +- .../components/Step1/RequestConfigView.tsx | 3 +- web/src/pages/Route/locales/en-US.ts | 7 ++++ web/src/pages/Route/locales/zh-CN.ts | 7 ++++ web/src/pages/Route/transform.ts | 10 +++-- web/src/pages/Route/typing.d.ts | 4 +- web/src/pages/Upstream/Create.tsx | 3 +- web/src/pages/Upstream/locales/en-US.ts | 4 +- web/src/pages/Upstream/locales/zh-CN.ts | 4 +- web/src/typings.d.ts | 1 - web/yarn.lock | 5 --- 28 files changed, 115 insertions(+), 55 deletions(-) create mode 100644 web/src/components/PanelSection/index.tsx diff --git a/web/package.json b/web/package.json index f138a1bd00..d0c4d13cf2 100644 --- a/web/package.json +++ b/web/package.json @@ -52,7 +52,6 @@ "@ant-design/icons": "^4.0.0", "@ant-design/pro-layout": "^6.0.0", "@ant-design/pro-table": "2.30.1", - "@api7-dashboard/ui": "^1.0.3", "@mrblenny/react-flow-chart": "^0.0.14", "@rjsf/antd": "2.2.0", "@rjsf/core": "2.2.0", diff --git a/web/src/components/PanelSection/index.tsx b/web/src/components/PanelSection/index.tsx new file mode 100644 index 0000000000..0fdfd10a92 --- /dev/null +++ b/web/src/components/PanelSection/index.tsx @@ -0,0 +1,39 @@ +/* + * 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, { CSSProperties } from 'react'; +import { Divider, Tooltip } from 'antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; + +const PanelSection: React.FC<{ + title: string; + style?: CSSProperties; + id?: string; + tooltip?: string; +}> = ({ title, style, id, children, tooltip }) => { + return ( +
+ + {title} +   + {tooltip && } + +
{children}
+
+ ); +}; + +export default PanelSection; diff --git a/web/src/components/Plugin/PluginPage.tsx b/web/src/components/Plugin/PluginPage.tsx index f47f9fd538..5b5016755c 100644 --- a/web/src/components/Plugin/PluginPage.tsx +++ b/web/src/components/Plugin/PluginPage.tsx @@ -16,10 +16,10 @@ */ import React, { useEffect, useState } from 'react'; import { Anchor, Layout, Card, Button, Form, Select, Alert } from 'antd'; -import { PanelSection } from '@api7-dashboard/ui'; import { omit, orderBy } from 'lodash'; import { useIntl } from 'umi'; +import PanelSection from '@/components/PanelSection'; import PluginDetail from './PluginDetail'; import { fetchList, fetchPluginTemplateList } from './service'; import { PLUGIN_ICON_LIST, PLUGIN_FILTER_LIST } from './data'; diff --git a/web/src/components/Plugin/data.tsx b/web/src/components/Plugin/data.tsx index df119c1f24..2c2ea81731 100644 --- a/web/src/components/Plugin/data.tsx +++ b/web/src/components/Plugin/data.tsx @@ -39,7 +39,6 @@ export enum PluginType { traffic = "traffic", serverless = "serverless", observability = "observability", - logging = "logging", other = "other" } @@ -64,7 +63,7 @@ export const PLUGIN_LIST = { type: PluginType.authentication }, "error-log-logger": { - type: PluginType.logging + type: PluginType.observability }, "fault-injection": { type: PluginType.security @@ -79,7 +78,7 @@ export const PLUGIN_LIST = { type: PluginType.other }, "syslog": { - type: PluginType.logging + type: PluginType.observability }, "traffic-split": { type: PluginType.traffic @@ -88,13 +87,13 @@ export const PLUGIN_LIST = { type: PluginType.authentication }, "kafka-logger": { - type: PluginType.logging + type: PluginType.observability }, "limit-conn": { type: PluginType.traffic }, "udp-logger": { - type: PluginType.logging + type: PluginType.observability }, "zipkin": { type: PluginType.observability @@ -104,7 +103,7 @@ export const PLUGIN_LIST = { hidden: true }, "log-rotate": { - type: PluginType.logging, + type: PluginType.observability, hidden: true }, "serverless-pre-function": { @@ -166,16 +165,16 @@ export const PLUGIN_LIST = { type: PluginType.other }, "http-logger": { - type: PluginType.logging + type: PluginType.observability }, "openid-connect": { type: PluginType.authentication }, "sls-logger": { - type: PluginType.logging + type: PluginType.observability }, "tcp-logger": { - type: PluginType.logging + type: PluginType.observability }, "uri-blocker": { type: PluginType.security diff --git a/web/src/components/Plugin/locales/en-US.ts b/web/src/components/Plugin/locales/en-US.ts index 96d2b2b6e0..b1f3ae4650 100644 --- a/web/src/components/Plugin/locales/en-US.ts +++ b/web/src/components/Plugin/locales/en-US.ts @@ -28,7 +28,6 @@ export default { 'component.plugin.traffic': 'Traffic Control', 'component.plugin.serverless': 'Serverless', 'component.plugin.observability': 'Tracing & Metrics & Logging', - 'component.plugin.logging': 'Logging', 'component.plugin.other': 'Other', // cors diff --git a/web/src/components/Plugin/locales/zh-CN.ts b/web/src/components/Plugin/locales/zh-CN.ts index b46a4137d2..b4f68f149a 100644 --- a/web/src/components/Plugin/locales/zh-CN.ts +++ b/web/src/components/Plugin/locales/zh-CN.ts @@ -28,7 +28,6 @@ export default { 'component.plugin.traffic': '流量控制', 'component.plugin.serverless': '无服务器架构', 'component.plugin.observability': '可观测性', - 'component.plugin.logging': '日志记录', 'component.plugin.other': '其它', // cors diff --git a/web/src/components/RightContent/AvatarDropdown.tsx b/web/src/components/RightContent/AvatarDropdown.tsx index 2304f1cacf..9221a05614 100644 --- a/web/src/components/RightContent/AvatarDropdown.tsx +++ b/web/src/components/RightContent/AvatarDropdown.tsx @@ -34,9 +34,6 @@ export type GlobalHeaderRightProps = { menu?: boolean; }; -/** - * 退出登录,并且将当前的 url 保存 - */ const settings = async () => { history.replace({ pathname: '/settings', diff --git a/web/src/components/Upstream/UpstreamForm.tsx b/web/src/components/Upstream/UpstreamForm.tsx index 42d71443e5..2cf01518da 100644 --- a/web/src/components/Upstream/UpstreamForm.tsx +++ b/web/src/components/Upstream/UpstreamForm.tsx @@ -19,7 +19,7 @@ import React, { useState, forwardRef, useImperativeHandle, useEffect } from 'rea import { useIntl } from 'umi'; import type { FormInstance } from 'antd/es/form'; -import { PanelSection } from '@api7-dashboard/ui'; +import PanelSection from '@/components/PanelSection'; import { transformRequest } from '@/pages/Upstream/transform'; import PassiveCheck from './components/passive-check'; import ActiveCheck from './components/active-check' @@ -96,8 +96,6 @@ const UpstreamForm: React.FC = forwardRef( const targetData = list.find((item) => item.id === upstream_id) as UpstreamComponent.ResponseData if (targetData) { form.setFieldsValue(transformUpstreamDataFromRequest(targetData)); - } else { - // TODO: 提示 upstream_id 找不到想要的数据 } }); } diff --git a/web/src/components/Upstream/components/TLS.tsx b/web/src/components/Upstream/components/TLS.tsx index 2d4feee0a5..bdc01030d7 100644 --- a/web/src/components/Upstream/components/TLS.tsx +++ b/web/src/components/Upstream/components/TLS.tsx @@ -69,7 +69,7 @@ const TLSComponent: React.FC = ({ form, readonly }) => { required rules={[{ required: true, message: "" }, { max: 64 * 1024 }, { min: 128 }]} > - + = ({ form, readonly }) => { required rules={[{ required: true, message: "" }, { max: 64 * 1024 }, { min: 128 }]} > - + ) diff --git a/web/src/components/Upstream/components/passive-check/Type.tsx b/web/src/components/Upstream/components/passive-check/Type.tsx index f43f445e40..eeab15030b 100644 --- a/web/src/components/Upstream/components/passive-check/Type.tsx +++ b/web/src/components/Upstream/components/passive-check/Type.tsx @@ -35,7 +35,7 @@ const options = [ } ] -const ActiveCheckTypeComponent: React.FC = ({ readonly }) => { +const PassiveCheckTypeComponent: React.FC = ({ readonly }) => { const { formatMessage } = useIntl() return ( @@ -66,4 +66,4 @@ const ActiveCheckTypeComponent: React.FC = ({ readonly }) => { ) } -export default ActiveCheckTypeComponent +export default PassiveCheckTypeComponent diff --git a/web/src/components/Upstream/locales/en-US.ts b/web/src/components/Upstream/locales/en-US.ts index 585fb6687d..53870871eb 100644 --- a/web/src/components/Upstream/locales/en-US.ts +++ b/web/src/components/Upstream/locales/en-US.ts @@ -16,7 +16,9 @@ */ export default { 'component.upstream.fields.tls.client_key': 'Client Key', + 'component.upstream.fields.tls.client_key.required': 'Please enter the client key', 'component.upstream.fields.tls.client_cert': 'Client Cert', + 'component.upstream.fields.tls.client_cert.required': 'Please enter the client cert', 'component.upstream.fields.discovery_type': 'Discovery Type', 'component.upstream.fields.discovery_type.tooltip': 'Discovery Type', diff --git a/web/src/components/Upstream/locales/zh-CN.ts b/web/src/components/Upstream/locales/zh-CN.ts index 62925e6926..5f93dcd3c3 100644 --- a/web/src/components/Upstream/locales/zh-CN.ts +++ b/web/src/components/Upstream/locales/zh-CN.ts @@ -16,7 +16,9 @@ */ export default { 'component.upstream.fields.tls.client_key': '客户端私钥', + 'component.upstream.fields.tls.client_key.required': '请输入客户端私钥', 'component.upstream.fields.tls.client_cert': '客户端证书', + 'component.upstream.fields.tls.client_cert.required': '请输入客户端证书', 'component.upstream.fields.discovery_type': '服务发现类型', 'component.upstream.fields.discovery_type.tooltip': '服务发现类型', diff --git a/web/src/global.less b/web/src/global.less index 3cf4adec56..c8c8668366 100644 --- a/web/src/global.less +++ b/web/src/global.less @@ -66,7 +66,6 @@ ol { } } -// 兼容IE11 @media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) { body .ant-design-pro > .ant-layout { min-height: 100vh; diff --git a/web/src/helpers.tsx b/web/src/helpers.tsx index d1d7d4ec56..9d290b30dc 100644 --- a/web/src/helpers.tsx +++ b/web/src/helpers.tsx @@ -73,9 +73,6 @@ export const getMenuData = (): MenuDataItem[] => { export const isLoginPage = () => window.location.pathname.indexOf('/user/login') !== -1; -/** - * 异常处理程序 - */ export const errorHandler = (error: { response: Response; data: any }): Promise => { const { response } = error; if (error && response && response.status) { diff --git a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx index 60dd5de068..b0f1761fca 100644 --- a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx +++ b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx @@ -18,11 +18,12 @@ 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'; import queryString from 'query-string'; import Base64 from 'base-64'; import urlRegexSafe from 'url-regex-safe'; +import PanelSection from '@/components/PanelSection'; + import { HTTP_METHOD_OPTION_LIST, DEFAULT_DEBUG_PARAM_FORM_DATA, diff --git a/web/src/pages/Route/components/Step1/MatchingRulesView.tsx b/web/src/pages/Route/components/Step1/MatchingRulesView.tsx index aea9494987..81f4dd2f1b 100644 --- a/web/src/pages/Route/components/Step1/MatchingRulesView.tsx +++ b/web/src/pages/Route/components/Step1/MatchingRulesView.tsx @@ -15,9 +15,10 @@ * limitations under the License. */ import React, { useState } from 'react'; -import { Button, Table, Modal, Form, Select, Input, Space } from 'antd'; +import { Button, Table, Modal, Form, Select, Input, Space, notification } from 'antd'; import { useIntl } from 'umi'; -import { PanelSection } from '@api7-dashboard/ui'; + +import PanelSection from '@/components/PanelSection'; const MatchingRulesView: React.FC = ({ advancedMatchingRules, @@ -36,21 +37,32 @@ const MatchingRulesView: React.FC = ({ const { formatMessage } = useIntl(); const onOk = () => { - modalForm.validateFields().then((value) => { + modalForm.validateFields().then((value: RouteModule.MatchingRule) => { + if (value.operator === "IN") { + try { + JSON.parse(value.value as string) + } catch (error) { + notification.warning({ + message: formatMessage({ id: 'page.route.fields.vars.invalid' }), + description: formatMessage({ id: 'page.route.fields.vars.in.invalid' }) + }) + return + } + } if (mode === 'EDIT') { const key = modalForm.getFieldValue('key'); onChange({ action: 'advancedMatchingRulesChange', data: advancedMatchingRules.map((rule) => { if (rule.key === key) { - return { ...(value as RouteModule.MatchingRule), key }; + return { ...value, key }; } return rule; }), }); } else { const rule = { - ...(value as RouteModule.MatchingRule), + ...value, key: Math.random().toString(36).slice(2), }; onChange({ @@ -280,7 +292,7 @@ const MatchingRulesView: React.FC = ({ }; return ( - + {!disabled && ( } > @@ -95,7 +86,7 @@ const Page: React.FC = (props) => { - {step === 1 && } + {step === 1 && } {step === 2 && } diff --git a/web/src/pages/Upstream/components/Step1.tsx b/web/src/pages/Upstream/components/Step1.tsx index 40f3c31e69..226e71c278 100644 --- a/web/src/pages/Upstream/components/Step1.tsx +++ b/web/src/pages/Upstream/components/Step1.tsx @@ -14,27 +14,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Form, Input } from 'antd'; import type { FormInstance } from 'antd/lib/form'; import { useIntl } from 'umi'; import UpstreamForm from '@/components/Upstream'; -import { fetchList } from '../service'; type Props = { form: FormInstance; disabled?: boolean; upstreamRef?: React.MutableRefObject; + neverReadonly?: boolean; }; -const Step1: React.FC = ({ form, disabled, upstreamRef }) => { +const Step1: React.FC = ({ form, disabled, upstreamRef, neverReadonly }) => { const { formatMessage } = useIntl(); - const [list, setList] = useState([]); - - useEffect(() => { - fetchList({}).then(({ data }) => setList(data)); - }, []); return ( <> @@ -56,7 +51,7 @@ const Step1: React.FC = ({ form, disabled, upstreamRef }) => { /> - + ); }; diff --git a/web/src/pages/Upstream/locales/zh-CN.ts b/web/src/pages/Upstream/locales/zh-CN.ts index 73611a22fb..eb0c19acc9 100644 --- a/web/src/pages/Upstream/locales/zh-CN.ts +++ b/web/src/pages/Upstream/locales/zh-CN.ts @@ -34,7 +34,7 @@ export default { 'page.upstream.step.type': '负载均衡算法', 'page.upstream.step.pass-host': 'Host 请求头', 'page.upstream.step.pass-host.pass': '保持与客户端请求一致的主机名', - 'page.upstream.step.pass-host.node': '使用上游节点列表中的主机名或 IP', + 'page.upstream.step.pass-host.node': '使用目标节点列表中的主机名或 IP', 'page.upstream.step.pass-host.rewrite': '自定义 Host 请求头(即将废弃)', 'page.upstream.step.pass-host.upstream_host': '自定义主机名', 'page.upstream.step.connect.timeout': '连接超时', diff --git a/web/src/pages/Upstream/service.ts b/web/src/pages/Upstream/service.ts index 3712f34d80..9dee86260e 100644 --- a/web/src/pages/Upstream/service.ts +++ b/web/src/pages/Upstream/service.ts @@ -16,6 +16,8 @@ */ import { request } from 'umi'; +import { convertToFormData } from '@/components/Upstream/service'; + export const fetchList = ({ current = 1, pageSize = 10, ...res }) => { return request>>('/upstreams', { params: { @@ -29,7 +31,7 @@ export const fetchList = ({ current = 1, pageSize = 10, ...res }) => { })); }; -export const fetchOne = (id: string) => request>(`/upstreams/${id}`); +export const fetchOne = (id: string) => request>(`/upstreams/${id}`).then(({data}) => convertToFormData(data)); export const create = (data: UpstreamModule.RequestBody) => request('/upstreams', { diff --git a/web/src/pages/Upstream/transform.ts b/web/src/pages/Upstream/transform.ts deleted file mode 100644 index 0b520f230d..0000000000 --- a/web/src/pages/Upstream/transform.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { pickBy, identity, omit, pick } from 'lodash'; - -/* - * 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. - */ -export const transformRequest = ( - formData: UpstreamModule.RequestBody, -): UpstreamModule.RequestBody | undefined | { upstream_id: string } => { - let data = pickBy(formData, identity) as UpstreamModule.RequestBody; - - data = omit(data, 'custom') - - const { - type, - hash_on, - key, - k8s_deployment_info, - nodes, - pass_host, - upstream_host, - upstream_id, - } = data; - - data.checks = pickBy(data.checks || {}, identity); - if (data.checks.active) { - data.checks.active = pickBy( - data.checks.active, - identity, - ) as UpstreamModule.HealthCheck['active']; - } - - if (upstream_id) { - return { upstream_id }; - } - - if (Object.keys(data.checks).length === 0) { - data = omit(data, 'checks'); - } - if (nodes && k8s_deployment_info) { - return undefined; - } - - if (!nodes && !k8s_deployment_info) { - return undefined; - } - - if (type === 'chash') { - if (!hash_on) { - return undefined; - } - - if (hash_on !== 'consumer' && !key) { - return undefined; - } - } - - if (pass_host === 'rewrite' && !upstream_host) { - return undefined; - } - - if (nodes) { - // NOTE: https://github.com/ant-design/ant-design/issues/27396 - data.nodes = data.nodes?.map((item) => { - return pick(item, ['host', 'port', 'weight']); - }); - return data; - } - - return undefined; -}; From 5d9cd3fca2ecca51ff62b1e3623ae3159673b2bc Mon Sep 17 00:00:00 2001 From: nic-chen <33000667+nic-chen@users.noreply.github.com> Date: Mon, 19 Apr 2021 11:21:25 +0800 Subject: [PATCH 7/7] change: remove ID of consumer (#1745) --- .github/workflows/backend-e2e-test.yml | 1 + api/conf/schema.json | 242 +++++++++--------- api/internal/core/entity/entity.go | 11 +- api/internal/core/store/store_mock.go | 10 + api/internal/core/store/validate_test.go | 43 +--- api/internal/handler/consumer/consumer.go | 31 ++- .../handler/consumer/consumer_test.go | 46 ++-- api/internal/handler/label/label_test.go | 4 - api/test/docker/apisix_config.yaml | 2 +- api/test/docker/apisix_config2.yaml | 2 +- api/test/docker/docker-compose.yaml | 4 +- api/test/e2enew/schema/schema_test.go | 2 +- web/cypress/fixtures/plugin-dataset.json | 4 +- .../create-upstream-with-cors-form.spec.js | 10 +- web/src/components/Plugin/PluginDetail.tsx | 4 +- 15 files changed, 213 insertions(+), 203 deletions(-) diff --git a/.github/workflows/backend-e2e-test.yml b/.github/workflows/backend-e2e-test.yml index 6f226add87..2914e9b9a2 100644 --- a/.github/workflows/backend-e2e-test.yml +++ b/.github/workflows/backend-e2e-test.yml @@ -46,6 +46,7 @@ jobs: docker-compose up -d sleep 5 docker logs docker_managerapi_1 + docker logs docker_apisix_1 - name: run test working-directory: ./api/test/e2e diff --git a/api/conf/schema.json b/api/conf/schema.json index e69c57aa6e..ff58ff4b78 100644 --- a/api/conf/schema.json +++ b/api/conf/schema.json @@ -10,17 +10,6 @@ "maxLength": 256, "type": "string" }, - "id": { - "anyOf": [{ - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, { - "minimum": 1, - "type": "integer" - }] - }, "labels": { "description": "key/value pairs to specify attributes", "maxProperties": 16, @@ -147,6 +136,27 @@ "maxLength": 1048576, "minLength": 1, "type": "string" + }, + "create_time": { + "type": "integer" + }, + "desc": { + "maxLength": 256, + "type": "string" + }, + "id": { + "anyOf": [{ + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-_.]+$", + "type": "string" + }, { + "minimum": 1, + "type": "integer" + }] + }, + "update_time": { + "type": "integer" } }, "required": ["content"], @@ -595,10 +605,6 @@ "description": "discovery type", "type": "string" }, - "enable_websocket": { - "description": "enable websocket for request", - "type": "boolean" - }, "hash_on": { "default": "vars", "enum": ["consumer", "cookie", "header", "vars", "vars_combinations"], @@ -664,6 +670,11 @@ "minimum": 1, "type": "integer" }, + "priority": { + "default": 0, + "description": "priority of node", + "type": "integer" + }, "weight": { "description": "weight of node", "minimum": 0, @@ -754,12 +765,6 @@ "uniqueItems": true }, "vars": { - "items": { - "description": "Nginx builtin variable name and value", - "maxItems": 4, - "minItems": 2, - "type": "array" - }, "type": "array" } }, @@ -1025,10 +1030,6 @@ "description": "discovery type", "type": "string" }, - "enable_websocket": { - "description": "enable websocket for request", - "type": "boolean" - }, "hash_on": { "default": "vars", "enum": ["consumer", "cookie", "header", "vars", "vars_combinations"], @@ -1094,6 +1095,11 @@ "minimum": 1, "type": "integer" }, + "priority": { + "default": 0, + "description": "priority of node", + "type": "integer" + }, "weight": { "description": "weight of node", "minimum": 0, @@ -1270,6 +1276,13 @@ }, "stream_route": { "properties": { + "create_time": { + "type": "integer" + }, + "desc": { + "maxLength": 256, + "type": "string" + }, "id": { "anyOf": [{ "maxLength": 64, @@ -1330,6 +1343,9 @@ "description": "server port", "type": "integer" }, + "update_time": { + "type": "integer" + }, "upstream": { "additionalProperties": false, "oneOf": [{ @@ -1535,10 +1551,6 @@ "description": "discovery type", "type": "string" }, - "enable_websocket": { - "description": "enable websocket for request", - "type": "boolean" - }, "hash_on": { "default": "vars", "enum": ["consumer", "cookie", "header", "vars", "vars_combinations"], @@ -1604,6 +1616,11 @@ "minimum": 1, "type": "integer" }, + "priority": { + "default": 0, + "description": "priority of node", + "type": "integer" + }, "weight": { "description": "weight of node", "minimum": 0, @@ -1887,10 +1904,6 @@ "description": "discovery type", "type": "string" }, - "enable_websocket": { - "description": "enable websocket for request", - "type": "boolean" - }, "hash_on": { "default": "vars", "enum": ["consumer", "cookie", "header", "vars", "vars_combinations"], @@ -1956,6 +1969,11 @@ "minimum": 1, "type": "integer" }, + "priority": { + "default": 0, + "description": "priority of node", + "type": "integer" + }, "weight": { "description": "weight of node", "minimum": 0, @@ -2297,54 +2315,59 @@ "priority": 2400, "schema": { "$comment": "this is a mark for our injected plugin schema", - "oneOf": [{ - "properties": { - "blacklist": { - "items": { - "type": "string" + "anyOf": [{ + "required": ["blacklist"] + }, { + "required": ["whitelist"] + }, { + "required": ["allowed_by_methods"] + }], + "properties": { + "allowed_by_methods": { + "items": { + "properties": { + "methods": { + "items": { + "enum": ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"], + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "user": { + "type": "string" + } }, - "minItems": 1, - "type": "array" - }, - "rejected_code": { - "default": 403, - "minimum": 200, - "type": "integer" + "type": "object" }, - "type": { - "default": "consumer_name", - "enum": ["consumer_name", "service_id"], - "type": "string" - } + "type": "array" }, - "required": ["blacklist"], - "title": "blacklist" - }, { - "properties": { - "rejected_code": { - "default": 403, - "minimum": 200, - "type": "integer" - }, - "type": { - "default": "consumer_name", - "enum": ["consumer_name", "service_id"], + "blacklist": { + "items": { "type": "string" }, - "whitelist": { - "items": { - "type": "string" - }, - "minItems": 1, - "type": "array" - } + "minItems": 1, + "type": "array" }, - "required": ["whitelist"], - "title": "whitelist" - }], - "properties": { "disable": { "type": "boolean" + }, + "rejected_code": { + "default": 403, + "minimum": 200, + "type": "integer" + }, + "type": { + "default": "consumer_name", + "enum": ["consumer_name", "service_id"], + "type": "string" + }, + "whitelist": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" } }, "type": "object" @@ -2376,6 +2399,17 @@ "description": "you can use '*' to allow all origins when no credentials,'**' to allow forcefully(it will bring some security risks, be carefully),multiple origin use ',' to split. default: *.", "type": "string" }, + "allow_origins_by_regex": { + "description": "you can use regex to allow specific origins when no credentials,for example use [.*\\.test.com] to allow a.test.com and b.test.com", + "items": { + "maxLength": 4096, + "minLength": 1, + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + }, "disable": { "type": "boolean" }, @@ -2608,6 +2642,9 @@ "type": "integer" }, "vars": { + "items": { + "type": "array" + }, "maxItems": 20, "type": "array" } @@ -2627,6 +2664,9 @@ "type": "integer" }, "vars": { + "items": { + "type": "array" + }, "maxItems": 20, "type": "array" } @@ -3682,6 +3722,9 @@ "maximum": 598, "minimum": 200, "type": "integer" + }, + "vars": { + "type": "array" } }, "type": "object" @@ -3762,6 +3805,9 @@ "default": "http://127.0.0.1:12800", "type": "string" }, + "report_interval": { + "type": "integer" + }, "service_instance_name": { "default": "APISIX Instance Name", "description": "User Service Instance Name", @@ -4028,43 +4074,6 @@ "items": { "properties": { "vars": { - "items": { - "additionalItems": { - "anyOf": [{ - "type": "string" - }, { - "type": "number" - }, { - "type": "boolean" - }, { - "items": { - "anyOf": [{ - "maxLength": 100, - "minLength": 1, - "type": "string" - }, { - "type": "number" - }, { - "type": "boolean" - }] - }, - "type": "array", - "uniqueItems": true - }] - }, - "items": [{ - "maxLength": 100, - "minLength": 1, - "type": "string" - }, { - "maxLength": 2, - "minLength": 1, - "type": "string" - }], - "maxItems": 10, - "minItems": 0, - "type": "array" - }, "type": "array" } }, @@ -4283,10 +4292,6 @@ "description": "discovery type", "type": "string" }, - "enable_websocket": { - "description": "enable websocket for request", - "type": "boolean" - }, "hash_on": { "default": "vars", "enum": ["consumer", "cookie", "header", "vars", "vars_combinations"], @@ -4352,6 +4357,11 @@ "minimum": 1, "type": "integer" }, + "priority": { + "default": 0, + "description": "priority of node", + "type": "integer" + }, "weight": { "description": "weight of node", "minimum": 0, @@ -4578,6 +4588,10 @@ "default": "APISIX", "description": "service name for zipkin reporter", "type": "string" + }, + "span_version": { + "default": 2, + "enum": [1, 2] } }, "required": ["endpoint", "sample_ratio"], diff --git a/api/internal/core/entity/entity.go b/api/internal/core/entity/entity.go index c42d31f9db..b81c7f4a6d 100644 --- a/api/internal/core/entity/entity.go +++ b/api/internal/core/entity/entity.go @@ -197,11 +197,12 @@ func (upstream *Upstream) Parse2NameResponse() (*UpstreamNameResponse, error) { // swagger:model Consumer type Consumer struct { - BaseInfo - Username string `json:"username"` - Desc string `json:"desc,omitempty"` - Plugins map[string]interface{} `json:"plugins,omitempty"` - Labels map[string]string `json:"labels,omitempty"` + Username string `json:"username"` + Desc string `json:"desc,omitempty"` + Plugins map[string]interface{} `json:"plugins,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + CreateTime int64 `json:"create_time,omitempty"` + UpdateTime int64 `json:"update_time,omitempty"` } // swagger:model SSL diff --git a/api/internal/core/store/store_mock.go b/api/internal/core/store/store_mock.go index 436ef6ba6c..c6801d69eb 100644 --- a/api/internal/core/store/store_mock.go +++ b/api/internal/core/store/store_mock.go @@ -18,6 +18,7 @@ package store import ( "context" + "sort" "github.com/stretchr/testify/mock" ) @@ -44,6 +45,15 @@ func (m *MockInterface) List(_ context.Context, input ListInput) (*ListOutput, e } else { r0 = ret.Get(0).(*ListOutput) } + + if input.Less == nil { + input.Less = defLessFunc + } + + sort.Slice(r0.Rows, func(i, j int) bool { + return input.Less(r0.Rows[i], r0.Rows[j]) + }) + r1 = ret.Error(1) return r0, r1 diff --git a/api/internal/core/store/validate_test.go b/api/internal/core/store/validate_test.go index 795fab3ec5..7b0b6f37fc 100644 --- a/api/internal/core/store/validate_test.go +++ b/api/internal/core/store/validate_test.go @@ -76,7 +76,6 @@ func TestAPISIXJsonSchemaValidator_Validate(t *testing.T) { consumer := &entity.Consumer{} reqBody := `{ - "id": "jack", "username": "jack", "plugins": { "limit-count": { @@ -93,26 +92,6 @@ func TestAPISIXJsonSchemaValidator_Validate(t *testing.T) { err = validator.Validate(consumer) assert.Nil(t, err) - consumer2 := &entity.Consumer{} - reqBody = `{ - "username": "jack", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "desc": "test description" - }` - err = json.Unmarshal([]byte(reqBody), consumer2) - assert.Nil(t, err) - - err = validator.Validate(consumer2) - assert.NotNil(t, err) - assert.EqualError(t, err, "schema validate failed: id: Must validate at least one schema (anyOf)\nid: Invalid type. Expected: string, given: null") - //check nil obj err = validator.Validate(nil) assert.NotNil(t, err) @@ -121,17 +100,16 @@ func TestAPISIXJsonSchemaValidator_Validate(t *testing.T) { //plugin schema fail consumer3 := &entity.Consumer{} reqBody = `{ - "id": "jack", - "username": "jack", - "plugins": { - "limit-count": { - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "desc": "test description" - }` + "username": "jack", + "plugins": { + "limit-count": { + "time_window": 60, + "rejected_code": 503, + "key": "remote_addr" + } + }, + "desc": "test description" + }` err = json.Unmarshal([]byte(reqBody), consumer3) assert.Nil(t, err) err = validator.Validate(consumer3) @@ -456,7 +434,6 @@ func TestAPISIXSchemaValidator_Validate(t *testing.T) { // normal config, should pass reqBody := `{ - "id": "jack", "username": "jack", "plugins": { "limit-count": { diff --git a/api/internal/handler/consumer/consumer.go b/api/internal/handler/consumer/consumer.go index 7efb238f97..0b169bbcbc 100644 --- a/api/internal/handler/consumer/consumer.go +++ b/api/internal/handler/consumer/consumer.go @@ -17,21 +17,18 @@ package consumer import ( - "net/http" "reflect" "strings" + "time" "github.com/gin-gonic/gin" "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" "github.com/shiningrush/droplet/wrapper" wgin "github.com/shiningrush/droplet/wrapper/gin" "github.com/apisix/manager-api/internal/core/entity" "github.com/apisix/manager-api/internal/core/store" "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils" - "github.com/apisix/manager-api/internal/utils/consts" ) type Handler struct { @@ -120,6 +117,17 @@ func (h *Handler) List(c droplet.Context) (interface{}, error) { } return true }, + Less: func(i, j interface{}) bool { + iBase := i.(*entity.Consumer) + jBase := j.(*entity.Consumer) + if iBase.CreateTime != jBase.CreateTime { + return iBase.CreateTime < jBase.CreateTime + } + if iBase.UpdateTime != jBase.UpdateTime { + return iBase.UpdateTime < jBase.UpdateTime + } + return iBase.Username < jBase.Username + }, PageSize: input.PageSize, PageNumber: input.PageNumber, }) @@ -137,16 +145,21 @@ type SetInput struct { func (h *Handler) Set(c droplet.Context) (interface{}, error) { input := c.Input().(*SetInput) - if input.ID != nil && utils.InterfaceToString(input.ID) != input.Username { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - consts.ErrIDUsername - } if input.Username != "" { input.Consumer.Username = input.Username } - input.Consumer.ID = input.Consumer.Username ensurePluginsDefValue(input.Plugins) + // Because the ID of consumer has been removed, + // `BaseInfo` is no longer embedded in consumer's struct, + // So we need to maintain create_time and update_time separately for consumer + savedConsumer, _ := h.consumerStore.Get(c.Context(), input.Consumer.Username) + input.Consumer.CreateTime = time.Now().Unix() + input.Consumer.UpdateTime = time.Now().Unix() + if savedConsumer != nil { + input.Consumer.CreateTime = savedConsumer.(*entity.Consumer).CreateTime + } + ret, err := h.consumerStore.Update(c.Context(), &input.Consumer, true) if err != nil { return handler.SpecCodeResponse(err), err diff --git a/api/internal/handler/consumer/consumer_test.go b/api/internal/handler/consumer/consumer_test.go index 0bc5eab36f..9a76bc98b3 100644 --- a/api/internal/handler/consumer/consumer_test.go +++ b/api/internal/handler/consumer/consumer_test.go @@ -112,8 +112,8 @@ func TestHandler_List(t *testing.T) { }, wantRet: &store.ListOutput{ Rows: []interface{}{ - &entity.Consumer{Username: "testUser"}, &entity.Consumer{Username: "iam-testUser"}, + &entity.Consumer{Username: "testUser"}, &entity.Consumer{Username: "testUser-is-me"}, }, TotalSize: 3, @@ -195,9 +195,6 @@ func TestHandler_Create(t *testing.T) { }, giveCtx: context.WithValue(context.Background(), "test", "value"), giveRet: &entity.Consumer{ - BaseInfo: entity.BaseInfo{ - ID: "name", - }, Username: "name", Plugins: map[string]interface{}{ "jwt-auth": map[string]interface{}{ @@ -207,9 +204,6 @@ func TestHandler_Create(t *testing.T) { }, wantInput: &SetInput{ Consumer: entity.Consumer{ - BaseInfo: entity.BaseInfo{ - ID: "name", - }, Username: "name", Plugins: map[string]interface{}{ "jwt-auth": map[string]interface{}{ @@ -219,9 +213,6 @@ func TestHandler_Create(t *testing.T) { }, }, wantRet: &entity.Consumer{ - BaseInfo: entity.BaseInfo{ - ID: "name", - }, Username: "name", Plugins: map[string]interface{}{ "jwt-auth": map[string]interface{}{ @@ -249,9 +240,6 @@ func TestHandler_Create(t *testing.T) { giveErr: fmt.Errorf("create failed"), wantInput: &SetInput{ Consumer: entity.Consumer{ - BaseInfo: entity.BaseInfo{ - ID: "name", - }, Username: "name", Plugins: map[string]interface{}{ "jwt-auth": map[string]interface{}{ @@ -275,10 +263,12 @@ func TestHandler_Create(t *testing.T) { mStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { methodCalled = true assert.Equal(t, tc.giveCtx, args.Get(0)) - assert.Equal(t, &tc.wantInput.Consumer, args.Get(1)) assert.True(t, args.Bool(2)) }).Return(tc.giveRet, tc.giveErr) + mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { + }).Return(nil, nil) + h := Handler{consumerStore: mStore} ctx := droplet.NewContext() ctx.SetInput(tc.giveInput) @@ -302,6 +292,7 @@ func TestHandler_Update(t *testing.T) { wantInput *entity.Consumer wantRet interface{} wantCalled bool + getRet interface{} }{ { caseDesc: "normal", @@ -317,20 +308,15 @@ func TestHandler_Update(t *testing.T) { }, giveCtx: context.WithValue(context.Background(), "test", "value"), giveRet: &entity.Consumer{ - BaseInfo: entity.BaseInfo{ - ID: "name", - }, Username: "name", Plugins: map[string]interface{}{ "jwt-auth": map[string]interface{}{ "exp": 500, }, }, + CreateTime: 1618648423, }, wantInput: &entity.Consumer{ - BaseInfo: entity.BaseInfo{ - ID: "name", - }, Username: "name", Plugins: map[string]interface{}{ "jwt-auth": map[string]interface{}{ @@ -339,17 +325,20 @@ func TestHandler_Update(t *testing.T) { }, }, wantRet: &entity.Consumer{ - BaseInfo: entity.BaseInfo{ - ID: "name", - }, Username: "name", Plugins: map[string]interface{}{ "jwt-auth": map[string]interface{}{ "exp": 500, }, }, + CreateTime: 1618648423, }, wantCalled: true, + getRet: &entity.Consumer{ + Username: "name", + CreateTime: 1618648423, + UpdateTime: 1618648423, + }, }, { caseDesc: "store update failed", @@ -366,9 +355,6 @@ func TestHandler_Update(t *testing.T) { }, giveErr: fmt.Errorf("create failed"), wantInput: &entity.Consumer{ - BaseInfo: entity.BaseInfo{ - ID: "name", - }, Username: "name", Plugins: map[string]interface{}{ "jwt-auth": map[string]interface{}{ @@ -391,10 +377,12 @@ func TestHandler_Update(t *testing.T) { mStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { methodCalled = true assert.Equal(t, tc.giveCtx, args.Get(0)) - assert.Equal(t, tc.wantInput, args.Get(1)) assert.True(t, args.Bool(2)) }).Return(tc.giveRet, tc.giveErr) + mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { + }).Return(tc.getRet, nil) + h := Handler{consumerStore: mStore} ctx := droplet.NewContext() ctx.SetInput(tc.giveInput) @@ -403,6 +391,10 @@ func TestHandler_Update(t *testing.T) { assert.Equal(t, tc.wantCalled, methodCalled) assert.Equal(t, tc.wantRet, ret) assert.Equal(t, tc.wantErr, err) + if err == nil { + assert.Equal(t, tc.getRet.(*entity.Consumer).CreateTime, ret.(*entity.Consumer).CreateTime) + assert.NotEqual(t, tc.getRet.(*entity.Consumer).UpdateTime, ret.(*entity.Consumer).UpdateTime) + } }) } } diff --git a/api/internal/handler/label/label_test.go b/api/internal/handler/label/label_test.go index 664288d190..d227b95211 100644 --- a/api/internal/handler/label/label_test.go +++ b/api/internal/handler/label/label_test.go @@ -167,10 +167,6 @@ func genUpstream(labels map[string]string) *entity.Upstream { func genConsumer(labels map[string]string) *entity.Consumer { r := entity.Consumer{ - BaseInfo: entity.BaseInfo{ - ID: rand.Int(), - CreateTime: rand.Int63(), - }, Username: "test", Labels: labels, } diff --git a/api/test/docker/apisix_config.yaml b/api/test/docker/apisix_config.yaml index 00fc8782ad..e56a74d16a 100644 --- a/api/test/docker/apisix_config.yaml +++ b/api/test/docker/apisix_config.yaml @@ -22,7 +22,7 @@ etcd: - "http://172.16.238.10:2379" - "http://172.16.238.11:2379" - "http://172.16.238.12:2379" - resync_delay: 0.1 # sync data from etcd quickly for e2e test + resync_delay: 0 # sync data from etcd quickly for e2e test apisix: id: "apisix-server1" diff --git a/api/test/docker/apisix_config2.yaml b/api/test/docker/apisix_config2.yaml index bd788988c9..5d23725d52 100644 --- a/api/test/docker/apisix_config2.yaml +++ b/api/test/docker/apisix_config2.yaml @@ -22,7 +22,7 @@ etcd: - "http://172.16.238.10:2379" - "http://172.16.238.11:2379" - "http://172.16.238.12:2379" - resync_delay: 0.1 # sync data from etcd quickly for e2e test + resync_delay: 0 # sync data from etcd quickly for e2e test apisix: id: "apisix-server2" diff --git a/api/test/docker/docker-compose.yaml b/api/test/docker/docker-compose.yaml index 16d6e62d99..2da09908a2 100644 --- a/api/test/docker/docker-compose.yaml +++ b/api/test/docker/docker-compose.yaml @@ -127,7 +127,7 @@ services: apisix: hostname: apisix_server1 - image: apache/apisix:2.4-alpine + image: apache/apisix:2.5-alpine restart: always volumes: - ./apisix_config.yaml:/usr/local/apisix/conf/config.yaml:ro @@ -148,7 +148,7 @@ services: apisix2: hostname: apisix_server2 - image: apache/apisix:2.4-alpine + image: apache/apisix:2.5-alpine restart: always volumes: - ./apisix_config2.yaml:/usr/local/apisix/conf/config.yaml:ro diff --git a/api/test/e2enew/schema/schema_test.go b/api/test/e2enew/schema/schema_test.go index 4f14d003b3..e856cc278b 100644 --- a/api/test/e2enew/schema/schema_test.go +++ b/api/test/e2enew/schema/schema_test.go @@ -70,7 +70,7 @@ var _ = ginkgo.Describe("Schema Test", func() { Path: "/apisix/admin/schemas/consumer", Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusOK, - ExpectBody: `"properties":{"create_time":{"type":"integer"},"desc":{"maxLength":256,"type":"string"},"id":{"anyOf":[{"maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9-_.]+$","type":"string"},{"minimum":1,"type":"integer"}]},"labels":{"description":"key/value pairs to specify attributes","maxProperties":16,"patternProperties":{".*":{"description":"value of label","maxLength":64,"minLength":1,"pattern":"^\\S+$","type":"string"}},"type":"object"},"plugins":{"type":"object"},"update_time":{"type":"integer"},"username":{"maxLength":32,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","type":"string"}}`, + ExpectBody: `"properties":{"create_time":{"type":"integer"},"desc":{"maxLength":256,"type":"string"},"labels":{"description":"key/value pairs to specify attributes","maxProperties":16,"patternProperties":{".*":{"description":"value of label","maxLength":64,"minLength":1,"pattern":"^\\S+$","type":"string"}},"type":"object"},"plugins":{"type":"object"},"update_time":{"type":"integer"},"username":{"maxLength":32,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","type":"string"}}`, Sleep: base.SleepTime, }), table.Entry("get schema of non-existent resources", base.HttpTestCase{ diff --git a/web/cypress/fixtures/plugin-dataset.json b/web/cypress/fixtures/plugin-dataset.json index b097ea8582..5f3739ad67 100644 --- a/web/cypress/fixtures/plugin-dataset.json +++ b/web/cypress/fixtures/plugin-dataset.json @@ -280,7 +280,7 @@ } }, { - "shouldValid": false, + "shouldValid": true, "data": { "whitelist": ["jack1"], "blacklist": ["jack2"] @@ -1265,7 +1265,7 @@ } }, { - "shouldValid": false, + "shouldValid": true, "data": { "rules": [ { diff --git a/web/cypress/integration/consumer/create-upstream-with-cors-form.spec.js b/web/cypress/integration/consumer/create-upstream-with-cors-form.spec.js index 3bf211114e..9afa31613c 100644 --- a/web/cypress/integration/consumer/create-upstream-with-cors-form.spec.js +++ b/web/cypress/integration/consumer/create-upstream-with-cors-form.spec.js @@ -25,7 +25,8 @@ context('Create and Delete Consumer', () => { }); const selector = { - max_age: "#max_age" + max_age: "#max_age", + allow_origins_by_regex: "#allow_origins_by_regex_0" } const data = { @@ -44,7 +45,9 @@ context('Create and Delete Consumer', () => { // config auth plugin cy.contains(this.domSelector.pluginCard, 'key-auth').within(() => { - cy.contains('Enable').click({ force: true }); + cy.contains('Enable').click({ + force: true + }); }); cy.focused(this.domSelector.drawer).should('exist'); cy.get(this.domSelector.disabledSwitcher).click().should('have.class', 'ant-switch-checked'); @@ -69,7 +72,7 @@ context('Create and Delete Consumer', () => { cy.get(this.domSelector.drawer).should('be.visible'); cy.get(selector.max_age).clear(); - // config proxy-mirror form + // config cors form cy.get(this.domSelector.drawer).within(() => { cy.contains('Submit').click({ force: true, @@ -79,6 +82,7 @@ context('Create and Delete Consumer', () => { cy.get(this.domSelector.notificationCloseIcon).click().should('not.exist'); cy.get(selector.max_age).type(data.time); + cy.get(selector.allow_origins_by_regex).type('.*.test.com'); cy.get(this.domSelector.drawer).within(() => { cy.contains('Submit').click({ force: true, diff --git a/web/src/components/Plugin/PluginDetail.tsx b/web/src/components/Plugin/PluginDetail.tsx index 385f9670eb..72c474b5e4 100644 --- a/web/src/components/Plugin/PluginDetail.tsx +++ b/web/src/components/Plugin/PluginDetail.tsx @@ -285,6 +285,7 @@ const PluginDetail: React.FC = ({ title={formatMessage({ id: 'page.plugin.drawer.popconfirm.title.delete' })} okText={formatMessage({ id: 'component.global.confirm' })} cancelText={formatMessage({ id: 'component.global.cancel' })} + disabled={readonly} onConfirm={() => { onChange({ formData: form.getFieldsValue(), @@ -294,13 +295,14 @@ const PluginDetail: React.FC = ({ }} > {initialData[name] ? ( - ) : null}