Skip to content

Commit

Permalink
Add OO based datatable rendering support (#219)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Crim authored Jun 21, 2020
2 parents 68316a7 + 1139a4e commit 530fba7
Show file tree
Hide file tree
Showing 31 changed files with 1,606 additions and 152 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## 2.6.0 (UNRELEASED)
## 2.6.0 (06/21/2020)
- [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.
- [PR-215](https://github.com/SourceLabOrg/kafka-webview/pull/215) Improve errors displayed when using the `test cluster` functionality.
- [PR-220](https://github.com/SourceLabOrg/kafka-webview/pull/220) Usernames/email addresses for locally defined users are no longer case-insensitive.
- [PR-219](https://github.com/SourceLabOrg/kafka-webview/pull/219) Improve datatables for /cluster and /view to include paging, sorting, and filtering.
- [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.

## 2.5.1 (05/19/2020)
- [ISSUE-209](https://github.com/SourceLabOrg/kafka-webview/issues/209) Expose HealthCheck and App Info endpoints without requiring authentication.
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ FROM openjdk:8-jre-alpine
MAINTAINER SourceLab.org <[email protected]>

## Define what version of Kafka Webview to build the image using.
ENV WEBVIEW_VER="2.5.0" \
WEBVIEW_SHA1="00a8db474ba2c584c5a473c8ca9acbd0259c01de" \
ENV WEBVIEW_VER="2.5.1" \
WEBVIEW_SHA1="f36027aa489ae08975b84882641e3fcd6f8102f6" \
WEBVIEW_HOME="/app"

# Create app and data directories
Expand Down
2 changes: 1 addition & 1 deletion checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
<property name="throwsIndent" value="4"/>
<property name="arrayInitIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="forceStrictCondition" value="true"/>
<property name="forceStrictCondition" value="false"/>
</module>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,23 @@
import org.sourcelab.kafka.webview.ui.controller.BaseController;
import org.sourcelab.kafka.webview.ui.manager.ui.BreadCrumbManager;
import org.sourcelab.kafka.webview.ui.manager.ui.FlashMessage;
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.Datatable;
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.DatatableColumn;
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.DatatableFilter;
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.LinkTemplate;
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.YesNoBadgeTemplate;
import org.sourcelab.kafka.webview.ui.model.Cluster;
import org.sourcelab.kafka.webview.ui.model.View;
import org.sourcelab.kafka.webview.ui.repository.ClusterRepository;
import org.sourcelab.kafka.webview.ui.repository.ViewRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.HashMap;
Expand All @@ -59,23 +67,65 @@ public class ClusterController extends BaseController {
* GET Displays cluster list.
*/
@RequestMapping(path = "", method = RequestMethod.GET)
public String clusterIndex(final Model model, final RedirectAttributes redirectAttributes) {
public String datatable(
final Model model,
final Pageable pageable,
@RequestParam Map<String,String> allParams
) {
// Setup breadcrumbs
final BreadCrumbManager manager = new BreadCrumbManager(model);
manager.addCrumb("Cluster Explorer", null);

// Retrieve all clusters
final Iterable<Cluster> clusterList = clusterRepository.findAllByOrderByNameAsc();
model.addAttribute("clusterList", clusterList);
// Global flag if we have no clusters at all.
model.addAttribute("hasNoClusters", false);
model.addAttribute("hasClusters", true);
if (clusterRepository.count() == 0) {
model.addAttribute("hasNoClusters", true);
model.addAttribute("hasClusters", false);
}

// Retrieve how many views for each cluster
final Map<Long, Long> viewsByClusterId = new HashMap<>();
for (final Cluster cluster: clusterList) {
for (final Cluster cluster: clusterRepository.findAll()) {
final Long clusterId = cluster.getId();
final Long count = viewRepository.countByClusterId(cluster.getId());
viewsByClusterId.put(clusterId, count);
}
model.addAttribute("viewsByClusterId", viewsByClusterId);

final Datatable.Builder<Cluster> builder = Datatable.newBuilder(Cluster.class)
.withRepository(clusterRepository)
.withPageable(pageable)
.withRequestParams(allParams)
.withUrl("/cluster")
.withLabel("Kafka Clusters")
.withColumn(DatatableColumn.newBuilder(Cluster.class)
.withFieldName("name")
.withLabel("Cluster")
.withRenderTemplate(new LinkTemplate<>(
(record) -> "/cluster/" + record.getId(),
Cluster::getName
))
.withIsSortable(true)
.build())
.withColumn(DatatableColumn.newBuilder(Cluster.class)
.withFieldName("id")
.withLabel("Views")
.withRenderTemplate(new LinkTemplate<>(
(record) -> "/view?cluster.id=" + record.getId(),
(record) -> viewsByClusterId.computeIfAbsent(record.getId(), (key) -> 0L) + " Views"
))
.withIsSortable(false)
.build())
.withColumn(DatatableColumn.newBuilder(Cluster.class)
.withFieldName("isSslEnabled")
.withLabel("SSL")
.withRenderTemplate(new YesNoBadgeTemplate<>(Cluster::isSslEnabled))
.withIsSortable(true)
.build())
.withSearch("name");

// Add datatable attribute
model.addAttribute("datatable", builder.build());

// Display template
return "cluster/index";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@
import org.sourcelab.kafka.webview.ui.controller.BaseController;
import org.sourcelab.kafka.webview.ui.manager.ui.BreadCrumbManager;
import org.sourcelab.kafka.webview.ui.manager.ui.FlashMessage;
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.Datatable;
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.DatatableColumn;
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.DatatableFilter;
import org.sourcelab.kafka.webview.ui.manager.ui.datatable.LinkTemplate;
import org.sourcelab.kafka.webview.ui.model.Cluster;
import org.sourcelab.kafka.webview.ui.model.View;
import org.sourcelab.kafka.webview.ui.repository.ClusterRepository;
import org.sourcelab.kafka.webview.ui.repository.MessageFormatRepository;
import org.sourcelab.kafka.webview.ui.repository.ViewRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -40,8 +46,11 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
Expand All @@ -50,42 +59,42 @@
@Controller
@RequestMapping("/view")
public class ViewController extends BaseController {
@Autowired
private ViewRepository viewRepository;

private final ViewRepository viewRepository;
private final ClusterRepository clusterRepository;

/**
* Constructor.
*/
@Autowired
private ClusterRepository clusterRepository;
public ViewController(
final ViewRepository viewRepository,
final ClusterRepository clusterRepository
) {
this.viewRepository = Objects.requireNonNull(viewRepository);
this.clusterRepository = Objects.requireNonNull(clusterRepository);
}

/**
* GET views index.
*/
@RequestMapping(path = "", method = RequestMethod.GET)
public String index(
public String datatable(
final Model model,
@RequestParam(name = "clusterId", required = false) final Long clusterId
@RequestParam(name = "cluster.id", required = false) final Long clusterId,
final Pageable pageable,
@RequestParam Map<String,String> allParams
) {
// Setup breadcrumbs
final BreadCrumbManager breadCrumbManager = new BreadCrumbManager(model);

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

final Iterable<View> views;
if (clusterId == null) {
// Retrieve all views order by name asc.
views = viewRepository.findAllByOrderByNameAsc();
} else {
// Retrieve only views for the cluster
views = viewRepository.findAllByClusterIdOrderByNameAsc(clusterId);
}

// Set model Attributes
model.addAttribute("viewList", views);
model.addAttribute("clustersById", clustersById);

final String clusterName;
if (clusterId != null && clustersById.containsKey(clusterId)) {
// If filtered by a cluster
Expand All @@ -104,14 +113,71 @@ public String index(
}
model.addAttribute("clusterName", clusterName);

// Create a filter
final List<DatatableFilter.FilterOption> filterOptions = new ArrayList<>();
clustersById
.forEach((id, cluster) -> filterOptions.add(new DatatableFilter.FilterOption(String.valueOf(id), cluster.getName())));
final DatatableFilter filter = new DatatableFilter("Cluster", "clusterId", filterOptions);
model.addAttribute("filters", new DatatableFilter[] { filter });

final Datatable.Builder<View> builder = Datatable.newBuilder(View.class)
.withRepository(viewRepository)
.withPageable(pageable)
.withRequestParams(allParams)
.withUrl("/view")
.withLabel("Views")
.withColumn(DatatableColumn.newBuilder(View.class)
.withFieldName("name")
.withLabel("View")
.withRenderFunction((View::getName))
.build())
.withColumn(DatatableColumn.newBuilder(View.class)
.withFieldName("topic")
.withLabel("Topic")
.withRenderFunction(View::getTopic)
.build())
.withColumn(DatatableColumn.newBuilder(View.class)
.withFieldName("cluster.name")
.withLabel("Cluster")
.withRenderTemplate(new LinkTemplate<>(
(record) -> "/cluster/" + record.getCluster().getId(),
(record) -> record.getCluster().getName()
)).build())
.withColumn(DatatableColumn.newBuilder(View.class)
.withLabel("")
.withFieldName("")
.withIsSortable(false)
.withRenderTemplate(new LinkTemplate<>(
(record) -> "/view/" + record.getId(),
(record) -> "Browse"
)).build())
.withColumn(DatatableColumn.newBuilder(View.class)
.withLabel("")
.withFieldName("")
.withIsSortable(false)
.withRenderTemplate(new LinkTemplate<>(
(record) -> "/stream/" + record.getId(),
(record) -> "Stream"
)).build())
.withFilter(new DatatableFilter("Cluster", "cluster.id", filterOptions))
.withSearch("name");

// Add datatable attribute
model.addAttribute("datatable", builder.build());

// Determine if we have no clusters setup so we can show appropriate inline help.
model.addAttribute("hasClusters", !clustersById.isEmpty());
model.addAttribute("hasNoClusters", clustersById.isEmpty());
model.addAttribute("hasViews", viewRepository.count() > 0);

return "view/index";
}

/**
* GET Displays view for specified view.
*/
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
public String index(
public String view(
@PathVariable final Long id,
final RedirectAttributes redirectAttributes,
final Model model) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import org.apache.kafka.clients.admin.TopicListing;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.KafkaException;
import org.apache.kafka.common.Node;
import org.apache.kafka.common.TopicPartitionInfo;
import org.apache.kafka.common.config.ConfigResource;
Expand Down
Loading

0 comments on commit 530fba7

Please sign in to comment.