This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 87c4c7ff7 fix(topic): allow a blank update to clear the persisted
topic remark (#4275)
87c4c7ff7 is described below
commit 87c4c7ff7679c36584dcba5f293de23c373d7df5
Author: btlqql <[email protected]>
AuthorDate: Tue Sep 15 20:25:11 2026 +0800
fix(topic): allow a blank update to clear the persisted topic remark (#4275)
`RocketMQAdminClientImpl.updateTopic` only wrote the cached row's remark
when the submitted value
had text:
if (StringUtils.hasText(topic.getRemark())) {
existing.setRemark(topic.getRemark());
}
so a blank submission behaved exactly like an omitted one. `TopicVO.remark`
/ `UpdateTopicDTO.remark`
are plain nullable strings with no validation and there is no separate
clear flag (unlike
`clearApiKey` / `clearDingtalkSigningSecret` in general settings), so a
blank remark is the only way
a caller can say "this topic has no remark" - and that value was discarded.
The `rmq_instance_topic`
row therefore keeps the previous remark while the endpoint answers 200, and
the topic list shows the
old text again on the next load.
Clearing the column is a two-part change. `updateById` omits null entity
fields
(`FieldStrategy.NOT_NULL`), the same mechanism already handled for the ACL
user/rule columns in
#3342, so the cleared remark is assigned explicitly instead of relying on
the entity write. An
omitted remark still keeps the stored value, which is the partial-update
behaviour the existing
comment in this method describes.
The response now reports the persisted remark instead of the submitted one,
so a clear and an
omitted value cannot be reported as a state the database does not hold.
`RocketMQAdminClientImplTest` pins both directions: a blank remark clears
the column through an
explicit assignment (and the returned topic is empty), while an omitted
remark keeps the stored value
and issues no assignment at all.
Fixes #4272
(cherry picked from commit 925958f7420dbe3bd543953b1c3c1c46147f47eb)
Signed-off-by: btlqql <[email protected]>
---
.../provider/apache/RocketMQAdminClientImpl.java | 20 ++++++++-
.../apache/RocketMQAdminClientImplTest.java | 51 ++++++++++++++++++++++
2 files changed, 69 insertions(+), 2 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
index 2e3d0dbca..f1131b7c4 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
@@ -53,6 +53,7 @@ import
org.apache.rocketmq.studio.persistence.mapper.RmqGroupMapper;
import org.apache.rocketmq.studio.persistence.mapper.RmqTopicMapper;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@@ -383,11 +384,21 @@ public class RocketMQAdminClientImpl implements
AdminClient {
if (topic.getType() != null) {
existing.setTopicType(topic.getType().name());
}
- if (StringUtils.hasText(topic.getRemark())) {
- existing.setRemark(topic.getRemark());
+ // A null remark was not submitted and keeps the stored
value; a submitted blank
+ // remark is an explicit clear, so it must persist as an
absent remark.
+ boolean clearRemark = topic.getRemark() != null &&
!StringUtils.hasText(topic.getRemark());
+ if (topic.getRemark() != null) {
+ existing.setRemark(clearRemark ? null :
topic.getRemark());
}
existing.setGmtModified(LocalDateTime.now());
topicMapper.updateById(existing);
+ if (clearRemark) {
+ // updateById omits null entity fields, so the cleared
remark has to be
+ // assigned explicitly instead of silently retaining
the stored value.
+ topicMapper.update(null, new UpdateWrapper<RmqTopic>()
+ .eq("id", existing.getId())
+ .set("remark", null));
+ }
}
recordAudit("UPDATE_TOPIC", topicName,
@@ -396,6 +407,11 @@ public class RocketMQAdminClientImpl implements
AdminClient {
topic.setId(existing == null ? null : existing.getId());
topic.setWriteQueues(writeQueues);
topic.setReadQueues(readQueues);
+ if (existing != null) {
+ // Report the persisted remark so a clear or an omitted
remark cannot be
+ // mistaken for a value the update did not write.
+ topic.setRemark(existing.getRemark());
+ }
return topic;
} catch (BusinessException e) {
recordAudit("UPDATE_TOPIC", topicName, e.getMessage(),
"FAILED");
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
index b07bddb62..487cc16ac 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
@@ -12,6 +12,7 @@ package org.apache.rocketmq.studio.provider.apache;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.rocketmq.client.exception.MQBrokerException;
@@ -704,6 +705,56 @@ class RocketMQAdminClientImplTest {
verify(topicMapper).updateById(existing);
}
+ @Test
+ void updateTopicClearsTheStoredRemarkWhenTheRequestSubmitsABlankRemark()
throws Exception {
+ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new
MybatisConfiguration(), ""), RmqTopic.class);
+ RmqTopic existing = new RmqTopic();
+ existing.setId(7L);
+ existing.setTopicType(TopicType.NORMAL.name());
+ existing.setRemark("old remark");
+ existing.setWriteQueueNums(8);
+ existing.setReadQueueNums(8);
+
when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfoWithMaster());
+ when(topicMapper.selectOne(any())).thenReturn(existing);
+ when(topicMapper.update(isNull(),
any(UpdateWrapper.class))).thenReturn(1);
+
+ TopicVO topic = new TopicVO();
+ topic.setName("orders");
+ topic.setRemark("");
+
+ TopicVO updated = adminClient.updateTopic(topic);
+
+ @SuppressWarnings("rawtypes")
+ ArgumentCaptor<UpdateWrapper> captor =
ArgumentCaptor.forClass(UpdateWrapper.class);
+ verify(topicMapper).update(isNull(), captor.capture());
+ assertThat(captor.getValue().getSqlSet()).contains("remark");
+
assertThat(captor.getValue().getParamNameValuePairs()).containsValue(null);
+ assertThat(existing.getRemark()).isNull();
+ assertThat(updated.getRemark()).isNull();
+ }
+
+ @Test
+ void updateTopicKeepsTheStoredRemarkWhenTheRequestOmitsIt() throws
Exception {
+ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new
MybatisConfiguration(), ""), RmqTopic.class);
+ RmqTopic existing = new RmqTopic();
+ existing.setId(7L);
+ existing.setTopicType(TopicType.NORMAL.name());
+ existing.setRemark("old remark");
+ existing.setWriteQueueNums(8);
+ existing.setReadQueueNums(8);
+
when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfoWithMaster());
+ when(topicMapper.selectOne(any())).thenReturn(existing);
+
+ TopicVO topic = new TopicVO();
+ topic.setName("orders");
+
+ TopicVO updated = adminClient.updateTopic(topic);
+
+ verify(topicMapper, never()).update(any(), any());
+ assertThat(existing.getRemark()).isEqualTo("old remark");
+ assertThat(updated.getRemark()).isEqualTo("old remark");
+ }
+
@Test
void topicWritesSendMessageTypeAttributeToBroker() throws Exception {
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new
MybatisConfiguration(), ""), RmqTopic.class);