Copilot commented on code in PR #7878:
URL: https://github.com/apache/incubator-seata/pull/7878#discussion_r2637802265
##########
namingserver/src/main/java/org/apache/seata/namingserver/manager/NamingManager.java:
##########
@@ -182,6 +167,25 @@ public Result<String> createGroup(String namespace, String
vGroup, String cluste
public Result<String> createGroup(
String namespace, String vGroup, String clusterName, String
unitName, boolean checkExist) {
+ // If unitName is blank, find it from cluster
+ String actualUnitName = unitName;
+ if (StringUtils.isBlank(unitName)) {
+ Map<String, ClusterData> clusterDataMap =
namespaceClusterDataMap.get(namespace);
+ if (clusterDataMap != null) {
+ ClusterData clusterData = clusterDataMap.get(clusterName);
+ if (clusterData != null &&
!CollectionUtils.isEmpty(clusterData.getUnitData())) {
+ Optional<Map.Entry<String, Unit>> optionalEntry =
+
clusterData.getUnitData().entrySet().stream().findFirst();
+ if (optionalEntry.isPresent()) {
+ actualUnitName = optionalEntry.get().getKey();
Review Comment:
The logic for finding a unit when unitName is blank selects the first
available unit using stream().findFirst(). This is non-deterministic behavior
since the order of entries in a HashMap/ConcurrentHashMap is not guaranteed.
For better predictability and stability, consider using a LinkedHashMap or
sorting the entries before selecting one, or document that the selection is
arbitrary.
```suggestion
Optional<String> optionalUnitName =
clusterData.getUnitData().keySet().stream().sorted().findFirst();
if (optionalUnitName.isPresent()) {
actualUnitName = optionalUnitName.get();
```
##########
console/src/main/resources/static/console-fe/src/pages/TransactionInfo/TransactionInfo.tsx:
##########
@@ -545,7 +564,7 @@ class TransactionInfo extends React.Component<GlobalProps,
TransactionInfoState>
startGlobalSessionTitle,
sendGlobalSessionTitle,
changeGlobalSessionTitle,
- } = locale;
+ } = locale.TransactionInfo || {};
Review Comment:
The code uses optional chaining and assumes locale is always defined due to
the Redux connection, but in lines 567 and 758, it accesses
locale.TransactionInfo without checking if locale exists. While this is likely
safe with the Redux setup, defensive coding would suggest adding a fallback
(e.g., locale?.TransactionInfo || {}) to prevent potential runtime errors if
the Redux state is not properly initialized.
```suggestion
} = locale?.TransactionInfo || {};
```
##########
console/src/main/resources/static/console-fe/src/pages/TransactionInfo/TransactionInfo.tsx:
##########
@@ -1290,7 +1364,9 @@ class TransactionInfo extends
React.Component<GlobalProps, TransactionInfoState>
return {
targetNamespace: value,
targetClusters: clusters,
- targetCluster: clusters.length > 0 ? clusters[0] : '',
+ targetCluster: '',
+ targetUnits: [],
+ targetUnit: '',
};
Review Comment:
When targetNamespace is changed, targetCluster is reset to an empty string
(line 1367), but the logic doesn't select a default cluster like it does for
createNamespace (where createCluster is set to clusters[0]). This inconsistency
could lead to user confusion where one dialog pre-selects a cluster and the
other doesn't. Consider setting targetCluster to clusters[0] when
clusters.length > 0 for a consistent user experience.
##########
console/src/main/resources/static/console-fe/src/pages/TransactionInfo/TransactionInfo.tsx:
##########
@@ -351,14 +356,26 @@ class TransactionInfo extends
React.Component<GlobalProps, TransactionInfoState>
loadNamespaces = async () => {
try {
const namespaces = await fetchNamespaceV2();
- const namespaceOptions = new Map<string, { clusters: string[],
clusterVgroups: {[key: string]: string[]} }>();
+ const namespaceOptions = new Map<string, NamespaceData>();
+
Object.keys(namespaces).forEach(namespaceKey => {
const namespaceData = namespaces[namespaceKey];
- const clusterVgroups = (namespaceData.clusterVgroups || {}) as {[key:
string]: string[]};
- const clusters = Object.keys(clusterVgroups);
+ const clustersData = namespaceData.clusters || {};
+ const clusterVgroups: {[key: string]: string[]} = {};
+ const clusterUnits: {[key: string]: string[]} = {};
+ const clusterTypes: {[key: string]: string} = {};
+ Object.keys(clustersData).forEach(clusterName => {
+ const cluster = clustersData[clusterName];
+ clusterVgroups[clusterName] = cluster.vgroups || [];
+ clusterUnits[clusterName] = cluster.units || [];
+ clusterTypes[clusterName] = cluster.type || 'default';
+ });
+ const clusters = Object.keys(clustersData);
namespaceOptions.set(namespaceKey, {
clusters,
clusterVgroups,
+ clusterUnits,
+ clusterTypes,
});
Review Comment:
The namespace data processing logic is duplicated across three components
(TransactionInfo, GlobalLockInfo, and ClusterManager). All three have nearly
identical code for parsing namespaces, extracting cluster data, and building
the namespaceOptions Map. This logic should be extracted into a shared utility
function to improve maintainability and ensure consistency.
##########
namingserver/src/main/java/org/apache/seata/namingserver/manager/NamingManager.java:
##########
@@ -474,6 +478,11 @@ public void instanceHeartBeatCheck() {
public Result<String> changeGroup(String namespace, String vGroup, String
clusterName, String unitName) {
long changeTime = System.currentTimeMillis();
ConcurrentMap<String, NamespaceBO> namespaceMap = new
ConcurrentHashMap<>(vGroupMap.get(vGroup));
+ Result<String> res = createGroup(namespace, vGroup, clusterName,
unitName, false);
+ if (!res.isSuccess()) {
+ LOGGER.error("add vgroup failed! {}", res.getMessage());
+ return res;
+ }
Review Comment:
The order of operations in changeGroup has been reversed - createGroup is
now called before collecting existing namespace/cluster mappings. While this
change may be intentional to ensure the new group exists before removing the
old one, it could lead to a partial state if the subsequent removeGroup
operations fail. Consider adding compensating logic or documentation explaining
why this order is necessary and how failures are handled.
##########
console/src/main/resources/static/console-fe/src/service/clusterManager.ts:
##########
@@ -31,3 +32,11 @@ export async function fetchClusterData(namespace: string,
clusterName: string):
});
return result;
}
+
+export async function changeGroup(namespace: string, clusterName: string,
vGroup: string, unitName: string = ''): Promise<any> {
+ const params = { namespace, clusterName, unitName, vGroup };
+ const result = await request.post('/naming/changeGroup',
qs.stringify(params), {
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+ });
+ return result;
+}
Review Comment:
This function is duplicated with the same implementation in
transactionInfo.ts (lines 260-266). Consider extracting this common function to
a shared location to avoid code duplication and ensure consistency across the
codebase.
```suggestion
export async function postChangeGroup(
namespace: string,
clusterName: string,
vGroup: string,
unitName: string = '',
): Promise<any> {
const params = { namespace, clusterName, unitName, vGroup };
const result = await request.post('/naming/changeGroup',
qs.stringify(params), {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
return result;
}
export async function changeGroup(namespace: string, clusterName: string,
vGroup: string, unitName: string = ''): Promise<any> {
return postChangeGroup(namespace, clusterName, vGroup, unitName);
}
```
##########
console/src/main/resources/static/console-fe/src/pages/TransactionInfo/TransactionInfo.tsx:
##########
@@ -18,11 +18,11 @@ import React from 'react';
import { ConfigProvider, Table, Button, DatePicker, Form, Icon, Switch,
Pagination, Dialog, Input, Select, Message } from
'@alicloud/console-components';
import Actions, { LinkButton } from '@alicloud/console-components-actions';
import { withRouter } from 'react-router-dom';
+import { connect } from 'react-redux';
import Page from '@/components/Page';
import { GlobalProps } from '@/module';
import getData, { changeGlobalData, deleteBranchData, deleteGlobalData,
GlobalSessionParam, sendGlobalCommitOrRollback,
startBranchData, startGlobalData, stopBranchData, stopGlobalData,
forceDeleteGlobalData, forceDeleteBranchData, fetchNamespaceV2, addGroup,
changeGroup } from '@/service/transactionInfo';
-import PropTypes from 'prop-types';
import moment from 'moment';
Review Comment:
The PropTypes import and static propTypes declaration have been removed but
the import is still present in ClusterManager.tsx, GlobalLockInfo.tsx, and
Login.tsx. This creates an inconsistency across the codebase. Either remove
PropTypes from all components or keep it in all for consistency, especially
since TypeScript provides static type checking through the GlobalProps
interface.
##########
namingserver/src/main/java/org/apache/seata/namingserver/manager/NamingManager.java:
##########
@@ -587,14 +623,23 @@ private NamespaceData collectNamespaceData() {
Set<String> vgroups = vgroupsMap.get(namespace);
vgroups.add(vGroup);
- // Build cluster to vgroups
+ // Build cluster to vgroups and unit to vgroups mapping
Map<String, Set<String>> clusterVg =
clusterVgroupsMap.get(namespace);
+ Map<String, Map<String, Set<String>>> clusterUnitVg =
unitVgroupsMap.get(namespace);
+
namespaceBO.getClusterMap().forEach((clusterName, clusterBO) -> {
Set<String> vgSet = clusterVg.computeIfAbsent(clusterName, k
-> new HashSet<>());
vgSet.add(vGroup);
+
+ // For each unit in this cluster, add the vgroup
+ Map<String, Set<String>> unitVgMap =
clusterUnitVg.computeIfAbsent(clusterName, k -> new HashMap<>());
+ clusterBO.getUnitNames().forEach(unitName -> {
+ Set<String> unitVgSet =
unitVgMap.computeIfAbsent(unitName, k -> new HashSet<>());
+ unitVgSet.add(vGroup);
+ });
Review Comment:
The unitVgroupsMap is being collected and passed to NamespaceData
constructor but appears to be unused in the namespaceV2() method. The method
only uses clustersMap, vgroupsMap, and clusterVgroupsMap when building the
response. If unitVgroupsMap is intended for future use or a different API
endpoint, consider documenting this or removing it if it's truly unused to
avoid confusion.
##########
console/src/main/resources/static/console-fe/src/reducers/locale.ts:
##########
@@ -52,7 +52,7 @@ const getCurrentLanguage = (): string => {
return lang;
}
-const getCurrentLocaleObj = (): any => {
+const getCurrentLocaleObj = (): ILocale => {
Review Comment:
The return type has been weakened from ILocale to any, which removes type
safety for locale objects throughout the application. This change should be
avoided to maintain type checking. Consider updating the return type to match
the actual locale structure being used.
##########
console/src/main/resources/static/console-fe/src/pages/ClusterManager/ClusterManager.tsx:
##########
@@ -215,11 +224,13 @@ class ClusterManager extends React.Component<GlobalProps,
ClusterManagerState> {
};
render() {
- const { locale = {} } = this.props;
+ const { locale } = this.props;
const rawLocale = locale.ClusterManager;
const clusterManagerLocale: ClusterManagerLocale = typeof rawLocale ===
'object' && rawLocale !== null ? rawLocale : {};
- const { title, subTitle, selectNamespaceFilerPlaceholder,
selectClusterFilerPlaceholder, searchButtonLabel, unitName, members,
clusterType, view, unitDialogTitle, control, transaction, weight, healthy,
term, unit, operations, internal, version, metadata, controlEndpoint,
transactionEndpoint, metadataDialogTitle } = clusterManagerLocale;
+ const { title, subTitle, selectNamespaceFilerPlaceholder,
selectClusterFilerPlaceholder, searchButtonLabel, unitName, members,
clusterType, view, unitDialogTitle, control, transaction, weight, healthy,
term, unit, operations, internal, version, metadata, controlEndpoint,
transactionEndpoint, metadataDialogTitle, role } = clusterManagerLocale;
Review Comment:
Unused variable unitName.
```suggestion
const { title, subTitle, selectNamespaceFilerPlaceholder,
selectClusterFilerPlaceholder, searchButtonLabel, members, clusterType, view,
unitDialogTitle, control, transaction, weight, healthy, term, unit, operations,
internal, version, metadata, controlEndpoint, transactionEndpoint,
metadataDialogTitle, role } = clusterManagerLocale;
```
--
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: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]