Skip to content

Commit 530fba7

Browse files
authored
Add OO based datatable rendering support (#219)
* wip datatable * wip for datatables * wip * wip * Add URL building * add search * wire up filters * work in progress, get spec api going * get search and filter params working * start with no results found page * start cleaning up * work on pager * finish pager * cleanup * update reference template * start cleaning up * cleanup * final cleanup * cleanup and fix test * fix failing test * Update Clusters datatable to use new framework * fix test cases * fix test cases * update changelog
2 parents 68316a7 + 1139a4e commit 530fba7

31 files changed

+1606
-152
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
The format is based on [Keep a Changelog](http://keepachangelog.com/)
33
and this project adheres to [Semantic Versioning](http://semver.org/).
44

5-
## 2.6.0 (UNRELEASED)
5+
## 2.6.0 (06/21/2020)
66
- [ISSUE-144](https://github.com/SourceLabOrg/kafka-webview/issues/144) Make providing a TrustStore file when setting up a SSL enabled cluster optional. You might not want/need this option if your JVM is already configured to accept the SSL certificate served by the cluster, or if the cluster's certificate can be validated by a publically accessible CA.
77
- [PR-215](https://github.com/SourceLabOrg/kafka-webview/pull/215) Improve errors displayed when using the `test cluster` functionality.
8-
- [PR-220](https://github.com/SourceLabOrg/kafka-webview/pull/220) Usernames/email addresses for locally defined users are no longer case-insensitive.
8+
- [PR-219](https://github.com/SourceLabOrg/kafka-webview/pull/219) Improve datatables for /cluster and /view to include paging, sorting, and filtering.
9+
- [PR-220](https://github.com/SourceLabOrg/kafka-webview/pull/220) Usernames/email addresses for locally defined users while logging in are no longer case-sensitive.
910

1011
## 2.5.1 (05/19/2020)
1112
- [ISSUE-209](https://github.com/SourceLabOrg/kafka-webview/issues/209) Expose HealthCheck and App Info endpoints without requiring authentication.

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ FROM openjdk:8-jre-alpine
55
MAINTAINER SourceLab.org <stephen.powis@gmail.com>
66

77
## Define what version of Kafka Webview to build the image using.
8-
ENV WEBVIEW_VER="2.5.0" \
9-
WEBVIEW_SHA1="00a8db474ba2c584c5a473c8ca9acbd0259c01de" \
8+
ENV WEBVIEW_VER="2.5.1" \
9+
WEBVIEW_SHA1="f36027aa489ae08975b84882641e3fcd6f8102f6" \
1010
WEBVIEW_HOME="/app"
1111

1212
# Create app and data directories

checkstyle.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
<property name="throwsIndent" value="4"/>
137137
<property name="arrayInitIndent" value="4"/>
138138
<property name="lineWrappingIndentation" value="4"/>
139-
<property name="forceStrictCondition" value="true"/>
139+
<property name="forceStrictCondition" value="false"/>
140140
</module>
141141
<module name="AbbreviationAsWordInName">
142142
<property name="ignoreFinal" value="false"/>

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/cluster/ClusterController.java

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,23 @@
2727
import org.sourcelab.kafka.webview.ui.controller.BaseController;
2828
import org.sourcelab.kafka.webview.ui.manager.ui.BreadCrumbManager;
2929
import org.sourcelab.kafka.webview.ui.manager.ui.FlashMessage;
30+
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.Datatable;
31+
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.DatatableColumn;
32+
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.DatatableFilter;
33+
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.LinkTemplate;
34+
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.YesNoBadgeTemplate;
3035
import org.sourcelab.kafka.webview.ui.model.Cluster;
36+
import org.sourcelab.kafka.webview.ui.model.View;
3137
import org.sourcelab.kafka.webview.ui.repository.ClusterRepository;
3238
import org.sourcelab.kafka.webview.ui.repository.ViewRepository;
3339
import org.springframework.beans.factory.annotation.Autowired;
40+
import org.springframework.data.domain.Pageable;
3441
import org.springframework.stereotype.Controller;
3542
import org.springframework.ui.Model;
3643
import org.springframework.web.bind.annotation.PathVariable;
3744
import org.springframework.web.bind.annotation.RequestMapping;
3845
import org.springframework.web.bind.annotation.RequestMethod;
46+
import org.springframework.web.bind.annotation.RequestParam;
3947
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
4048

4149
import java.util.HashMap;
@@ -59,23 +67,65 @@ public class ClusterController extends BaseController {
5967
* GET Displays cluster list.
6068
*/
6169
@RequestMapping(path = "", method = RequestMethod.GET)
62-
public String clusterIndex(final Model model, final RedirectAttributes redirectAttributes) {
70+
public String datatable(
71+
final Model model,
72+
final Pageable pageable,
73+
@RequestParam Map<String,String> allParams
74+
) {
6375
// Setup breadcrumbs
6476
final BreadCrumbManager manager = new BreadCrumbManager(model);
6577
manager.addCrumb("Cluster Explorer", null);
6678

67-
// Retrieve all clusters
68-
final Iterable<Cluster> clusterList = clusterRepository.findAllByOrderByNameAsc();
69-
model.addAttribute("clusterList", clusterList);
79+
// Global flag if we have no clusters at all.
80+
model.addAttribute("hasNoClusters", false);
81+
model.addAttribute("hasClusters", true);
82+
if (clusterRepository.count() == 0) {
83+
model.addAttribute("hasNoClusters", true);
84+
model.addAttribute("hasClusters", false);
85+
}
7086

7187
// Retrieve how many views for each cluster
7288
final Map<Long, Long> viewsByClusterId = new HashMap<>();
73-
for (final Cluster cluster: clusterList) {
89+
for (final Cluster cluster: clusterRepository.findAll()) {
7490
final Long clusterId = cluster.getId();
7591
final Long count = viewRepository.countByClusterId(cluster.getId());
7692
viewsByClusterId.put(clusterId, count);
7793
}
78-
model.addAttribute("viewsByClusterId", viewsByClusterId);
94+
95+
final Datatable.Builder<Cluster> builder = Datatable.newBuilder(Cluster.class)
96+
.withRepository(clusterRepository)
97+
.withPageable(pageable)
98+
.withRequestParams(allParams)
99+
.withUrl("/cluster")
100+
.withLabel("Kafka Clusters")
101+
.withColumn(DatatableColumn.newBuilder(Cluster.class)
102+
.withFieldName("name")
103+
.withLabel("Cluster")
104+
.withRenderTemplate(new LinkTemplate<>(
105+
(record) -> "/cluster/" + record.getId(),
106+
Cluster::getName
107+
))
108+
.withIsSortable(true)
109+
.build())
110+
.withColumn(DatatableColumn.newBuilder(Cluster.class)
111+
.withFieldName("id")
112+
.withLabel("Views")
113+
.withRenderTemplate(new LinkTemplate<>(
114+
(record) -> "/view?cluster.id=" + record.getId(),
115+
(record) -> viewsByClusterId.computeIfAbsent(record.getId(), (key) -> 0L) + " Views"
116+
))
117+
.withIsSortable(false)
118+
.build())
119+
.withColumn(DatatableColumn.newBuilder(Cluster.class)
120+
.withFieldName("isSslEnabled")
121+
.withLabel("SSL")
122+
.withRenderTemplate(new YesNoBadgeTemplate<>(Cluster::isSslEnabled))
123+
.withIsSortable(true)
124+
.build())
125+
.withSearch("name");
126+
127+
// Add datatable attribute
128+
model.addAttribute("datatable", builder.build());
79129

80130
// Display template
81131
return "cluster/index";

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/view/ViewController.java

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,17 @@
2727
import org.sourcelab.kafka.webview.ui.controller.BaseController;
2828
import org.sourcelab.kafka.webview.ui.manager.ui.BreadCrumbManager;
2929
import org.sourcelab.kafka.webview.ui.manager.ui.FlashMessage;
30+
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.Datatable;
31+
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.DatatableColumn;
32+
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.DatatableFilter;
33+
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.LinkTemplate;
3034
import org.sourcelab.kafka.webview.ui.model.Cluster;
3135
import org.sourcelab.kafka.webview.ui.model.View;
3236
import org.sourcelab.kafka.webview.ui.repository.ClusterRepository;
37+
import org.sourcelab.kafka.webview.ui.repository.MessageFormatRepository;
3338
import org.sourcelab.kafka.webview.ui.repository.ViewRepository;
3439
import org.springframework.beans.factory.annotation.Autowired;
40+
import org.springframework.data.domain.Pageable;
3541
import org.springframework.stereotype.Controller;
3642
import org.springframework.ui.Model;
3743
import org.springframework.web.bind.annotation.PathVariable;
@@ -40,8 +46,11 @@
4046
import org.springframework.web.bind.annotation.RequestParam;
4147
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
4248

49+
import java.util.ArrayList;
4350
import java.util.HashMap;
51+
import java.util.List;
4452
import java.util.Map;
53+
import java.util.Objects;
4554
import java.util.Optional;
4655

4756
/**
@@ -50,42 +59,42 @@
5059
@Controller
5160
@RequestMapping("/view")
5261
public class ViewController extends BaseController {
53-
@Autowired
54-
private ViewRepository viewRepository;
5562

63+
private final ViewRepository viewRepository;
64+
private final ClusterRepository clusterRepository;
65+
66+
/**
67+
* Constructor.
68+
*/
5669
@Autowired
57-
private ClusterRepository clusterRepository;
70+
public ViewController(
71+
final ViewRepository viewRepository,
72+
final ClusterRepository clusterRepository
73+
) {
74+
this.viewRepository = Objects.requireNonNull(viewRepository);
75+
this.clusterRepository = Objects.requireNonNull(clusterRepository);
76+
}
5877

5978
/**
6079
* GET views index.
6180
*/
6281
@RequestMapping(path = "", method = RequestMethod.GET)
63-
public String index(
82+
public String datatable(
6483
final Model model,
65-
@RequestParam(name = "clusterId", required = false) final Long clusterId
84+
@RequestParam(name = "cluster.id", required = false) final Long clusterId,
85+
final Pageable pageable,
86+
@RequestParam Map<String,String> allParams
6687
) {
6788
// Setup breadcrumbs
6889
final BreadCrumbManager breadCrumbManager = new BreadCrumbManager(model);
6990

91+
// Determine if we actually have any clusters setup
7092
// Retrieve all clusters and index by id
7193
final Map<Long, Cluster> clustersById = new HashMap<>();
7294
clusterRepository
7395
.findAllByOrderByNameAsc()
7496
.forEach((cluster) -> clustersById.put(cluster.getId(), cluster));
7597

76-
final Iterable<View> views;
77-
if (clusterId == null) {
78-
// Retrieve all views order by name asc.
79-
views = viewRepository.findAllByOrderByNameAsc();
80-
} else {
81-
// Retrieve only views for the cluster
82-
views = viewRepository.findAllByClusterIdOrderByNameAsc(clusterId);
83-
}
84-
85-
// Set model Attributes
86-
model.addAttribute("viewList", views);
87-
model.addAttribute("clustersById", clustersById);
88-
8998
final String clusterName;
9099
if (clusterId != null && clustersById.containsKey(clusterId)) {
91100
// If filtered by a cluster
@@ -104,14 +113,71 @@ public String index(
104113
}
105114
model.addAttribute("clusterName", clusterName);
106115

116+
// Create a filter
117+
final List<DatatableFilter.FilterOption> filterOptions = new ArrayList<>();
118+
clustersById
119+
.forEach((id, cluster) -> filterOptions.add(new DatatableFilter.FilterOption(String.valueOf(id), cluster.getName())));
120+
final DatatableFilter filter = new DatatableFilter("Cluster", "clusterId", filterOptions);
121+
model.addAttribute("filters", new DatatableFilter[] { filter });
122+
123+
final Datatable.Builder<View> builder = Datatable.newBuilder(View.class)
124+
.withRepository(viewRepository)
125+
.withPageable(pageable)
126+
.withRequestParams(allParams)
127+
.withUrl("/view")
128+
.withLabel("Views")
129+
.withColumn(DatatableColumn.newBuilder(View.class)
130+
.withFieldName("name")
131+
.withLabel("View")
132+
.withRenderFunction((View::getName))
133+
.build())
134+
.withColumn(DatatableColumn.newBuilder(View.class)
135+
.withFieldName("topic")
136+
.withLabel("Topic")
137+
.withRenderFunction(View::getTopic)
138+
.build())
139+
.withColumn(DatatableColumn.newBuilder(View.class)
140+
.withFieldName("cluster.name")
141+
.withLabel("Cluster")
142+
.withRenderTemplate(new LinkTemplate<>(
143+
(record) -> "/cluster/" + record.getCluster().getId(),
144+
(record) -> record.getCluster().getName()
145+
)).build())
146+
.withColumn(DatatableColumn.newBuilder(View.class)
147+
.withLabel("")
148+
.withFieldName("")
149+
.withIsSortable(false)
150+
.withRenderTemplate(new LinkTemplate<>(
151+
(record) -> "/view/" + record.getId(),
152+
(record) -> "Browse"
153+
)).build())
154+
.withColumn(DatatableColumn.newBuilder(View.class)
155+
.withLabel("")
156+
.withFieldName("")
157+
.withIsSortable(false)
158+
.withRenderTemplate(new LinkTemplate<>(
159+
(record) -> "/stream/" + record.getId(),
160+
(record) -> "Stream"
161+
)).build())
162+
.withFilter(new DatatableFilter("Cluster", "cluster.id", filterOptions))
163+
.withSearch("name");
164+
165+
// Add datatable attribute
166+
model.addAttribute("datatable", builder.build());
167+
168+
// Determine if we have no clusters setup so we can show appropriate inline help.
169+
model.addAttribute("hasClusters", !clustersById.isEmpty());
170+
model.addAttribute("hasNoClusters", clustersById.isEmpty());
171+
model.addAttribute("hasViews", viewRepository.count() > 0);
172+
107173
return "view/index";
108174
}
109175

110176
/**
111177
* GET Displays view for specified view.
112178
*/
113179
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
114-
public String index(
180+
public String view(
115181
@PathVariable final Long id,
116182
final RedirectAttributes redirectAttributes,
117183
final Model model) {

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/kafka/KafkaOperations.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import org.apache.kafka.clients.admin.TopicListing;
4242
import org.apache.kafka.clients.consumer.KafkaConsumer;
4343
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
44-
import org.apache.kafka.common.KafkaException;
4544
import org.apache.kafka.common.Node;
4645
import org.apache.kafka.common.TopicPartitionInfo;
4746
import org.apache.kafka.common.config.ConfigResource;

0 commit comments

Comments
 (0)