Skip to content

Commit e4abfd5

Browse files
authored
ISSUE-111 - More gracefully handle protobuf serialization (#113)
* Fix missing dependency * [ISSUE-111] properly register protobuf serializer, add ability for UI to render JSON * minor cleanup
1 parent 1620213 commit e4abfd5

File tree

9 files changed

+156
-18
lines changed

9 files changed

+156
-18
lines changed

kafka-webview-ui/pom.xml

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@
171171
<artifactId>guava</artifactId>
172172
<version>27.0.1-jre</version>
173173
</dependency>
174+
<!-- Explicitly include updated protocol buffer version -->
175+
<dependency>
176+
<groupId>com.google.protobuf</groupId>
177+
<artifactId>protobuf-java</artifactId>
178+
<version>3.6.1</version>
179+
</dependency>
174180

175181
<!-- For tests -->
176182
<dependency>
@@ -217,14 +223,6 @@
217223
<scope>test</scope>
218224
</dependency>
219225

220-
<!-- Test case for validating protocol buffer support in Jackson -->
221-
<dependency>
222-
<groupId>com.google.protobuf</groupId>
223-
<artifactId>protobuf-java</artifactId>
224-
<version>3.6.1</version>
225-
<scope>test</scope>
226-
</dependency>
227-
228226
<!-- Embedded LDAP server for tests -->
229227
<dependency>
230228
<groupId>org.zapodot</groupId>

kafka-webview-ui/src/main/frontend/gulp-tasks/build-dist.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var vendorsJS = [
4040
'node_modules/pace-progress/pace.min.js',
4141
'node_modules/popper.js/dist/umd/popper.min.js',
4242
'node_modules/popper.js/dist/umd/popper.min.js.map',
43+
'node_modules/renderjson/renderjson.js',
4344
'node_modules/select2/dist/js/select2.min.js',
4445
'node_modules/toastr/toastr.js',
4546
'node_modules/handlebars/dist/handlebars.js',

kafka-webview-ui/src/main/frontend/package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

kafka-webview-ui/src/main/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"multiselect-two-sides": "^2.4.0",
1515
"pace-progress": "^1.0.2",
1616
"popper.js": "^1.12.5",
17+
"renderjson": "^1.4.0",
1718
"simple-line-icons": "^2.4.1",
1819
"sockjs-client": "^1.3.0",
1920
"stompjs": "^2.3.3"

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/configuration/PluginConfig.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
package org.sourcelab.kafka.webview.ui.configuration;
2626

27+
import com.fasterxml.jackson.databind.ObjectMapper;
2728
import com.hubspot.jackson.datatype.protobuf.ProtobufModule;
2829
import org.apache.kafka.common.serialization.Deserializer;
2930
import org.sourcelab.kafka.webview.ui.manager.encryption.SecretManager;
@@ -36,6 +37,7 @@
3637
import org.sourcelab.kafka.webview.ui.manager.plugin.UploadManager;
3738
import org.sourcelab.kafka.webview.ui.manager.sasl.SaslUtility;
3839
import org.sourcelab.kafka.webview.ui.plugin.filter.RecordFilter;
40+
import org.springframework.beans.factory.annotation.Autowired;
3941
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
4042
import org.springframework.context.annotation.Bean;
4143
import org.springframework.stereotype.Component;
@@ -122,12 +124,17 @@ public KafkaOperationsFactory getKafkaOperationsFactory(final AppProperties appP
122124
* @return Jackson2ObjectMapperBuilderCustomizer instance.
123125
*/
124126
@Bean
125-
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
127+
public Jackson2ObjectMapperBuilderCustomizer registerJacksonProtobufModule() {
126128
return jacksonObjectMapperBuilder -> {
127-
// Register custom protocol buffer serializer as protocol buffers is a common serialization format.
129+
// Register custom protocol buffer serializer.
128130
jacksonObjectMapperBuilder.modulesToInstall(new ProtobufModule());
129131
};
130132
}
133+
134+
@Autowired(required = true)
135+
public void configureJackson(ObjectMapper jackson2ObjectMapper) {
136+
jackson2ObjectMapper.registerModule(new ProtobufModule());
137+
}
131138

132139
/**
133140
* For creating instances of AdminClient.

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/configuration/WebConfig.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,18 @@
2424

2525
package org.sourcelab.kafka.webview.ui.configuration;
2626

27+
import com.fasterxml.jackson.databind.ObjectMapper;
28+
import com.hubspot.jackson.datatype.protobuf.ProtobufModule;
2729
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.http.converter.HttpMessageConverter;
31+
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
32+
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
2833
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
2934
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
3035
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
3136

37+
import java.util.List;
38+
3239
/**
3340
* Application configuration for Web resources.
3441
*/
@@ -57,4 +64,20 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) {
5764
.addResourceHandler("/img/**")
5865
.addResourceLocations("classpath:/static/img/");
5966
}
67+
68+
/**
69+
* Ensure that ProtobufModule is registered so protobufs can easily be serialized.
70+
* @param converters list of converters registered.
71+
*/
72+
@Override
73+
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
74+
converters.clear();
75+
76+
final ObjectMapper mapper = Jackson2ObjectMapperBuilder
77+
.json()
78+
.modulesToInstall(new ProtobufModule())
79+
.build();
80+
converters.add(new MappingJackson2HttpMessageConverter(mapper));
81+
}
82+
6083
}

