[tor-commits] [metrics-web/master] Add metrics timeline events underneath graphs.

karsten at torproject.org karsten at torproject.org
Fri Dec 15 16:33:27 UTC 2017


commit 2d013aba0b14809d0f2781ad37eac325444388e6
Author: Karsten Loesing <karsten.loesing at gmx.net>
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%; }
 
 
 



More information about the tor-commits mailing list