Skip to content

Commit

Permalink
Update query-metric service to generate HTML response via Spring MVC
Browse files Browse the repository at this point in the history
  • Loading branch information
billoley committed Feb 29, 2024
1 parent 0ba5287 commit 32f814a
Show file tree
Hide file tree
Showing 26 changed files with 1,001 additions and 840 deletions.
5 changes: 5 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
package datawave.microservice.querymetric;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.web.servlet.ModelAndView;

import com.fasterxml.jackson.annotation.JsonIgnore;

import datawave.microservice.querymetric.BaseQueryMetric.PageMetric;
import datawave.webservice.HtmlProvider;
import datawave.webservice.result.BaseResponse;

public abstract class BaseQueryMetricListResponse<T extends BaseQueryMetric> extends BaseResponse implements HtmlProvider {
public abstract class BaseQueryMetricListResponse<T extends BaseQueryMetric> extends BaseResponse {

private static final long serialVersionUID = 1L;
private static final String TITLE = "Query Metrics";
private static final String EMPTY = "";
@XmlElementWrapper(name = "queryMetrics")
@XmlElement(name = "queryMetric")
protected List<T> result = null;
Expand All @@ -35,22 +22,12 @@ public abstract class BaseQueryMetricListResponse<T extends BaseQueryMetric> ext
protected boolean isGeoQuery = false;
@XmlTransient
private boolean administratorMode = false;
private String JQUERY_INCLUDES;
protected String BASE_URL = "/DataWave/Query/Metrics";

public BaseQueryMetricListResponse() {
setHtmlIncludePaths(new HashMap<>());
}
protected String basePath = "/DataWave/Query/Metrics";

public void setHtmlIncludePaths(Map<String,String> pathMap) {
// @formatter:off
JQUERY_INCLUDES =
"<script type='text/javascript' src='" + pathMap.getOrDefault("jquery", "") + "/jquery.min.js'></script>\n";
// @formatter:on
}
protected String viewName = "querymetric";

public void setBaseUrl(String baseUrl) {
this.BASE_URL = baseUrl;
public void setBasePath(String basePath) {
this.basePath = basePath;
}

private static String numToString(long number) {
Expand Down Expand Up @@ -90,131 +67,12 @@ public void setGeoQuery(boolean geoQuery) {
isGeoQuery = geoQuery;
}

@JsonIgnore
@XmlTransient
@Override
public String getTitle() {
return TITLE;
public void setViewName(String viewName) {
this.viewName = viewName;
}

@JsonIgnore
@XmlTransient
@Override
public String getPageHeader() {
return getTitle();
}

@JsonIgnore
@XmlTransient
@Override
public String getHeadContent() {
if (isGeoQuery) {
// @formatter:off
return JQUERY_INCLUDES +
"<script type='text/javascript'>" +
"$(document).ready(function() {" +
" var currentUrl = window.location.href.replace(/\\/+$/, '');" +
" var queryHeader = document.getElementById(\"query-header\").innerHTML;" +
" queryHeader = queryHeader + '<br>(<a href=\"' + currentUrl + '/map\">map</a>)';" +
" document.getElementById(\"query-header\").innerHTML = queryHeader;" +
"});" +
"</script>";
// @formatter: on
} else {
return EMPTY;
}
}

@JsonIgnore
@XmlTransient
@Override
public String getMainContent() {
StringBuilder builder = new StringBuilder();

builder.append("<table>\n");
builder.append("<tr>\n");
builder.append("<th>Visibility</th><th>Query Date</th><th>User</th><th>UserDN</th><th>Proxy Server(s)</th><th>Query ID</th><th>Query Type</th>");
builder.append("<th>Query Logic</th><th id=\"query-header\">Query</th><th>Begin Date</th><th>End Date</th><th>Query Auths</th><th>Server</th>");
builder.append("<th>Query Setup Time (ms)</th><th>Query Setup Call Time (ms)</th><th>Number Pages</th><th>Number Results</th>");
builder.append("<th>Total Page Time (ms)</th><th>Total Page Call Time (ms)</th><th>Total Page Serialization Time (ms)</th>");
builder.append("<th>Total Page Bytes Sent (uncompressed)</th><th>Lifecycle</th><th>Elapsed Time</th><th>Error Code</th><th>Error Message</th>");
builder.append("\n</tr>\n");

TreeMap<Date,T> metricMap = new TreeMap<Date,T>(Collections.reverseOrder());

for (T metric : this.getResult()) {
metricMap.put(metric.getCreateDate(), metric);
}

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss");

int x = 0;
for (T metric : metricMap.values()) {
// highlight alternating rows
if (x % 2 == 0) {
builder.append("<tr class=\"highlight\" style=\"vertical-align:top;\">\n");
} else {
builder.append("<tr style=\"vertical-align:top;\">\n");
}
x++;

builder.append("<td>").append(metric.getColumnVisibility()).append("</td>");
builder.append("<td style=\"min-width:125px !important;\">").append(sdf.format(metric.getCreateDate())).append("</td>");
builder.append("<td>").append(metric.getUser()).append("</td>");
String userDN = metric.getUserDN();
builder.append("<td style=\"min-width:500px !important;\">").append(userDN == null ? "" : userDN).append("</td>");
String proxyServers = metric.getProxyServers() == null ? "" : StringUtils.join(metric.getProxyServers(), "<BR/>");
builder.append("<td>").append(proxyServers).append("</td>");
if (this.isAdministratorMode()) {
builder.append("<td><a href=\"/DataWave/Query/Metrics/user/").append(metric.getUser()).append("/").append(metric.getQueryId()).append("/")
.append("\">").append(metric.getQueryId()).append("</a></td>");
} else {
builder.append("<td><a href=\"/DataWave/Query/Metrics/id/").append(metric.getQueryId()).append("/").append("\">").append(metric.getQueryId())
.append("</a></td>");
}
builder.append("<td>").append(metric.getQueryType()).append("</td>");
builder.append("<td>").append(metric.getQueryLogic()).append("</td>");
builder.append("<td style=\"word-wrap: break-word;\">").append(StringEscapeUtils.escapeHtml4(metric.getQuery())).append("</td>");

String beginDate = metric.getBeginDate() == null ? "" : sdf.format(metric.getBeginDate());
builder.append("<td style=\"min-width:125px !important;\">").append(beginDate).append("</td>");
String endDate = metric.getEndDate() == null ? "" : sdf.format(metric.getEndDate());
builder.append("<td style=\"min-width:125px !important;\">").append(endDate).append("</td>");
String queryAuths = metric.getQueryAuthorizations() == null ? "" : metric.getQueryAuthorizations().replaceAll(",", " ");
builder.append("<td style=\"word-wrap: break-word; min-width:300px !important;\">").append(queryAuths).append("</td>");

builder.append("<td>").append(metric.getHost()).append("</td>");
builder.append("<td>").append(numToString(metric.getSetupTime())).append("</td>");
builder.append("<td>").append(numToString(metric.getCreateCallTime())).append("</td>\n");
builder.append("<td>").append(metric.getNumPages()).append("</td>");
builder.append("<td>").append(metric.getNumResults()).append("</td>");
long count = 0l;
long callTime = 0l;
long serializationTime = 0l;
long bytesSent = 0l;
for (PageMetric p : metric.getPageTimes()) {
count += p.getReturnTime();
callTime += (p.getCallTime()) == -1 ? 0 : p.getCallTime();
serializationTime += (p.getSerializationTime()) == -1 ? 0 : p.getSerializationTime();
bytesSent += (p.getBytesWritten()) == -1 ? 0 : p.getBytesWritten();
}
builder.append("<td>").append(count).append("</td>\n");
builder.append("<td>").append(numToString(callTime)).append("</td>\n");
builder.append("<td>").append(numToString(serializationTime)).append("</td>\n");
builder.append("<td>").append(numToString(bytesSent)).append("</td>\n");
builder.append("<td>").append(metric.getLifecycle()).append("</td>");
builder.append("<td>").append(metric.getElapsedTime()).append("</td>");
String errorCode = metric.getErrorCode();
builder.append("<td style=\"word-wrap: break-word;\">").append((errorCode == null) ? "" : StringEscapeUtils.escapeHtml4(errorCode)).append("</td>");
String errorMessage = metric.getErrorMessage();
builder.append("<td style=\"word-wrap: break-word;\">").append((errorMessage == null) ? "" : StringEscapeUtils.escapeHtml4(errorMessage))
.append("</td>");

builder.append("\n</tr>\n");
}

builder.append("</table>\n");

return builder.toString();
}
/**
* @return the ModelAndView for template html page
*/
abstract public ModelAndView createModelAndView();
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

import org.springframework.web.servlet.ModelAndView;

import com.fasterxml.jackson.annotation.JsonIgnore;

import datawave.webservice.HtmlProvider;
import datawave.webservice.result.BaseResponse;

/**
Expand All @@ -25,15 +26,11 @@
@XmlRootElement(name = "QueryGeometry")
@XmlAccessorType(XmlAccessType.NONE)
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public class QueryGeometryResponse extends BaseResponse implements HtmlProvider {
public class QueryGeometryResponse extends BaseResponse {

private static final long serialVersionUID = 1L;

private static final String TITLE = "Query Geometry";

private String LEAFLET_INCLUDES;
private String JQUERY_INCLUDES;
private String MAP_INCLUDES;
private Map<String,String> pathPrefixMap = new HashMap<>();

public QueryGeometryResponse() {
this(null, null);
Expand All @@ -42,20 +39,22 @@ public QueryGeometryResponse() {
public QueryGeometryResponse(String queryId, String basemaps) {
this.queryId = queryId;
this.basemaps = basemaps;
setHtmlIncludePaths(new HashMap<>());
}

public void setHtmlIncludePaths(Map<String,String> pathMap) {
// @formatter:off
LEAFLET_INCLUDES =
"<link rel='stylesheet' type='text/css' href='" + pathMap.getOrDefault("leaflet", "") + "/leaflet.css' />\n" +
"<script type='text/javascript' src='" + pathMap.getOrDefault("leaflet", "") + "/leaflet.js'></script>\n";
JQUERY_INCLUDES =
"<script type='text/javascript' src='" + pathMap.getOrDefault("jquery", "") + "/jquery.min.js'></script>\n";
MAP_INCLUDES =
"<link rel='stylesheet' type='text/css' href='" + pathMap.getOrDefault("css", "") + "/queryMap.css' />\n" +
"<script type='text/javascript' src='" + pathMap.getOrDefault("js", "") + "/queryMap.js'></script>";
// @formatter:on
public void setPathPrefixMap(Map<String,String> pathPrefixMap) {
this.pathPrefixMap = pathPrefixMap;
}

public ModelAndView createModelAndView() {
ModelAndView mav = new ModelAndView();
mav.setViewName("querymetricgeometry");
mav.addObject("basemapScript", "var basemaps = " + this.basemaps + ";");
mav.addObject("geoJsonFeaturesScript", "var features = " + toGeoJsonFeatures() + ";");
mav.addObject("jqueryPrefix", this.pathPrefixMap.getOrDefault("jquery", ""));
mav.addObject("leafletPrefix", this.pathPrefixMap.getOrDefault("leaflet", ""));
mav.addObject("cssPrefix", this.pathPrefixMap.getOrDefault("css", ""));
mav.addObject("jsPrefix", this.pathPrefixMap.getOrDefault("js", ""));
return mav;
}

@XmlElement(name = "queryId", nillable = true)
Expand All @@ -69,38 +68,6 @@ public void setHtmlIncludePaths(Map<String,String> pathMap) {
@XmlElement(name = "feature")
protected List<QueryGeometry> result = null;

@JsonIgnore
@XmlTransient
@Override
public String getTitle() {
if (queryId != null)
return TITLE + " - " + queryId;
return TITLE;
}

@JsonIgnore
@XmlTransient
@Override
public String getHeadContent() {
String basemapData = "<script type='text/javascript'>var basemaps = " + basemaps + ";</script>\n";
String featureData = "<script type='text/javascript'>var features = " + toGeoJsonFeatures() + ";</script>\n";
return String.join("\n", featureData, JQUERY_INCLUDES, LEAFLET_INCLUDES, basemapData, MAP_INCLUDES);
}

@JsonIgnore
@XmlTransient
@Override
public String getPageHeader() {
return getTitle();
}

@JsonIgnore
@XmlTransient
@Override
public String getMainContent() {
return "<div align='center'><div id='map' style='height: calc(100% - 85px); width: 100%; position: fixed; top: 86px; left: 0px;'></div></div>";
}

private String toGeoJsonFeatures() {
if (!this.result.isEmpty())
return "[ " + this.result.stream().map(QueryGeometry::toGeoJsonFeature).collect(Collectors.joining(", ")) + " ]";
Expand Down
Loading

0 comments on commit 32f814a

Please sign in to comment.