You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2022/08/05 08:44:31 UTC

[GitHub] [apisix-dashboard] guoqqqi commented on a diff in pull request #2568: feat: support regex_uri attribute in redirect plugin (#2434)

guoqqqi commented on code in PR #2568:
URL: https://github.com/apache/apisix-dashboard/pull/2568#discussion_r938554022


##########
web/cypress/integration/route/create-edit-route-with-redirect-plugin.spec.js:
##########
@@ -51,7 +51,6 @@ context('Create Edit and Delete Route with redirect plugin', () => {
   it('should create route with custom redirect plugin', function () {
     cy.visit('/');
     cy.contains('Route').click();
-    cy.get(selector.empty).should('be.visible');

Review Comment:
   why remove this line, this is to ensure that there is no other dirty data when the route is created



##########
web/cypress/integration/route/create-edit-route-with-redirect-plugin.spec.js:
##########
@@ -17,8 +17,8 @@
 /* eslint-disable no-undef */
 
 context('Create Edit and Delete Route with redirect plugin', () => {
-  const name = `routeName${new Date().valueOf()}`;
-  const newName = `newName${new Date().valueOf()}`;
+  const name = `${new Date().getFullYear()}-${new Date().getMonth() + 1}-${new Date().getDay()}`;
+  const newName = `${new Date().getFullYear()}*${new Date().getMonth() + 1}*${new Date().getDay()}`;

Review Comment:
   In fact, I think it's enough that the two names are different and it doesn't require such a complicated calculation to get a name variable



##########
web/cypress/integration/route/create-edit-route-with-redirect-plugin.spec.js:
##########
@@ -99,7 +98,6 @@ context('Create Edit and Delete Route with redirect plugin', () => {
     cy.contains(data.submitSuccess);
     cy.contains('Goto List').click();
     cy.url().should('contains', 'routes/list');
-    cy.contains(newName).should('be.visible');

Review Comment:
   ditto, We need some assertions to be sure that the data return is normal



##########
web/src/pages/Route/components/Step1/MetaView.tsx:
##########
@@ -258,67 +269,195 @@ const MetaView: React.FC<RouteModule.Step1PassProps> = ({
     );
   };
 
-  const CustomRedirect: React.FC = () => (
-    <Form.Item
-      noStyle
-      shouldUpdate={(prev, next) => {
-        if (prev.redirectOption !== next.redirectOption) {
-          onChange({ action: 'redirectOptionChange', data: next.redirectOption });
-        }
-        return prev.redirectOption !== next.redirectOption;
-      }}
-    >
-      {() => {
-        if (form.getFieldValue('redirectOption') === 'customRedirect') {
-          return (
+  const CustomRedirectType = () => {
+    switch (form.getFieldValue('customRedirectType')) {
+      case CUSTOM_REDIRECT_TYPE.STATIC:
+        return (
+          <Form.Item
+            label={formatMessage({ id: 'page.route.form.itemLabel.redirectCustom' })}
+            required
+            style={{ marginBottom: 0 }}
+          >
+            <Row gutter={10}>
+              <Col span={5}>
+                <Form.Item
+                  name="redirectURI"
+                  rules={[
+                    {
+                      required: true,
+                      message: `${formatMessage({
+                        id: 'component.global.pleaseEnter',
+                      })}${formatMessage({
+                        id: 'page.route.form.itemLabel.redirectURI',
+                      })}`,
+                    },
+                  ]}
+                >
+                  <Input
+                    placeholder={formatMessage({
+                      id: 'page.route.input.placeholder.redirectCustom',
+                    })}
+                    disabled={disabled}
+                  />
+                </Form.Item>
+              </Col>
+              <Col span={5}>
+                <Form.Item name="ret_code" rules={[{ required: true }]}>
+                  <Select disabled={disabled} data-cy="redirect_code">
+                    <Select.Option value={301}>
+                      {formatMessage({ id: 'page.route.select.option.redirect301' })}
+                    </Select.Option>
+                    <Select.Option value={302}>
+                      {formatMessage({ id: 'page.route.select.option.redirect302' })}
+                    </Select.Option>
+                  </Select>
+                </Form.Item>
+              </Col>
+            </Row>
+          </Form.Item>
+        );
+      case CUSTOM_REDIRECT_TYPE.REGEXP:
+        return (
+          <React.Fragment>
+            <Form.List name="regex_uri" initialValue={['', '']}>
+              {(fields) =>
+                fields.map((field, index) => {
+                  switch (index) {
+                    case 0:

Review Comment:
   ```suggestion
                       case 0:
   ```
   `0` and `1` Not a good choice, it makes the code somewhat unreadable



##########
web/src/pages/Route/components/Step1/MetaView.tsx:
##########
@@ -258,67 +269,195 @@ const MetaView: React.FC<RouteModule.Step1PassProps> = ({
     );
   };
 
-  const CustomRedirect: React.FC = () => (
-    <Form.Item
-      noStyle
-      shouldUpdate={(prev, next) => {
-        if (prev.redirectOption !== next.redirectOption) {
-          onChange({ action: 'redirectOptionChange', data: next.redirectOption });
-        }
-        return prev.redirectOption !== next.redirectOption;
-      }}
-    >
-      {() => {
-        if (form.getFieldValue('redirectOption') === 'customRedirect') {
-          return (
+  const CustomRedirectType = () => {
+    switch (form.getFieldValue('customRedirectType')) {
+      case CUSTOM_REDIRECT_TYPE.STATIC:
+        return (
+          <Form.Item
+            label={formatMessage({ id: 'page.route.form.itemLabel.redirectCustom' })}
+            required
+            style={{ marginBottom: 0 }}
+          >
+            <Row gutter={10}>
+              <Col span={5}>
+                <Form.Item
+                  name="redirectURI"
+                  rules={[
+                    {
+                      required: true,
+                      message: `${formatMessage({
+                        id: 'component.global.pleaseEnter',
+                      })}${formatMessage({
+                        id: 'page.route.form.itemLabel.redirectURI',
+                      })}`,
+                    },
+                  ]}
+                >
+                  <Input
+                    placeholder={formatMessage({
+                      id: 'page.route.input.placeholder.redirectCustom',
+                    })}
+                    disabled={disabled}
+                  />
+                </Form.Item>
+              </Col>
+              <Col span={5}>
+                <Form.Item name="ret_code" rules={[{ required: true }]}>
+                  <Select disabled={disabled} data-cy="redirect_code">
+                    <Select.Option value={301}>
+                      {formatMessage({ id: 'page.route.select.option.redirect301' })}
+                    </Select.Option>
+                    <Select.Option value={302}>
+                      {formatMessage({ id: 'page.route.select.option.redirect302' })}
+                    </Select.Option>
+                  </Select>
+                </Form.Item>
+              </Col>
+            </Row>
+          </Form.Item>
+        );
+      case CUSTOM_REDIRECT_TYPE.REGEXP:
+        return (
+          <React.Fragment>
+            <Form.List name="regex_uri" initialValue={['', '']}>
+              {(fields) =>
+                fields.map((field, index) => {
+                  switch (index) {
+                    case 0:
+                      return (
+                        <Form.Item
+                          label={formatMessage({ id: 'page.route.form.itemLabel.regex' })}
+                          name={field.name}
+                          key={field.name}
+                          rules={[
+                            {
+                              required: true,
+                              message: `${formatMessage({
+                                id: 'component.global.pleaseEnter',
+                              })} ${formatMessage({ id: 'page.route.form.itemLabel.regex' })}`,
+                            },
+                          ]}
+                        >
+                          <Input
+                            placeholder={`${formatMessage({
+                              id: 'component.global.pleaseEnter',
+                            })} ${formatMessage({ id: 'page.route.form.itemLabel.regex' })}`}
+                            disabled={disabled}
+                          />
+                        </Form.Item>
+                      );
+                    case 1:
+                      return (
+                        <Form.Item
+                          label={formatMessage({ id: 'page.route.form.itemLabel.template' })}
+                          name={field.name}
+                          key={field.name}
+                          rules={[
+                            {
+                              required: true,
+                              message: `${formatMessage({
+                                id: 'component.global.pleaseEnter',
+                              })} ${formatMessage({ id: 'page.route.form.itemLabel.template' })}`,

Review Comment:
   ditto



##########
web/src/pages/Route/components/Step1/MetaView.tsx:
##########
@@ -258,67 +269,195 @@ const MetaView: React.FC<RouteModule.Step1PassProps> = ({
     );
   };
 
-  const CustomRedirect: React.FC = () => (
-    <Form.Item
-      noStyle
-      shouldUpdate={(prev, next) => {
-        if (prev.redirectOption !== next.redirectOption) {
-          onChange({ action: 'redirectOptionChange', data: next.redirectOption });
-        }
-        return prev.redirectOption !== next.redirectOption;
-      }}
-    >
-      {() => {
-        if (form.getFieldValue('redirectOption') === 'customRedirect') {
-          return (
+  const CustomRedirectType = () => {
+    switch (form.getFieldValue('customRedirectType')) {
+      case CUSTOM_REDIRECT_TYPE.STATIC:
+        return (
+          <Form.Item
+            label={formatMessage({ id: 'page.route.form.itemLabel.redirectCustom' })}
+            required
+            style={{ marginBottom: 0 }}
+          >
+            <Row gutter={10}>
+              <Col span={5}>
+                <Form.Item
+                  name="redirectURI"
+                  rules={[
+                    {
+                      required: true,
+                      message: `${formatMessage({
+                        id: 'component.global.pleaseEnter',
+                      })}${formatMessage({
+                        id: 'page.route.form.itemLabel.redirectURI',
+                      })}`,
+                    },
+                  ]}
+                >
+                  <Input
+                    placeholder={formatMessage({
+                      id: 'page.route.input.placeholder.redirectCustom',
+                    })}
+                    disabled={disabled}
+                  />
+                </Form.Item>
+              </Col>
+              <Col span={5}>
+                <Form.Item name="ret_code" rules={[{ required: true }]}>
+                  <Select disabled={disabled} data-cy="redirect_code">
+                    <Select.Option value={301}>
+                      {formatMessage({ id: 'page.route.select.option.redirect301' })}
+                    </Select.Option>
+                    <Select.Option value={302}>
+                      {formatMessage({ id: 'page.route.select.option.redirect302' })}
+                    </Select.Option>
+                  </Select>
+                </Form.Item>
+              </Col>
+            </Row>
+          </Form.Item>
+        );
+      case CUSTOM_REDIRECT_TYPE.REGEXP:
+        return (
+          <React.Fragment>
+            <Form.List name="regex_uri" initialValue={['', '']}>
+              {(fields) =>
+                fields.map((field, index) => {
+                  switch (index) {
+                    case 0:
+                      return (
+                        <Form.Item
+                          label={formatMessage({ id: 'page.route.form.itemLabel.regex' })}
+                          name={field.name}
+                          key={field.name}
+                          rules={[
+                            {
+                              required: true,
+                              message: `${formatMessage({
+                                id: 'component.global.pleaseEnter',
+                              })} ${formatMessage({ id: 'page.route.form.itemLabel.regex' })}`,

Review Comment:
   ditto



##########
web/cypress/integration/route/create-edit-route-with-redirect-regex-plugin.spec.js:
##########
@@ -0,0 +1,124 @@
+/*
+ * 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 Edit and Delete Route with redirect plugin', () => {
+  const name = `${new Date().getFullYear()}-${new Date().getMonth() + 1}-${new Date().getDay()}`;
+  const newName = `${new Date().getFullYear()}*${new Date().getMonth() + 1}*${new Date().getDay()}`;
+
+  const selector = {
+    empty: '.ant-empty-normal',
+    name: '#name',
+    redirect: '[data-cy=route-redirect]',
+    customRedirectSelectOpt: '#redirectOption_list_1',
+    regex: '#regex',
+    customRedirectCode: '[data-cy=redirect_code]',
+    customRedirectRegexLabel: "[title='Regexp']",
+    regexp: '#regex_uri_0',
+    template: '#regex_uri_1',
+    deleteAlert: '.ant-modal-body',
+    notificationCloseIcon: '.ant-notification-close-icon',
+    notification: '.ant-notification-notice-message',
+    webSocketSelector: '[title=WebSocket]',
+    enable_websocket_button: '#enable_websocket',
+  };
+
+  const data = {
+    customRedirectRegexUri: '^/iresty/(.)/(.)/(.*)',
+    customRedirectTemplate: '/$1-$2-$3',
+    submitSuccess: 'Submit Successfully',
+    deleteRouteSuccess: 'Delete Route Successfully',
+    step2Title: 'Define API Backend Server',
+    step3Title: 'Plugin Config',
+    setUpstreamNotice: 'If you do not bind the service, you must set the Upstream (Step 2)',
+  };
+
+  beforeEach(() => {
+    cy.login();
+  });
+
+  it('should create route with custom redirect plugin', function () {
+    cy.visit('/');
+    cy.contains('Route').click();
+    cy.contains('Create').click();
+    cy.contains('Next').click().click();
+    cy.get(selector.name).type(name);
+    cy.get(selector.redirect).click();
+    cy.contains('Custom').click({ force: true });

Review Comment:
   the `force` is a dangerous attribute and it is likely that he has not yet clicked on Custom Option



##########
web/src/pages/Route/components/Step1/MetaView.tsx:
##########
@@ -258,67 +269,195 @@ const MetaView: React.FC<RouteModule.Step1PassProps> = ({
     );
   };
 
-  const CustomRedirect: React.FC = () => (
-    <Form.Item
-      noStyle
-      shouldUpdate={(prev, next) => {
-        if (prev.redirectOption !== next.redirectOption) {
-          onChange({ action: 'redirectOptionChange', data: next.redirectOption });
-        }
-        return prev.redirectOption !== next.redirectOption;
-      }}
-    >
-      {() => {
-        if (form.getFieldValue('redirectOption') === 'customRedirect') {
-          return (
+  const CustomRedirectType = () => {
+    switch (form.getFieldValue('customRedirectType')) {
+      case CUSTOM_REDIRECT_TYPE.STATIC:
+        return (
+          <Form.Item
+            label={formatMessage({ id: 'page.route.form.itemLabel.redirectCustom' })}
+            required
+            style={{ marginBottom: 0 }}
+          >
+            <Row gutter={10}>
+              <Col span={5}>
+                <Form.Item
+                  name="redirectURI"
+                  rules={[
+                    {
+                      required: true,
+                      message: `${formatMessage({
+                        id: 'component.global.pleaseEnter',
+                      })}${formatMessage({
+                        id: 'page.route.form.itemLabel.redirectURI',
+                      })}`,

Review Comment:
   It is better not to use template strings to complete the internationalization, the Chinese state will have extra spaces



##########
web/cypress/integration/route/create-edit-route-with-redirect-regex-plugin.spec.js:
##########
@@ -0,0 +1,124 @@
+/*
+ * 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 Edit and Delete Route with redirect plugin', () => {
+  const name = `${new Date().getFullYear()}-${new Date().getMonth() + 1}-${new Date().getDay()}`;
+  const newName = `${new Date().getFullYear()}*${new Date().getMonth() + 1}*${new Date().getDay()}`;
+
+  const selector = {
+    empty: '.ant-empty-normal',
+    name: '#name',
+    redirect: '[data-cy=route-redirect]',
+    customRedirectSelectOpt: '#redirectOption_list_1',
+    regex: '#regex',
+    customRedirectCode: '[data-cy=redirect_code]',
+    customRedirectRegexLabel: "[title='Regexp']",
+    regexp: '#regex_uri_0',
+    template: '#regex_uri_1',
+    deleteAlert: '.ant-modal-body',
+    notificationCloseIcon: '.ant-notification-close-icon',
+    notification: '.ant-notification-notice-message',
+    webSocketSelector: '[title=WebSocket]',
+    enable_websocket_button: '#enable_websocket',
+  };
+
+  const data = {
+    customRedirectRegexUri: '^/iresty/(.)/(.)/(.*)',
+    customRedirectTemplate: '/$1-$2-$3',
+    submitSuccess: 'Submit Successfully',
+    deleteRouteSuccess: 'Delete Route Successfully',
+    step2Title: 'Define API Backend Server',
+    step3Title: 'Plugin Config',
+    setUpstreamNotice: 'If you do not bind the service, you must set the Upstream (Step 2)',
+  };
+
+  beforeEach(() => {
+    cy.login();
+  });
+
+  it('should create route with custom redirect plugin', function () {
+    cy.visit('/');
+    cy.contains('Route').click();
+    cy.contains('Create').click();
+    cy.contains('Next').click().click();
+    cy.get(selector.name).type(name);
+    cy.get(selector.redirect).click();
+    cy.contains('Custom').click({ force: true });
+    cy.get(selector.regex).click();
+
+    // after choose Custom option, Custom Redirect form field should be visible
+    cy.get(selector.customRedirectRegexLabel).should('be.visible');
+    cy.get(selector.regexp).should('be.visible');
+    cy.get(selector.template).should('be.visible');
+    cy.get(selector.customRedirectCode).should('be.visible');
+    cy.get(selector.webSocketSelector).should('exist');
+    cy.get(selector.enable_websocket_button).should('exist');
+
+    // step 2 and step 3 should not be visible
+    cy.contains(data.step2Title).should('not.exist');
+    cy.contains(data.step3Title).should('not.exist');
+    // type customRedirectRegexUri and customRedirectTemplate
+    cy.get(selector.regexp).type(data.customRedirectRegexUri);
+    cy.get(selector.template).type(data.customRedirectTemplate);
+    cy.contains('Next').click();
+    cy.contains('Submit').click();
+    cy.contains(data.submitSuccess);
+
+    // back to route list page
+    cy.contains('Goto List').click();
+    cy.url().should('contains', 'routes/list');
+  });
+
+  it('should edit the route without notice', function () {

Review Comment:
   When editing this route, you should ensure that the data created is displayed back properly



##########
web/src/pages/Route/components/Step1/MetaView.tsx:
##########
@@ -258,67 +269,195 @@ const MetaView: React.FC<RouteModule.Step1PassProps> = ({
     );
   };
 
-  const CustomRedirect: React.FC = () => (
-    <Form.Item
-      noStyle
-      shouldUpdate={(prev, next) => {
-        if (prev.redirectOption !== next.redirectOption) {
-          onChange({ action: 'redirectOptionChange', data: next.redirectOption });
-        }
-        return prev.redirectOption !== next.redirectOption;
-      }}
-    >
-      {() => {
-        if (form.getFieldValue('redirectOption') === 'customRedirect') {
-          return (
+  const CustomRedirectType = () => {
+    switch (form.getFieldValue('customRedirectType')) {
+      case CUSTOM_REDIRECT_TYPE.STATIC:
+        return (
+          <Form.Item
+            label={formatMessage({ id: 'page.route.form.itemLabel.redirectCustom' })}
+            required
+            style={{ marginBottom: 0 }}
+          >
+            <Row gutter={10}>
+              <Col span={5}>
+                <Form.Item
+                  name="redirectURI"
+                  rules={[
+                    {
+                      required: true,
+                      message: `${formatMessage({
+                        id: 'component.global.pleaseEnter',
+                      })}${formatMessage({
+                        id: 'page.route.form.itemLabel.redirectURI',
+                      })}`,
+                    },
+                  ]}
+                >
+                  <Input
+                    placeholder={formatMessage({
+                      id: 'page.route.input.placeholder.redirectCustom',
+                    })}
+                    disabled={disabled}
+                  />
+                </Form.Item>
+              </Col>
+              <Col span={5}>
+                <Form.Item name="ret_code" rules={[{ required: true }]}>
+                  <Select disabled={disabled} data-cy="redirect_code">
+                    <Select.Option value={301}>
+                      {formatMessage({ id: 'page.route.select.option.redirect301' })}
+                    </Select.Option>
+                    <Select.Option value={302}>
+                      {formatMessage({ id: 'page.route.select.option.redirect302' })}
+                    </Select.Option>
+                  </Select>
+                </Form.Item>
+              </Col>
+            </Row>
+          </Form.Item>
+        );
+      case CUSTOM_REDIRECT_TYPE.REGEXP:
+        return (
+          <React.Fragment>
+            <Form.List name="regex_uri" initialValue={['', '']}>
+              {(fields) =>
+                fields.map((field, index) => {
+                  switch (index) {
+                    case 0:
+                      return (
+                        <Form.Item
+                          label={formatMessage({ id: 'page.route.form.itemLabel.regex' })}
+                          name={field.name}
+                          key={field.name}
+                          rules={[
+                            {
+                              required: true,
+                              message: `${formatMessage({
+                                id: 'component.global.pleaseEnter',
+                              })} ${formatMessage({ id: 'page.route.form.itemLabel.regex' })}`,
+                            },
+                          ]}
+                        >
+                          <Input
+                            placeholder={`${formatMessage({
+                              id: 'component.global.pleaseEnter',
+                            })} ${formatMessage({ id: 'page.route.form.itemLabel.regex' })}`}
+                            disabled={disabled}
+                          />
+                        </Form.Item>
+                      );
+                    case 1:
+                      return (
+                        <Form.Item
+                          label={formatMessage({ id: 'page.route.form.itemLabel.template' })}
+                          name={field.name}
+                          key={field.name}
+                          rules={[
+                            {
+                              required: true,
+                              message: `${formatMessage({
+                                id: 'component.global.pleaseEnter',
+                              })} ${formatMessage({ id: 'page.route.form.itemLabel.template' })}`,
+                            },
+                          ]}
+                        >
+                          <Input
+                            placeholder={`${formatMessage({
+                              id: 'component.global.pleaseEnter',
+                            })} ${formatMessage({ id: 'page.route.form.itemLabel.template' })}`}

Review Comment:
   ditto



##########
web/cypress/integration/route/create-edit-route-with-redirect-regex-plugin.spec.js:
##########
@@ -0,0 +1,124 @@
+/*
+ * 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 Edit and Delete Route with redirect plugin', () => {
+  const name = `${new Date().getFullYear()}-${new Date().getMonth() + 1}-${new Date().getDay()}`;
+  const newName = `${new Date().getFullYear()}*${new Date().getMonth() + 1}*${new Date().getDay()}`;
+
+  const selector = {
+    empty: '.ant-empty-normal',
+    name: '#name',
+    redirect: '[data-cy=route-redirect]',
+    customRedirectSelectOpt: '#redirectOption_list_1',
+    regex: '#regex',
+    customRedirectCode: '[data-cy=redirect_code]',
+    customRedirectRegexLabel: "[title='Regexp']",
+    regexp: '#regex_uri_0',
+    template: '#regex_uri_1',
+    deleteAlert: '.ant-modal-body',
+    notificationCloseIcon: '.ant-notification-close-icon',
+    notification: '.ant-notification-notice-message',
+    webSocketSelector: '[title=WebSocket]',
+    enable_websocket_button: '#enable_websocket',
+  };
+
+  const data = {
+    customRedirectRegexUri: '^/iresty/(.)/(.)/(.*)',
+    customRedirectTemplate: '/$1-$2-$3',
+    submitSuccess: 'Submit Successfully',
+    deleteRouteSuccess: 'Delete Route Successfully',
+    step2Title: 'Define API Backend Server',
+    step3Title: 'Plugin Config',
+    setUpstreamNotice: 'If you do not bind the service, you must set the Upstream (Step 2)',
+  };
+
+  beforeEach(() => {
+    cy.login();
+  });
+
+  it('should create route with custom redirect plugin', function () {
+    cy.visit('/');
+    cy.contains('Route').click();
+    cy.contains('Create').click();
+    cy.contains('Next').click().click();
+    cy.get(selector.name).type(name);
+    cy.get(selector.redirect).click();
+    cy.contains('Custom').click({ force: true });
+    cy.get(selector.regex).click();
+
+    // after choose Custom option, Custom Redirect form field should be visible
+    cy.get(selector.customRedirectRegexLabel).should('be.visible');
+    cy.get(selector.regexp).should('be.visible');
+    cy.get(selector.template).should('be.visible');
+    cy.get(selector.customRedirectCode).should('be.visible');
+    cy.get(selector.webSocketSelector).should('exist');
+    cy.get(selector.enable_websocket_button).should('exist');
+
+    // step 2 and step 3 should not be visible
+    cy.contains(data.step2Title).should('not.exist');
+    cy.contains(data.step3Title).should('not.exist');
+    // type customRedirectRegexUri and customRedirectTemplate
+    cy.get(selector.regexp).type(data.customRedirectRegexUri);
+    cy.get(selector.template).type(data.customRedirectTemplate);
+    cy.contains('Next').click();
+    cy.contains('Submit').click();
+    cy.contains(data.submitSuccess);
+
+    // back to route list page
+    cy.contains('Goto List').click();
+    cy.url().should('contains', 'routes/list');
+  });
+
+  it('should edit the route without notice', function () {
+    cy.visit('/');
+    cy.contains('Route').click();
+
+    cy.get(selector.name).type(name);
+    cy.contains('Search').click();
+    cy.contains(name).siblings().contains('Configure').click();
+
+    // NOTE: make sure all components rerender done
+    cy.get('#status').should('have.class', 'ant-switch-checked');
+    // should not shown set upstream notice
+    cy.contains(data.setUpstreamNotice).should('not.exist');
+    cy.get(selector.name).clear().type(newName);
+    cy.get(selector.webSocketSelector).should('exist');
+    cy.get(selector.enable_websocket_button).should('exist');
+
+    cy.contains('Next').click();
+    cy.contains('Submit').click();
+    cy.contains(data.submitSuccess);
+    cy.contains('Goto List').click();
+    cy.url().should('contains', 'routes/list');
+  });
+
+  it('should delete the route', function () {
+    cy.visit('/routes/list');
+    cy.get(selector.name).clear().type(newName);
+    cy.contains('Search').click();
+    cy.contains(newName).siblings().contains('More').click();
+    cy.contains('Delete').click();
+    cy.get(selector.deleteAlert)
+      .should('be.visible')
+      .within(() => {
+        cy.contains('OK').click();
+      });
+    cy.get(selector.notification).should('contain', data.deleteRouteSuccess);
+    cy.get(selector.notificationCloseIcon).click({ multiple: true });

Review Comment:
   ```suggestion
       cy.get(selector.notificationCloseIcon).click();
   ```
   No need to click continuously if there is only one prompt



##########
web/src/pages/Route/components/Step1/MetaView.tsx:
##########
@@ -258,67 +269,195 @@ const MetaView: React.FC<RouteModule.Step1PassProps> = ({
     );
   };
 
-  const CustomRedirect: React.FC = () => (
-    <Form.Item
-      noStyle
-      shouldUpdate={(prev, next) => {
-        if (prev.redirectOption !== next.redirectOption) {
-          onChange({ action: 'redirectOptionChange', data: next.redirectOption });
-        }
-        return prev.redirectOption !== next.redirectOption;
-      }}
-    >
-      {() => {
-        if (form.getFieldValue('redirectOption') === 'customRedirect') {
-          return (
+  const CustomRedirectType = () => {
+    switch (form.getFieldValue('customRedirectType')) {
+      case CUSTOM_REDIRECT_TYPE.STATIC:
+        return (
+          <Form.Item
+            label={formatMessage({ id: 'page.route.form.itemLabel.redirectCustom' })}
+            required
+            style={{ marginBottom: 0 }}
+          >
+            <Row gutter={10}>
+              <Col span={5}>
+                <Form.Item
+                  name="redirectURI"
+                  rules={[
+                    {
+                      required: true,
+                      message: `${formatMessage({
+                        id: 'component.global.pleaseEnter',
+                      })}${formatMessage({
+                        id: 'page.route.form.itemLabel.redirectURI',
+                      })}`,
+                    },
+                  ]}
+                >
+                  <Input
+                    placeholder={formatMessage({
+                      id: 'page.route.input.placeholder.redirectCustom',
+                    })}
+                    disabled={disabled}
+                  />
+                </Form.Item>
+              </Col>
+              <Col span={5}>
+                <Form.Item name="ret_code" rules={[{ required: true }]}>
+                  <Select disabled={disabled} data-cy="redirect_code">
+                    <Select.Option value={301}>
+                      {formatMessage({ id: 'page.route.select.option.redirect301' })}
+                    </Select.Option>
+                    <Select.Option value={302}>
+                      {formatMessage({ id: 'page.route.select.option.redirect302' })}
+                    </Select.Option>
+                  </Select>
+                </Form.Item>
+              </Col>
+            </Row>
+          </Form.Item>
+        );
+      case CUSTOM_REDIRECT_TYPE.REGEXP:
+        return (
+          <React.Fragment>
+            <Form.List name="regex_uri" initialValue={['', '']}>
+              {(fields) =>
+                fields.map((field, index) => {
+                  switch (index) {
+                    case 0:
+                      return (
+                        <Form.Item
+                          label={formatMessage({ id: 'page.route.form.itemLabel.regex' })}
+                          name={field.name}
+                          key={field.name}
+                          rules={[
+                            {
+                              required: true,
+                              message: `${formatMessage({
+                                id: 'component.global.pleaseEnter',
+                              })} ${formatMessage({ id: 'page.route.form.itemLabel.regex' })}`,
+                            },
+                          ]}
+                        >
+                          <Input
+                            placeholder={`${formatMessage({
+                              id: 'component.global.pleaseEnter',
+                            })} ${formatMessage({ id: 'page.route.form.itemLabel.regex' })}`}

Review Comment:
   ditto



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org