This is an automated email from the ASF dual-hosted git repository.
dengliming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new 591499b3 fix: guard optional discovery fields (#671)
591499b3 is described below
commit 591499b3150ec2c90f137f141112218548ba8fef
Author: Efe <[email protected]>
AuthorDate: Mon Sep 28 06:32:30 2026 +0300
fix: guard optional discovery fields (#671)
* fix: guard optional discovery fields
Signed-off-by: Efe Gökdemir <[email protected]>
* fix: guard missing discovery handlers during render
Signed-off-by: Efe Gökdemir <[email protected]>
* fix(discovery): satisfy lint for connected modal import
Signed-off-by: Efe Gökdemir <[email protected]>
---------
Signed-off-by: Efe Gökdemir <[email protected]>
---
src/routes/Document/components/ApiInfo.js | 5 +-
.../Plugin/Discovery/DiscoveryConfigModal.js | 5 +-
src/routes/Plugin/Discovery/ProxySelectorModal.js | 10 ++--
.../Plugin/Discovery/ProxySelectorModal.test.js | 58 ++++++++++++++++++++++
src/routes/Plugin/Discovery/index.js | 6 +--
src/routes/Plugin/Discovery/optionalFields.js | 28 +++++++++++
src/routes/Plugin/Discovery/optionalFields.test.js | 44 ++++++++++++++++
7 files changed, 145 insertions(+), 11 deletions(-)
diff --git a/src/routes/Document/components/ApiInfo.js
b/src/routes/Document/components/ApiInfo.js
index 62f7f28e..dfdd8bf1 100644
--- a/src/routes/Document/components/ApiInfo.js
+++ b/src/routes/Document/components/ApiInfo.js
@@ -21,6 +21,7 @@ import ApiDebug from "./ApiDebug";
import ApiContext from "./ApiContext";
import { getIntlContent } from "../../../utils/IntlUtils";
import { Method } from "./globalData";
+import { getLastTagName } from "../../Plugin/Discovery/optionalFields";
const { Title, Text, Paragraph } = Typography;
@@ -159,9 +160,7 @@ function ApiInfo(props) {
>
<Row gutter={24}>
<Col span={12}>
- <Title level={2}>
- {apiDetail.tags[apiDetail.tags.length - 1].name}
- </Title>
+ <Title level={2}>{getLastTagName(apiDetail.tags)}</Title>
</Col>
<Col span={12} style={{ textAlign: "right" }}>
<Button onClick={handleUpdate}>
diff --git a/src/routes/Plugin/Discovery/DiscoveryConfigModal.js
b/src/routes/Plugin/Discovery/DiscoveryConfigModal.js
index 3514e0b6..4466b35a 100644
--- a/src/routes/Plugin/Discovery/DiscoveryConfigModal.js
+++ b/src/routes/Plugin/Discovery/DiscoveryConfigModal.js
@@ -19,6 +19,7 @@ import React, { Component } from "react";
import { Button, Col, Form, Input, Modal, Popconfirm, Row, Select } from
"antd";
import { connect } from "dva";
import { getIntlContent } from "../../../utils/IntlUtils";
+import { getDiscoveryProps } from "./optionalFields";
const FormItem = Form.Item;
@@ -50,7 +51,9 @@ class DiscoveryConfigModal extends Component {
},
});
} else {
- this.setState({ configPropsJson: JSON.parse(data.props) });
+ this.setState({
+ configPropsJson: getDiscoveryProps(JSON.parse(data?.props || "{}")),
+ });
}
}
diff --git a/src/routes/Plugin/Discovery/ProxySelectorModal.js
b/src/routes/Plugin/Discovery/ProxySelectorModal.js
index 62ce971c..63c5242e 100644
--- a/src/routes/Plugin/Discovery/ProxySelectorModal.js
+++ b/src/routes/Plugin/Discovery/ProxySelectorModal.js
@@ -36,6 +36,7 @@ import styles from "../index.less";
import ProxySelectorCopy from "./ProxySelectorCopy.js";
import { findKeyByValue } from "../../../utils/utils";
import EditableFormTable from "./DiscoveryUpstreamTable";
+import { getDefaultValueList, getDiscoveryProps } from "./optionalFields";
const FormItem = Form.Item;
const { TabPane } = Tabs;
@@ -60,7 +61,7 @@ export class ProxySelectorModalComponent extends Component {
const { isAdd, isSetConfig, discoveryType, data, pluginId, dispatch } =
this.props;
const { discoveryDicts } = this.state;
- const { props } = this.props.data || {};
+ const props = getDiscoveryProps(this.props.data?.props);
if (!isAdd || isSetConfig) {
this.setState({
@@ -109,8 +110,9 @@ export class ProxySelectorModalComponent extends Component {
pluginHandles[0] = filteredArray;
this.setState({ pluginHandleList: pluginHandles });
- let defaultValue = handlerArray[0].defaultValue;
- this.setState({ defaultValueList: defaultValue.split(",") });
+ this.setState({
+ defaultValueList: getDefaultValueList(handlerArray[0]),
+ });
}
},
},
@@ -607,7 +609,7 @@ export class ProxySelectorModalComponent extends Component {
style={{ width: "100%" }}
>
{(() => {
- if (discoveryHandler != null) {
+ if (discoveryHandler?.[0] != null) {
let item = discoveryHandler[0];
let checkRule = item.checkRule;
let required = item.required === "1";
diff --git a/src/routes/Plugin/Discovery/ProxySelectorModal.test.js
b/src/routes/Plugin/Discovery/ProxySelectorModal.test.js
new file mode 100644
index 00000000..7f97041b
--- /dev/null
+++ b/src/routes/Plugin/Discovery/ProxySelectorModal.test.js
@@ -0,0 +1,58 @@
+/*
+ * 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 { render } from "@testing-library/react";
+
+jest.mock("./ProxySelectorCopy.js", () => () => null);
+jest.mock("./DiscoveryUpstreamTable", () => () => null);
+
+const { ProxySelectorModalComponent } = require("./ProxySelectorModal");
+
+describe("ProxySelectorModal", () => {
+ it("renders when another plugin handle exists without a discovery handler",
() => {
+ const props = {
+ chosenType: "zookeeper",
+ data: {
+ discovery: { props: "{}", serverList: [] },
+ handler: "{}",
+ },
+ discoveryDicts: [],
+ discoveryType: "zookeeper",
+ typeEnums: ["local", "zookeeper"],
+ dispatch: ({ payload }) => {
+ if (payload.callBack) {
+ payload.callBack([[{ field: "loadBalance", dataType: 1 }]]);
+ }
+ },
+ form: {
+ getFieldDecorator: () => (element) => element,
+ },
+ handleCancel: jest.fn(),
+ handleOk: jest.fn(),
+ isAdd: true,
+ isSetConfig: false,
+ pluginId: "plugin-id",
+ recordCount: 0,
+ discoveryUpstreams: [],
+ };
+
+ expect(() =>
+ render(<ProxySelectorModalComponent {...props} />),
+ ).not.toThrow();
+ });
+});
diff --git a/src/routes/Plugin/Discovery/index.js
b/src/routes/Plugin/Discovery/index.js
index 3484a538..30390703 100644
--- a/src/routes/Plugin/Discovery/index.js
+++ b/src/routes/Plugin/Discovery/index.js
@@ -30,7 +30,7 @@ import {
import { getIntlContent } from "../../../utils/IntlUtils";
import discoveryStyles from "./discovery.less";
import DiscoveryConfigModal from "./DiscoveryConfigModal";
-import ProxySelectorModal from "./ProxySelectorModal";
+import ConnectedProxySelectorModal from "./ProxySelectorModal";
import { DiscoveryCard } from "./DiscoveryCard";
import AuthButton from "../../../utils/AuthButton";
import { getUpdateModal, updatePluginsEnabled } from "../../../utils/plugin";
@@ -308,7 +308,7 @@ export default class DiscoveryProxy extends Component {
}
this.setState({
popup: (
- <ProxySelectorModal
+ <ConnectedProxySelectorModal
pluginId={plugin.id}
recordCount={cardData.discoveryUpstreams.length}
typeEnums={typeEnums}
@@ -420,7 +420,7 @@ export default class DiscoveryProxy extends Component {
});
this.setState({
popup: (
- <ProxySelectorModal
+ <ConnectedProxySelectorModal
recordCount={updateArray.length}
discoveryUpstreams={updateArray}
discoveryType={data.discovery.type}
diff --git a/src/routes/Plugin/Discovery/optionalFields.js
b/src/routes/Plugin/Discovery/optionalFields.js
new file mode 100644
index 00000000..a810ed35
--- /dev/null
+++ b/src/routes/Plugin/Discovery/optionalFields.js
@@ -0,0 +1,28 @@
+/*
+ * 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 function getLastTagName(tags) {
+ return tags?.[tags.length - 1]?.name;
+}
+
+export function getDiscoveryProps(props) {
+ return props || {};
+}
+
+export function getDefaultValueList(handler) {
+ return handler?.defaultValue ? handler.defaultValue.split(",") : [];
+}
diff --git a/src/routes/Plugin/Discovery/optionalFields.test.js
b/src/routes/Plugin/Discovery/optionalFields.test.js
new file mode 100644
index 00000000..e68481c3
--- /dev/null
+++ b/src/routes/Plugin/Discovery/optionalFields.test.js
@@ -0,0 +1,44 @@
+/*
+ * 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 {
+ getDefaultValueList,
+ getDiscoveryProps,
+ getLastTagName,
+} from "./optionalFields";
+
+describe("optional discovery and document fields", () => {
+ it("returns the final tag name without assuming tags exist", () => {
+ expect(getLastTagName([{ name: "first" }, { name: "last" }])).toBe("last");
+ expect(getLastTagName([])).toBeUndefined();
+ expect(getLastTagName(undefined)).toBeUndefined();
+ });
+
+ it("normalizes missing discovery props to an empty object", () => {
+ expect(getDiscoveryProps(null)).toEqual({});
+ expect(getDiscoveryProps({ timeout: "3s" })).toEqual({ timeout: "3s" });
+ });
+
+ it("does not dereference a missing discovery handler", () => {
+ expect(getDefaultValueList(undefined)).toEqual([]);
+ expect(getDefaultValueList({})).toEqual([]);
+ expect(getDefaultValueList({ defaultValue: "http,https" })).toEqual([
+ "http",
+ "https",
+ ]);
+ });
+});