[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [metrics-web/master] Add metrics timeline events underneath graphs.
commit 2d013aba0b14809d0f2781ad37eac325444388e6
Author: Karsten Loesing <karsten.loesing@xxxxxxx>
Date: Wed Nov 29 11:01:06 2017 +0100
Add metrics timeline events underneath graphs.
Implements a first version of #24260.
---
.../org/torproject/metrics/web/GraphServlet.java | 60 ++++++++++++++
.../java/org/torproject/metrics/web/Metric.java | 11 +++
.../org/torproject/metrics/web/MetricServlet.java | 12 +++
.../main/java/org/torproject/metrics/web/News.java | 94 +++++++++++++++++++++-
website/src/main/resources/etc/metrics.json | 15 ++--
website/src/main/resources/web/WEB-INF/graph.jsp | 41 ++++++++++
website/src/main/resources/web/css/style.css | 24 ++----
7 files changed, 234 insertions(+), 23 deletions(-)
diff --git a/website/src/main/java/org/torproject/metrics/web/GraphServlet.java b/website/src/main/java/org/torproject/metrics/web/GraphServlet.java
index b376be5..02f1cee 100644
--- a/website/src/main/java/org/torproject/metrics/web/GraphServlet.java
+++ b/website/src/main/java/org/torproject/metrics/web/GraphServlet.java
@@ -219,6 +219,66 @@ public class GraphServlet extends MetricServlet {
String url = "?" + urlBuilder.toString().substring(5);
request.setAttribute("parameters", url);
}
+ if (this.includeRelatedEvents.contains(requestedId)) {
+ request.setAttribute("includeRelatedEvents", true);
+ String startParameter = dateFormat.format(defaultStartDate);
+ String endParameter = dateFormat.format(defaultEndDate);
+ String countryParameter = "all";
+ String eventsParameter = "off";
+ if (null != checkedParameters) {
+ for (Map.Entry<String, String[]> checkedParameter
+ : checkedParameters.entrySet()) {
+ switch (checkedParameter.getKey()) {
+ case "start":
+ startParameter = checkedParameter.getValue()[0];
+ break;
+ case "end":
+ endParameter = checkedParameter.getValue()[0];
+ break;
+ case "country":
+ countryParameter = checkedParameter.getValue()[0];
+ break;
+ case "events":
+ eventsParameter = checkedParameter.getValue()[0];
+ break;
+ }
+ }
+ }
+ if (!"off".equals(eventsParameter)) {
+ request.setAttribute("displayEventsNotice", true);
+ }
+ List<String> relatedEvents = new ArrayList<>();
+ for (News event : this.sortedEvents) {
+ if (null == event.getStart()) {
+ /* Skip event without start date. */
+ continue;
+ }
+ if (event.getStart().compareTo(endParameter) > 0) {
+ /* Skip event starting after displayed time period. */
+ continue;
+ }
+ if (null != event.getEnd()
+ && event.getEnd().compareTo(startParameter) < 0) {
+ /* Skip multi-day event ending before displayed time period. */
+ continue;
+ }
+ if (null == event.getEnd()
+ && event.getStart().compareTo(startParameter) < 0) {
+ /* Skip single-day event happening before displayed time period. */
+ continue;
+ }
+ if (!"all".equals(countryParameter) && null != event.getPlaces()
+ && !event.getPlaces().contains(countryParameter)) {
+ /* Skip country-specific event for another country than the
+ * displayed one. */
+ continue;
+ }
+ /* We could filter by transport or version here, but that's a
+ * non-trivial task. */
+ relatedEvents.add(event.formatAsTableRow());
+ }
+ request.setAttribute("relatedEvents", relatedEvents);
+ }
}
request.getRequestDispatcher("WEB-INF/graph.jsp").forward(request,
response);
diff --git a/website/src/main/java/org/torproject/metrics/web/Metric.java b/website/src/main/java/org/torproject/metrics/web/Metric.java
index 321ed1a..50f3978 100644
--- a/website/src/main/java/org/torproject/metrics/web/Metric.java
+++ b/website/src/main/java/org/torproject/metrics/web/Metric.java
@@ -3,6 +3,9 @@
package org.torproject.metrics.web;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
@SuppressWarnings("checkstyle:membername")
public class Metric {
@@ -28,6 +31,10 @@ public class Metric {
private String[] data_column_spec;
+ @Expose
+ @SerializedName("include_related_events")
+ private boolean includeRelatedEvents = false;
+
public String getId() {
return this.id;
}
@@ -71,5 +78,9 @@ public class Metric {
public String[] getData() {
return this.data;
}
+
+ public boolean getIncludeRelatedEvents() {
+ return this.includeRelatedEvents;
+ }
}
diff --git a/website/src/main/java/org/torproject/metrics/web/MetricServlet.java b/website/src/main/java/org/torproject/metrics/web/MetricServlet.java
index 730a767..0b3bb11 100644
--- a/website/src/main/java/org/torproject/metrics/web/MetricServlet.java
+++ b/website/src/main/java/org/torproject/metrics/web/MetricServlet.java
@@ -3,7 +3,9 @@
package org.torproject.metrics.web;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -33,6 +35,10 @@ public abstract class MetricServlet extends AnyServlet {
protected Map<String, Category> categoriesById = new HashMap<>();
+ protected Set<String> includeRelatedEvents = new HashSet<>();
+
+ protected List<News> sortedEvents = new ArrayList<>();
+
@Override
public void init() throws ServletException {
super.init();
@@ -59,6 +65,9 @@ public abstract class MetricServlet extends AnyServlet {
if (metric.getData() != null) {
this.data.put(id, metric.getData());
}
+ if (metric.getIncludeRelatedEvents()) {
+ this.includeRelatedEvents.add(id);
+ }
}
for (Category category :
ContentProvider.getInstance().getCategoriesList()) {
@@ -66,6 +75,9 @@ public abstract class MetricServlet extends AnyServlet {
this.categoriesById.put(id, category);
}
}
+ this.sortedEvents.addAll(ContentProvider.getInstance().getNewsList());
+ Collections.sort(this.sortedEvents,
+ (o1, o2) -> o2.getStart().compareTo(o1.getStart()));
}
}
diff --git a/website/src/main/java/org/torproject/metrics/web/News.java b/website/src/main/java/org/torproject/metrics/web/News.java
index c7630ab..9afa598 100644
--- a/website/src/main/java/org/torproject/metrics/web/News.java
+++ b/website/src/main/java/org/torproject/metrics/web/News.java
@@ -3,13 +3,19 @@
package org.torproject.metrics.web;
+import org.torproject.metrics.web.graphs.Countries;
+
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
public class News {
private String start;
private String end;
- private String[] places;
+ private List<String> places;
private String[] protocols;
@@ -27,7 +33,7 @@ public class News {
return this.end;
}
- String[] getPlaces() {
+ List<String> getPlaces() {
return this.places;
}
@@ -46,5 +52,89 @@ public class News {
boolean isUnknown() {
return this.unknown;
}
+
+ static SortedMap<String, String> countries;
+
+ static {
+ countries = new TreeMap<>();
+ for (String[] country : Countries.getInstance().getCountryList()) {
+ countries.put(country[0], country[1]);
+ }
+ }
+
+ String formatAsTableRow() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<tr><td><span class=\"dates\">");
+ if (null == this.start) {
+ /* Invalid event without start date. */
+ sb.append("N/A");
+ } else if (null == this.end || this.start.equals(this.end)) {
+ /* Single-day event. */
+ sb.append(this.start);
+ } else {
+ /* Multi-day event. */
+ sb.append(this.start).append(" to ").append(this.end);
+ }
+ sb.append("</span></td><td>");
+ if (null != this.places) {
+ boolean appendUnknownCountry = false;
+ for (String place : this.getPlaces()) {
+ if (countries.containsKey(place)) {
+ sb.append(" <span class=\"label label-warning\">")
+ .append(countries.get(place)).append("</span>");
+ } else {
+ appendUnknownCountry = true;
+ }
+ }
+ if (appendUnknownCountry) {
+ sb.append(" <span class=\"label label-warning\">"
+ + "Unknown country</span>");
+ }
+ }
+ if (null != this.protocols) {
+ for (String protocol : this.protocols) {
+ switch (protocol) {
+ case "relay":
+ sb.append(" <span class=\"label label-success\">Relays</span>");
+ break;
+ case "bridge":
+ sb.append(" <span class=\"label label-primary\">Bridges</span>");
+ break;
+ case "<OR>":
+ sb.append(" <span class=\"label label-info\"><OR></span>");
+ break;
+ default:
+ sb.append(" <span class=\"label label-info\">").append(protocol)
+ .append("</span>");
+ break;
+ }
+ }
+ }
+ if (this.unknown) {
+ sb.append(" <span class=\"label label-default\">Unknown</span>");
+ }
+ sb.append("</td><td>");
+ if (null != this.description) {
+ sb.append(this.description).append("<br/>");
+ }
+ if (null != this.links) {
+ for (String link : this.links) {
+ int tagEnd = link.indexOf('>');
+ if (tagEnd < 0 || tagEnd + 2 > link.length()) {
+ continue;
+ }
+ sb.append(link, 0, tagEnd);
+ sb.append(" class=\"link\"");
+ if (!link.startsWith("<a href=\"https://metrics.torproject.org/")) {
+ sb.append(" target=\"_blank\"");
+ }
+ sb.append('>')
+ .append(link.substring(tagEnd + 1, tagEnd + 2).toUpperCase())
+ .append(link.substring(tagEnd + 2));
+ }
+ }
+ sb.append("</td></tr>");
+ return sb.toString();
+ }
}
diff --git a/website/src/main/resources/etc/metrics.json b/website/src/main/resources/etc/metrics.json
index b9bd3ad..a3a4918 100644
--- a/website/src/main/resources/etc/metrics.json
+++ b/website/src/main/resources/etc/metrics.json
@@ -161,7 +161,8 @@
],
"data": [
"clients"
- ]
+ ],
+ "include_related_events": true
},
{
"id": "userstats-relay-table",
@@ -222,7 +223,8 @@
],
"data": [
"clients"
- ]
+ ],
+ "include_related_events": true
},
{
"id": "userstats-bridge-table",
@@ -259,7 +261,8 @@
],
"data": [
"clients"
- ]
+ ],
+ "include_related_events": true
},
{
"id": "userstats-bridge-combined",
@@ -274,7 +277,8 @@
],
"data": [
"userstats-combined"
- ]
+ ],
+ "include_related_events": true
},
{
"id": "userstats-bridge-version",
@@ -289,7 +293,8 @@
],
"data": [
"clients"
- ]
+ ],
+ "include_related_events": true
},
{
"id": "oxford-anonymous-internet",
diff --git a/website/src/main/resources/web/WEB-INF/graph.jsp b/website/src/main/resources/web/WEB-INF/graph.jsp
index 98f5a21..238f6d5 100644
--- a/website/src/main/resources/web/WEB-INF/graph.jsp
+++ b/website/src/main/resources/web/WEB-INF/graph.jsp
@@ -169,6 +169,47 @@
</div><!-- col-md-4 -->
</div><!-- row -->
+
+ <c:if test="${includeRelatedEvents}">
+ <div class="row">
+ <div class="col-md-12">
+ <h2>Related events</h2>
+ <p>The following events have been manually collected on
+ <a href="https://trac.torproject.org/projects/tor/wiki/doc/MetricsTimeline" target="_blank">this wiki page</a>
+ and might be related to the displayed graph.</p>
+ <c:if test="${displayEventsNotice}">
+ <div class="alert alert-danger">
+ <p>The manually collected events in this table do not
+ necessarily match the automatically generated possible
+ censorship events shown in the graph.</p>
+ </div>
+ </c:if>
+ <table class="table events">
+ <thead>
+ <tr>
+ <th class="dates">Dates</th>
+ <th class="tags">Places/Protocols</th>
+ <th class="description">Description and Links</th>
+ </tr>
+ </thead>
+ <tbody>
+ <c:choose>
+ <c:when test="${empty relatedEvents}">
+ <tr><td colspan="3">No events are known that might
+ be related to the displayed graph.</td></tr>
+ </c:when>
+ <c:otherwise>
+ <c:forEach var="relatedEvent" items="${relatedEvents}">
+ ${relatedEvent}
+ </c:forEach>
+ </c:otherwise>
+ </c:choose>
+ </tbody>
+ </table>
+ </div><!-- col-md-12 -->
+ </div><!-- row -->
+ </c:if>
+
</div><!-- tab-pane -->
</div><!-- tab-content -->
</div><!-- container -->
diff --git a/website/src/main/resources/web/css/style.css b/website/src/main/resources/web/css/style.css
index 2652faf..1148e6f 100644
--- a/website/src/main/resources/web/css/style.css
+++ b/website/src/main/resources/web/css/style.css
@@ -475,23 +475,15 @@ a.btn[target="_blank"]:before {
-/* research download tables */
-td, th {
- padding-left:0 !important;
-}
-td a {
- padding-right:1em;
+/* related events table */
+.events a.link { padding-right:1em; }
+.events th.dates { width:20%; }
+.events th.tags { width:20%; }
+.events th.description { width:60%; }
+.events td span.dates {
+ color: #7d4698;
+ font-weight: bold;
}
-th.title { width:34%; }
-th.author { width:34%; }
-th.date { width:16%; }
-th.download { width:16%; }
-
-
-/* tools table */
-th.title-tools { width:15%; }
-th.description { width:70%; }
-th.link { width:15%; }
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits