Skip to content

Commit fd60bda

Browse files
authored
Text search for topic messages (#62)
* Text search for topic messages * Code optimization
1 parent 32a09fa commit fd60bda

File tree

4 files changed

+47
-7
lines changed

4 files changed

+47
-7
lines changed

kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/service/ClusterService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ private <T> Mono<T> updateCluster(T topic, String clusterName, KafkaCluster clus
147147
});
148148
}
149149

150-
public Flux<TopicMessage> getMessages(String clusterName, String topicName, ConsumerPosition consumerPosition, Integer limit) {
150+
public Flux<TopicMessage> getMessages(String clusterName, String topicName, ConsumerPosition consumerPosition, String query, Integer limit) {
151151
return clustersStorage.getClusterByName(clusterName)
152-
.map(c -> consumingService.loadMessages(c, topicName, consumerPosition, limit))
152+
.map(c -> consumingService.loadMessages(c, topicName, consumerPosition, query, limit))
153153
.orElse(Flux.empty());
154154

155155
}

kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/service/ConsumingService.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@
44
import lombok.extern.log4j.Log4j2;
55

66
import java.time.Duration;
7+
import java.util.LinkedList;
78
import java.util.List;
89
import java.util.Map;
910
import java.util.Optional;
1011
import java.util.stream.Collectors;
1112

13+
import org.apache.commons.lang3.StringUtils;
1214
import org.apache.kafka.clients.consumer.ConsumerRecord;
1315
import org.apache.kafka.clients.consumer.ConsumerRecords;
1416
import org.apache.kafka.clients.consumer.KafkaConsumer;
1517
import org.apache.kafka.common.TopicPartition;
1618
import org.apache.kafka.common.utils.Bytes;
1719
import org.springframework.stereotype.Service;
1820

21+
import com.fasterxml.jackson.databind.JsonNode;
22+
import com.fasterxml.jackson.databind.ObjectMapper;
1923
import com.provectus.kafka.ui.cluster.deserialization.DeserializationService;
2024
import com.provectus.kafka.ui.cluster.deserialization.RecordDeserializer;
2125
import com.provectus.kafka.ui.cluster.model.ConsumerPosition;
@@ -36,12 +40,12 @@ public class ConsumingService {
3640

3741
private static final int MAX_RECORD_LIMIT = 100;
3842
private static final int DEFAULT_RECORD_LIMIT = 20;
39-
private static final int MAX_POLLS_COUNT = 30;
4043

4144
private final KafkaService kafkaService;
4245
private final DeserializationService deserializationService;
46+
private final ObjectMapper objectMapper = new ObjectMapper();
4347

44-
public Flux<TopicMessage> loadMessages(KafkaCluster cluster, String topic, ConsumerPosition consumerPosition, Integer limit) {
48+
public Flux<TopicMessage> loadMessages(KafkaCluster cluster, String topic, ConsumerPosition consumerPosition, String query, Integer limit) {
4549
int recordsLimit = Optional.ofNullable(limit)
4650
.map(s -> Math.min(s, MAX_RECORD_LIMIT))
4751
.orElse(DEFAULT_RECORD_LIMIT);
@@ -50,12 +54,44 @@ public Flux<TopicMessage> loadMessages(KafkaCluster cluster, String topic, Consu
5054
return Flux.create(emitter::emit)
5155
.subscribeOn(Schedulers.boundedElastic())
5256
.map(r -> ClusterUtil.mapToTopicMessage(r, recordDeserializer))
57+
.filter(m -> filterTopicMessage(m, query))
5358
.limitRequest(recordsLimit);
5459
}
5560

61+
private boolean filterTopicMessage(TopicMessage message, String query) {
62+
if (StringUtils.isEmpty(query)) {
63+
return true;
64+
}
65+
66+
Object content = message.getContent();
67+
JsonNode tree = objectMapper.valueToTree(content);
68+
return treeContainsValue(tree, query);
69+
}
70+
71+
private boolean treeContainsValue(JsonNode tree, String query) {
72+
LinkedList<JsonNode> nodesForSearch = new LinkedList<>();
73+
nodesForSearch.add(tree);
74+
75+
while (!nodesForSearch.isEmpty()) {
76+
JsonNode node = nodesForSearch.removeFirst();
77+
78+
if (node.isContainerNode()) {
79+
node.elements().forEachRemaining(nodesForSearch::add);
80+
continue;
81+
}
82+
83+
String nodeValue = node.asText();
84+
if (nodeValue.contains(query)) {
85+
return true;
86+
}
87+
}
88+
89+
return false;
90+
}
91+
5692
@RequiredArgsConstructor
5793
private static class RecordEmitter {
58-
94+
private static final int MAX_POLLS_COUNT = 30;
5995
private static final Duration POLL_TIMEOUT_MS = Duration.ofMillis(1000L);
6096

6197
private final KafkaService kafkaService;

kafka-ui-api/src/main/java/com/provectus/kafka/ui/rest/MetricsRestController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ public Mono<ResponseEntity<Flux<TopicConfig>>> getTopicConfigs(String clusterNam
6565
}
6666

6767
@Override
68-
public Mono<ResponseEntity<Flux<TopicMessage>>> getTopicMessages(String clusterName, String topicName, @Valid SeekType seekType, @Valid List<String> seekTo, @Valid Integer limit, ServerWebExchange exchange) {
68+
public Mono<ResponseEntity<Flux<TopicMessage>>> getTopicMessages(String clusterName, String topicName, @Valid SeekType seekType, @Valid List<String> seekTo, @Valid Integer limit, @Valid String q, ServerWebExchange exchange) {
6969
return parseConsumerPosition(seekType, seekTo)
70-
.map(consumerPosition -> ResponseEntity.ok(clusterService.getMessages(clusterName, topicName, consumerPosition, limit)));
70+
.map(consumerPosition -> ResponseEntity.ok(clusterService.getMessages(clusterName, topicName, consumerPosition, q, limit)));
7171
}
7272

7373
@Override

kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ paths:
229229
in: query
230230
schema:
231231
type: integer
232+
- name: q
233+
in: query
234+
schema:
235+
type: string
232236
responses:
233237
200:
234238
description: OK

0 commit comments

Comments
 (0)