kafka-webview-ui/src/main/resources/templates/layout.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
<script src="/vendors/js/jquery-ui.min.js"></script>
5454
<script src="/vendors/js/jquery-ui-timepicker-addon.min.js"></script>
5555

56+
<!-- JSON Rendering -->
57+
<script src="/vendors/js/renderjson.min.js"></script>
58+
5659
<!-- Websocket -->
5760
<script src="/vendors/js/sockjs.min.js"></script>
5861
<script src="/vendors/js/stomp.min.js"></script>

kafka-webview-ui/src/main/resources/templates/stream/index.html

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ <h4 class="alert-heading">Stream Disconnected</h4>
109109
// End point to consume a view
110110
consumerBaseUrl: "/user/topic/view",
111111

112+
// JSON Render settings.
113+
useJsonRender: true,
114+
jsonRenderToLevel: 1000,
115+
112116
getConsumerUrl : function() {
113117
return SocketDetails.consumerBaseUrl + '/' + SocketDetails.viewId + '/' + SocketDetails.userId;
114118
},
@@ -245,12 +249,44 @@ <h4 class="alert-heading">Stream Disconnected</h4>
245249

246250
// Append results to browser
247251
appendResult: function(result) {
252+
// Determine how to render key
253+
var keyIsJson = false;
254+
var key = result.key;
255+
256+
// Determine if result.value is a string, or JSON object
257+
if (result.key instanceof Object && !(result.key instanceof String)) {
258+
// Use fancy json rendering.
259+
if (SocketDetails.useJsonRender) {
260+
keyIsJson = true;
261+
} else {
262+
// Convert to String
263+
key = JSON.stringify(result.key);
264+
}
265+
}
266+
267+
// Determine how to render value
268+
var valueIsJson = false;
269+
var value = result.value;
270+
271+
// Determine if result.value is a string, or JSON object
272+
if (result.value instanceof Object && !(result.value instanceof String)) {
273+
// Use fancy json rendering.
274+
if (SocketDetails.useJsonRender) {
275+
valueIsJson = true;
276+
} else {
277+
// Convert to String
278+
value = JSON.stringify(result.value);
279+
}
280+
}
281+
248282
// Generate html from template
249283
var properties = {
250284
partition: result.partition,
251285
offset: result.offset,
252-
key: result.key,
253-
value: result.value,
286+
key: key,
287+
keyIsJson: keyIsJson,
288+
value: value,
289+
valueIsJson: valueIsJson,
254290
timestamp: result.timestamp,
255291
date: DateTools.displayTimestamp(result.timestamp),
256292
timezone: DateTools.localTimezone,
@@ -261,6 +297,14 @@ <h4 class="alert-heading">Stream Disconnected</h4>
261297
// Prepend it to the top of our table
262298
SocketDetails.resultsTable.prepend(resultHtml);
263299

300+
// If json, generate formatted json
301+
if (keyIsJson) {
302+
jQuery("td#key-" + result.partition + "-" + result.offset).html(renderjson(result.key));
303+
}
304+
if (valueIsJson) {
305+
jQuery("td#value-" + result.partition + "-" + result.offset).html(renderjson(result.value));
306+
}
307+
264308
// Remove rows > max results from the end
265309
var resultRows = SocketDetails.resultsTable.children();
266310
if (resultRows.length > SocketDetails.maxResults) {
@@ -294,6 +338,8 @@ <h4 class="alert-heading">Stream Disconnected</h4>
294338
* for the selected value.
295339
*/
296340
jQuery('#startPositionSelector').change(function(event) {
341+
renderjson.set_show_to_level(SocketDetails.jsonRenderToLevel);
342+
297343
var selected = jQuery(this).val();
298344

299345
// Hide all options
@@ -320,8 +366,12 @@ <h4 class="alert-heading">Stream Disconnected</h4>
320366
<span class="timestamp" title="{{date}}">{{timestamp}}</span>
321367
{{/if}}
322368
</td>
323-
<td><pre>{{key}}</pre></td>
324-
<td><pre>{{value}}</pre></td>
369+
<td id="key-{{partition}}-{{offset}}">
370+
{{#unless keyIsJson }}<pre>{{key}}</pre>{{/unless}}
371+
</td>
372+
<td id="value-{{partition}}-{{offset}}">
373+
{{#unless valueIsJson }}<pre>{{value}}</pre>{{/unless}}
374+
</td>
325375
</tr>
326376
</script>
327377
</section>

kafka-webview-ui/src/main/resources/templates/view/consume.html

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,16 @@
2323
viewId: '[[${view.id}]]',
2424
formatDates: true,
2525
resultsPerPartition: '[[${view.resultsPerPartition}]]',
26+
27+
// JSON Render settings.
28+
useJsonRender: true,
29+
jsonRenderToLevel: 1000
2630
};
2731

2832
// On load, fire off ajax request to load results.
2933
jQuery(document).ready(function() {
34+
renderjson.set_show_to_level(ConsumerInfo.jsonRenderToLevel);
35+
3036
// Fire off initial consume request
3137
executeConsume('next');
3238

@@ -160,12 +166,44 @@
160166

161167
// Loop over results
162168
jQuery.each(results.results, function (index, result) {
169+
// Determine how to render key
170+
var keyIsJson = false;
171+
var key = result.key;
172+
173+
// Determine if result.value is a string, or JSON object
174+
if (result.key instanceof Object && !(result.key instanceof String)) {
175+
// Use fancy json rendering.
176+
if (ConsumerInfo.useJsonRender) {
177+
keyIsJson = true;
178+
} else {
179+
// Convert to String
180+
key = JSON.stringify(result.key);
181+
}
182+
}
183+
184+
// Determine how to render value
185+
var valueIsJson = false;
186+
var value = result.value;
187+
188+
// Determine if result.value is a string, or JSON object
189+
if (result.value instanceof Object && !(result.value instanceof String)) {
190+
// Use fancy json rendering.
191+
if (ConsumerInfo.useJsonRender) {
192+
valueIsJson = true;
193+
} else {
194+
// Convert to String
195+
value = JSON.stringify(result.value);
196+
}
197+
}
198+
163199
// Generate html from template
164200
var properties = {
165201
partition: result.partition,
166202
offset: result.offset,
167-
key: result.key,
168-
value: result.value,
203+
key: key,
204+
keyIsJson: keyIsJson,
205+
value: value,
206+
valueIsJson: valueIsJson,
169207
timestamp: result.timestamp,
170208
date: DateTools.displayTimestamp(result.timestamp),
171209
timezone: DateTools.localTimezone,
@@ -175,6 +213,14 @@
175213

176214
// Append it to our table
177215
jQuery(table).append(resultHtml);
216+
217+
// If json, generate formatted json
218+
if (keyIsJson) {
219+
jQuery("td#key-" + result.partition + "-" + result.offset).html(renderjson(result.key));
220+
}
221+
if (valueIsJson) {
222+
jQuery("td#value-" + result.partition + "-" + result.offset).html(renderjson(result.value));
223+
}
178224
});
179225

180226
// Hide loader
@@ -393,8 +439,12 @@ <h4 class="alert-heading">No Results Found</h4>
393439
<span class="timestamp" title="{{date}}">{{timestamp}}</span>
394440
{{/if}}
395441
</td>
396-
<td><pre>{{key}}</pre></td>
397-
<td><pre>{{value}}</pre></td>
442+
<td id="key-{{partition}}-{{offset}}">
443+
{{#unless keyIsJson }}<pre>{{key}}</pre>{{/unless}}
444+
</td>
445+
<td id="value-{{partition}}-{{offset}}">
446+
{{#unless isJson }}<pre>{{value}}</pre>{{/unless}}
447+
</td>
398448
</tr>
399449
</script>
400450
</div>

0 commit comments

Comments
 (0)