Merge branch 'task/JAL-4001_Plausible_API' into develop
authorJames Procter <j.procter@dundee.ac.uk>
Thu, 29 Jun 2023 08:55:51 +0000 (09:55 +0100)
committerJames Procter <j.procter@dundee.ac.uk>
Thu, 29 Jun 2023 08:55:51 +0000 (09:55 +0100)
help/help/html/privacy.html
help/markdown/releases/release-2_11_2_7.md [new file with mode: 0644]
help/markdown/whatsnew/whatsnew-2_11_2_7.md [new file with mode: 0644]
j11lib/JGoogleAnalytics_0.3.jar [deleted file]
j8lib/JGoogleAnalytics_0.3.jar [deleted file]
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/analytics/Plausible.java [moved from src/jalview/analytics/GoogleAnalytics4.java with 58% similarity]
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/util/HttpUtils.java

index 9ad61b5..73138f6 100644 (file)
   <p>Usage data is collected from the logs of various web services
     that the Jalview Desktop contacts through its normal operation.
     These are described below:</p>
-  <ul>
-    <li><em>HTTP logs on the Jalview website</em><br> We
-      record IP addresses of machines which access the web site, either
-      via the browser when downloading the application, or when the
-      Jalview Desktop user interface is launched.<br> <br>
-      <ul>
-        <li><i>The Jalview Getdown Launcher</i> (Since 2.11.0) examines release
-          channels every time Jalview launches to determine if a new
-          release is available.</li>
-        <li><i>The questionnaire web service at
-            www.jalview.org/cgi-bin/questionnaire.pl is checked and a
-            unique cookie for the current questionnaire is stored in the
-            Jalview properties file.</i></li>
-        <li><i>The Jalview web services stack is contacted to
-            retrieve the currently available web services. All
-            interactions with the public Jalview web services are
-            logged, but we delete all job data (input data and results)
-            after about two weeks.</i></li>
-      </ul> <br></li>
-    <li><em>Google Analytics</em><br> Since Jalview 2.4.0b2,
-      the Jalview Desktop records usage data with Google Analytics via
-      the <a href="http://code.google.com/p/jgoogleanalytics/">JGoogleAnalytics</a>
-      class.<br> The Google Analytics logs for Jalview version 2.4
-      only record the fact that the application was started, but in the
-      future, we will use this mechanism to improve the Desktop user
-      interface, by tracking which parts of the user interface are being
-      used most often.</li>
-  </ul>
-  </p>
+       <ul>
+               <li><em>HTTP logs on the Jalview website</em><br> We record
+                       IP addresses of machines which access the web site, either via the
+                       browser when downloading the application, or when the Jalview Desktop
+                       user interface is launched.<br> <br>
+                       <ul>
+                               <li><i>The Jalview Getdown Launcher</i> (Since 2.11.0) examines
+                                       release channels every time Jalview launches to determine if a new
+                                       release is available.</li>
+                               <li><i>The questionnaire web service at
+                                               www.jalview.org/cgi-bin/questionnaire.pl is checked and a unique
+                                               cookie for the current questionnaire is stored in the Jalview
+                                               properties file.</i></li>
+                               <li><i>The Jalview web services stack is contacted to
+                                               retrieve the currently available web services. All interactions
+                                               with the public Jalview web services are logged, but we delete all
+                                               job data (input data and results) after about two weeks.</i></li>
+                       </ul> <br></li>
+               <li><em>Usage Analytics</em><br> Since Jalview 2.11.2.7, the
+                       Jalview Desktop records usage data with Plausible.io via a custom
+                       GPLv3 client developed by Ben Soares. Prir to this, Jalview versions
+                       as far back as 2.4 recorded application launces via <a
+                       href="http://code.google.com/p/jgoogleanalytics/">JGoogleAnalytics</a>
+                       .<br> Usage logs for Jalview record the fact that the
+                       application was started, and details about the OS, installed Jalview
+                       launcher (if any) and java version used. In the future, we will use
+                       this mechanism to improve the Desktop user interface, by tracking
+                       which parts of the user interface are being used most often.</li>
+       </ul>
   <p>
     <strong>Stopping Jalview from calling home</strong><br> If you
     run Jalview in 'headless mode' via the command line, then the
diff --git a/help/markdown/releases/release-2_11_2_7.md b/help/markdown/releases/release-2_11_2_7.md
new file mode 100644 (file)
index 0000000..1ab0ebb
--- /dev/null
@@ -0,0 +1,11 @@
+---
+version: 2.11.2.7
+date: 2023-06-29
+channel: "release"
+---
+
+## New Features
+- <!-- JAL-4001 --> Jalview now reports usage statistics via Plausible.io
+
+## Issues Resolved
+- <!-- JAL-4116 --> PDB structures slow to view when Jalview Java console is open
diff --git a/help/markdown/whatsnew/whatsnew-2_11_2_7.md b/help/markdown/whatsnew/whatsnew-2_11_2_7.md
new file mode 100644 (file)
index 0000000..a5e5a79
--- /dev/null
@@ -0,0 +1,3 @@
+Jalview 2.11.2.7 is a rapid patch patch release. 
+
+
diff --git a/j11lib/JGoogleAnalytics_0.3.jar b/j11lib/JGoogleAnalytics_0.3.jar
deleted file mode 100644 (file)
index 0dbc98c..0000000
Binary files a/j11lib/JGoogleAnalytics_0.3.jar and /dev/null differ
diff --git a/j8lib/JGoogleAnalytics_0.3.jar b/j8lib/JGoogleAnalytics_0.3.jar
deleted file mode 100644 (file)
index 0dbc98c..0000000
Binary files a/j8lib/JGoogleAnalytics_0.3.jar and /dev/null differ
index 7f5746e..924b9cb 100644 (file)
@@ -1452,8 +1452,8 @@ label.choose_tempfac_type = Choose Temperature Factor type
 label.interpret_tempfac_as = Interpret Temperature Factor as
 label.add_pae_matrix_file = Add PAE matrix file
 label.nothing_selected = Nothing selected
-prompt.google_analytics_title = Jalview Usage Statistics
-prompt.google_analytics = Do you want to help make Jalview better by enabling the collection of usage statistics with Google Analytics?\nYou can enable or disable usage tracking in the preferences.
+prompt.analytics_title = Jalview Usage Statistics
+prompt.analytics = Do you want to help make Jalview better by enabling the collection of usage statistics with Plausible analytics?\nYou can enable or disable usage tracking in the preferences.
 label.working_ellipsis = Working ... 
 action.show_groups_on_matrix = Show groups on matrix
 action.show_groups_on_matrix_tooltip = When enabled, clusters defined on the matrix's associated tree or below the assigned threshold are shown as different colours on the matrix annotation row
index a4594dc..150a407 100644 (file)
@@ -1434,5 +1434,5 @@ label.tftype_default = Default
 label.tftype_plddt = pLDDT
 label.add_pae_matrix_file = Añadir un fichero de matriz PAE
 label.nothing_selected = Nada seleccionado
-prompt.google_analytics_title = Jalview Estadísticas de Uso
-prompt.google_analytics = ¿Quiere ayudar a mejorar Jalview habilitando la recopilación de estadísticas de uso con Google Analytics?\nPuede habilitar o deshabilitar el seguimiento de uso en las preferencias.
+prompt.analytics_title = Jalview Estadísticas de Uso
+prompt.analytics = ¿Quiere ayudar a mejorar Jalview habilitando la recopilación de estadísticas de uso con análisis Plausible?\nPuede habilitar o deshabilitar el seguimiento de uso en las preferencias.
similarity index 58%
rename from src/jalview/analytics/GoogleAnalytics4.java
rename to src/jalview/analytics/Plausible.java
index ba8a920..74db1f7 100644 (file)
@@ -1,8 +1,11 @@
 package jalview.analytics;
 
+import java.io.BufferedReader;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+import java.lang.invoke.MethodHandles;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -16,76 +19,86 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Random;
-import java.util.UUID;
 
 import jalview.bin.Cache;
 import jalview.bin.Console;
 import jalview.util.ChannelProperties;
+import jalview.util.HttpUtils;
 
-public class GoogleAnalytics4
+public class Plausible
 {
-  private static final String JALVIEW_ID = "Jalview Desktop";
+  private static final String USER_AGENT = HttpUtils.getUserAgent(
+          MethodHandles.lookup().lookupClass().getCanonicalName());
 
-  private static final String SESSION_ID = new Random().toString();
+  private static final String JALVIEW_ID = "Jalview Desktop";
 
-  private static final String MEASUREMENT_ID = "G-6TMPHMXEQ0";
+  private static final String DOMAIN = "jalview.org";
 
-  private static final String API_SECRET = "Qb9NSbqkRDqizG6j2BBJ2g";
+  private static final String CONFIG_API_BASE_URL = "https://www.jalview.org/services/config/analytics/url";
 
-  // This will generate a different CLIENT_ID each time the application is
-  // launched. Do we want to store it in .jalview_properties?
-  private static final String CLIENT_ID = UUID.randomUUID().toString();
+  private static final String DEFAULT_API_BASE_URL = "https://plausible.io/api/event";
 
-  private static final String BASE_URL = "https://www.google-analytics.com/mp/collect";
+  private static final String API_BASE_URL;
 
-  private static final String DESKTOP_EVENT = "desktop_event";
+  public static final String APPLICATION_BASE_URL = "desktop://localhost";
 
   private List<Map.Entry<String, String>> queryStringValues;
 
   private List<Map.Entry<String, Object>> jsonObject;
 
-  private List<Event> events;
-
   private List<Map.Entry<String, String>> cookieValues;
 
   private static boolean ENABLED = false;
 
-  private static GoogleAnalytics4 instance = null;
+  private static boolean DEBUG = true;
+
+  private static Plausible instance = null;
 
-  private static final Map<String, String> defaultParams;
+  private static final Map<String, String> defaultProps;
 
   static
   {
-    defaultParams = new HashMap<>();
-    defaultParams.put("app_name",
+    defaultProps = new HashMap<>();
+    defaultProps.put("app_name",
             ChannelProperties.getProperty("app_name") + " Desktop");
-    defaultParams.put("version", Cache.getProperty("VERSION"));
-    defaultParams.put("build_date",
+    defaultProps.put("version", Cache.getProperty("VERSION"));
+    defaultProps.put("build_date",
             Cache.getDefault("BUILD_DATE", "unknown"));
-    defaultParams.put("java_version", System.getProperty("java.version"));
+    defaultProps.put("java_version", System.getProperty("java.version"));
     String val = System.getProperty("sys.install4jVersion");
     if (val != null)
     {
-      defaultParams.put("install4j_version", val);
+      defaultProps.put("install4j_version", val);
     }
     val = System.getProperty("installer_template_version");
     if (val != null)
     {
-      defaultParams.put("install4j_template_version", val);
+      defaultProps.put("install4j_template_version", val);
     }
     val = System.getProperty("launcher_version");
     if (val != null)
     {
-      defaultParams.put("launcher_version", val);
+      defaultProps.put("launcher_version", val);
     }
-    defaultParams.put("java_arch",
+    defaultProps.put("java_arch",
             System.getProperty("os.arch") + " "
                     + System.getProperty("os.name") + " "
                     + System.getProperty("os.version"));
+    defaultProps.put("os", System.getProperty("os.name"));
+    defaultProps.put("os_version", System.getProperty("os.version"));
+    defaultProps.put("os_arch", System.getProperty("os.arch"));
+    String installation = Cache.applicationProperties
+            .getProperty("INSTALLATION");
+    if (installation != null)
+    {
+      defaultProps.put("installation", installation);
+    }
+
+    // ascertain the API_BASE_URL
+    API_BASE_URL = getAPIBaseURL();
   }
 
-  private GoogleAnalytics4()
+  private Plausible()
   {
     this.resetLists();
   }
@@ -95,72 +108,79 @@ public class GoogleAnalytics4
     ENABLED = b;
   }
 
-  public void sendAnalytics(String eventName, String... paramsStrings)
+  public void sendEvent(String eventName, String urlString,
+          String... propsStrings)
   {
-    sendAnalytics(eventName, false, paramsStrings);
+    sendEvent(eventName, urlString, false, propsStrings);
   }
 
   /**
    * The simplest way to send an analytic event.
    * 
    * @param eventName
-   *          The event name. To emulate a webpage view use "page_view" and set
-   *          a "page_location" parameter. See
-   *          https://developers.google.com/analytics/devguides/collection/ga4/events?client_type=gtag
-   * @param sendDefaultParams
-   *          Flag whether to add the default params about the application.
-   * @param paramsStrings
+   *          The event name. To emulate a webpage view use "pageview" and set a
+   *          "url" key/value. See https://plausible.io/docs/events-api
+   * @param sendDefaultProps
+   *          Flag whether to add the default props about the application.
+   * @param propsStrings
    *          Optional multiple Strings in key, value pairs (there should be an
-   *          even number of paramsStrings) to be set as parameters of the
-   *          event. To emulate a webpage view use "page_location" as the URL in
-   *          a "page_view" event.
+   *          even number of propsStrings) to be set as property of the event.
+   *          To emulate a webpage view set "url" as the URL in a "pageview"
+   *          event.
    */
-  public void sendAnalytics(String eventName, boolean sendDefaultParams,
-          String... paramsStrings)
+  public void sendEvent(String eventName, String urlString,
+          boolean sendDefaultProps, String... propsStrings)
   {
     // clear out old lists
     this.resetLists();
 
     if (!ENABLED)
     {
-      Console.debug("GoogleAnalytics4 not enabled.");
+      Console.debug("Plausible not enabled.");
       return;
     }
-    Map<String, String> params = new HashMap<>();
-    params.put("event_category", DESKTOP_EVENT);
-    params.put("event_label", eventName);
+    Map<String, String> props = new HashMap<>();
 
     // add these to all events from this application instance
-    if (sendDefaultParams)
+    if (sendDefaultProps)
     {
-      params.putAll(defaultParams);
+      props.putAll(defaultProps);
     }
 
-    // add (and overwrite with) the passed in params
-    if (paramsStrings != null && paramsStrings.length > 0)
+    // add (and overwrite with) the passed in props
+    if (propsStrings != null && propsStrings.length > 0)
     {
-      if (paramsStrings.length % 2 != 0)
+      if (propsStrings.length % 2 != 0)
       {
         Console.warn(
-                "Cannot addEvent with odd number of paramsStrings.  Ignoring the last one.");
+                "Cannot addEvent with odd number of propsStrings.  Ignoring the last one.");
       }
-      for (int i = 0; i < paramsStrings.length - 1; i += 2)
+      for (int i = 0; i < propsStrings.length - 1; i += 2)
       {
-        String key = paramsStrings[i];
-        String value = paramsStrings[i + 1];
-        params.put(key, value);
+        String key = propsStrings[i];
+        String value = propsStrings[i + 1];
+        props.put(key, value);
       }
     }
 
-    addEvent(eventName, params);
-    addQueryStringValue("measurement_id", MEASUREMENT_ID);
-    addQueryStringValue("api_secret", API_SECRET);
-    addJsonValue("client_id", CLIENT_ID);
-    addJsonValues("events", Event.toObjectList(events));
+    addJsonValue("domain", DOMAIN);
+    addJsonValue("name", eventName);
+    StringBuilder eventUrlSb = new StringBuilder(APPLICATION_BASE_URL);
+    if (!APPLICATION_BASE_URL.endsWith("/") && !urlString.startsWith("/"))
+    {
+      eventUrlSb.append("/");
+    }
+    eventUrlSb.append(urlString);
+    addJsonValue("url", eventUrlSb.toString());
+    addJsonObject("props", props);
     StringBuilder urlSb = new StringBuilder();
-    urlSb.append(BASE_URL);
-    urlSb.append('?');
-    urlSb.append(buildQueryString());
+    urlSb.append(API_BASE_URL);
+    String qs = buildQueryString();
+    if (qs != null && qs.length() > 0)
+    {
+      urlSb.append('?');
+      urlSb.append(qs);
+    }
     try
     {
       URL url = new URL(urlSb.toString());
@@ -171,15 +191,21 @@ public class GoogleAnalytics4
 
       String jsonString = buildJson();
 
-      Console.debug("GA4: HTTP Request is: '" + urlSb.toString() + "'");
-      Console.debug("GA4: POSTed JSON is:\n" + jsonString);
+      Console.debug(
+              "Plausible: HTTP Request is: '" + urlSb.toString() + "'");
+      if (DEBUG)
+      {
+        Console.debug("Plausible: User-Agent is: '" + USER_AGENT + "'");
+      }
+      Console.debug("Plausible: POSTed JSON is:\n" + jsonString);
 
       byte[] jsonBytes = jsonString.getBytes(StandardCharsets.UTF_8);
       int jsonLength = jsonBytes.length;
 
       httpURLConnection.setFixedLengthStreamingMode(jsonLength);
       httpURLConnection.setRequestProperty("Content-Type",
-              "application/json; charset=UTF-8");
+              "application/json");
+      httpURLConnection.setRequestProperty("User-Agent", USER_AGENT);
       httpURLConnection.connect();
       try (OutputStream os = httpURLConnection.getOutputStream())
       {
@@ -187,50 +213,63 @@ public class GoogleAnalytics4
       }
       int responseCode = httpURLConnection.getResponseCode();
       String responseMessage = httpURLConnection.getResponseMessage();
+
       if (responseCode < 200 || responseCode > 299)
       {
-        Console.warn("GoogleAnalytics4 connection failed: '" + responseCode
-                + " " + responseMessage + "'");
+        Console.warn("Plausible connection failed: '" + responseCode + " "
+                + responseMessage + "'");
       }
       else
       {
-        Console.debug("GoogleAnalytics4 connection succeeded: '"
-                + responseCode + " " + responseMessage + "'");
+        Console.debug("Plausible connection succeeded: '" + responseCode
+                + " " + responseMessage + "'");
+      }
+
+      if (DEBUG)
+      {
+        BufferedReader br = new BufferedReader(new InputStreamReader(
+                (httpURLConnection.getInputStream())));
+        StringBuilder sb = new StringBuilder();
+        String response;
+        while ((response = br.readLine()) != null)
+        {
+          sb.append(response);
+        }
+        String body = sb.toString();
+        Console.debug("Plausible response content:\n" + body);
       }
     } catch (MalformedURLException e)
     {
       Console.debug(
-              "Somehow the GoogleAnalytics4 BASE_URL and queryString is malformed.",
+              "Somehow the Plausible BASE_URL and queryString is malformed: '"
+                      + urlSb.toString() + "'",
               e);
       return;
     } catch (IOException e)
     {
-      Console.debug("Connection to GoogleAnalytics4 BASE_URL '" + BASE_URL
+      Console.debug("Connection to Plausible BASE_URL '" + API_BASE_URL
               + "' failed.", e);
     } catch (ClassCastException e)
     {
       Console.debug(
-              "Couldn't cast URLConnection to HttpURLConnection in GoogleAnalytics4.",
+              "Couldn't cast URLConnection to HttpURLConnection in Plausible.",
               e);
     }
   }
 
-  public void addEvent(String name, Map<String, String> params)
+  private void addJsonObject(String key, Map<String, String> map)
   {
-    Event event = new Event(name);
-    if (params != null && params.size() > 0)
+    List<Map.Entry<String, ? extends Object>> list = new ArrayList<>();
+    for (String k : map.keySet())
     {
-      for (String key : params.keySet())
-      {
-        String value = params.get(key);
-        event.addParam(key, value);
-      }
+      list.add(stringEntry(k, map.get(k)));
     }
-    events.add(event);
+    addJsonObject(key, list);
+
   }
 
   private void addJsonObject(String key,
-          List<Map.Entry<String, Object>> object)
+          List<Map.Entry<String, ? extends Object>> object)
   {
     jsonObject.add(objectEntry(key, object));
   }
@@ -268,16 +307,15 @@ public class GoogleAnalytics4
   private void resetLists()
   {
     jsonObject = new ArrayList<>();
-    events = new ArrayList<Event>();
     queryStringValues = new ArrayList<>();
     cookieValues = new ArrayList<>();
   }
 
-  public static GoogleAnalytics4 getInstance()
+  public static Plausible getInstance()
   {
     if (instance == null)
     {
-      instance = new GoogleAnalytics4();
+      instance = new Plausible();
     }
     return instance;
   }
@@ -480,52 +518,56 @@ public class GoogleAnalytics4
   {
     return new AbstractMap.SimpleEntry<String, String>(s, v);
   }
-}
-
-class Event
-{
-  private String name;
-
-  private List<Map.Entry<String, String>> params;
 
-  @SafeVarargs
-  public Event(String name, Map.Entry<String, String>... paramEntries)
+  private static String getAPIBaseURL()
   {
-    this.name = name;
-    this.params = new ArrayList<Map.Entry<String, String>>();
-    for (Map.Entry<String, String> paramEntry : paramEntries)
+    try
     {
-      if (paramEntry == null)
+      URL url = new URL(CONFIG_API_BASE_URL);
+      URLConnection urlConnection = url.openConnection();
+      HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
+      httpURLConnection.setRequestMethod("GET");
+      httpURLConnection.setRequestProperty("User-Agent", USER_AGENT);
+      httpURLConnection.setConnectTimeout(5000);
+      httpURLConnection.setReadTimeout(3000);
+      httpURLConnection.connect();
+      int responseCode = httpURLConnection.getResponseCode();
+      String responseMessage = httpURLConnection.getResponseMessage();
+
+      if (responseCode < 200 || responseCode > 299)
       {
-        continue;
+        Console.warn("Config URL connection to '" + CONFIG_API_BASE_URL
+                + "' failed: '" + responseCode + " " + responseMessage
+                + "'");
       }
-      params.add(paramEntry);
-    }
-  }
 
-  public void addParam(String param, String value)
-  {
-    params.add(GoogleAnalytics4.stringEntry(param, value));
-  }
+      BufferedReader br = new BufferedReader(
+              new InputStreamReader((httpURLConnection.getInputStream())));
+      StringBuilder sb = new StringBuilder();
+      String response;
+      while ((response = br.readLine()) != null)
+      {
+        sb.append(response);
+      }
+      if (sb.length() > 7 && sb.substring(0, 5).equals("https"))
+      {
+        return sb.toString();
+      }
 
-  protected List<Map.Entry<String, Object>> toObject()
-  {
-    List<Map.Entry<String, Object>> object = new ArrayList<>();
-    object.add(GoogleAnalytics4.objectEntry("name", (Object) name));
-    if (params.size() > 0)
+    } catch (MalformedURLException e)
     {
-      object.add(GoogleAnalytics4.objectEntry("params", (Object) params));
-    }
-    return object;
-  }
-
-  protected static List<Object> toObjectList(List<Event> events)
-  {
-    List<Object> eventObjectList = new ArrayList<>();
-    for (Event event : events)
+      Console.debug("Somehow the config URL is malformed: '"
+              + CONFIG_API_BASE_URL + "'", e);
+    } catch (IOException e)
     {
-      eventObjectList.add((Object) event.toObject());
+      Console.debug("Connection to Plausible BASE_URL '" + API_BASE_URL
+              + "' failed.", e);
+    } catch (ClassCastException e)
+    {
+      Console.debug(
+              "Couldn't cast URLConnection to HttpURLConnection in Plausible.",
+              e);
     }
-    return eventObjectList;
+    return DEFAULT_API_BASE_URL;
   }
 }
index fc9ddda..5741908 100755 (executable)
@@ -43,6 +43,7 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
@@ -52,7 +53,7 @@ import java.util.TreeSet;
 import javax.swing.LookAndFeel;
 import javax.swing.UIManager;
 
-import jalview.analytics.GoogleAnalytics4;
+import jalview.analytics.Plausible;
 import jalview.datamodel.PDBEntry;
 import jalview.gui.Preferences;
 import jalview.gui.UserDefinedColours;
@@ -128,7 +129,7 @@ import jalview.ws.sifts.SiftsSettings;
  * service</li>
  * <li>QUESTIONNAIRE last questionnaire:responder id string from questionnaire
  * service</li>
- * <li>USAGESTATS (false - user prompted) Enable google analytics tracker for
+ * <li>USAGESTATS (false - user prompted) Enable analytics tracker for
  * collecting usage statistics</li>
  * <li>SHOW_OVERVIEW boolean for overview window display</li>
  * <li>ANTI_ALIAS boolean for smooth fonts</li>
@@ -954,146 +955,41 @@ public class Cache
   }
 
   /**
-   * GA tracker object - actually JGoogleAnalyticsTracker null if tracking not
-   * enabled.
+   * Initialise the tracker if it is not done already.
    */
-  protected static Object tracker = null;
-
-  protected static Class trackerfocus = null;
-
-  protected static Class jgoogleanalyticstracker = null;
-
-  private static boolean useGA4 = true;
-
-  /**
-   * Initialise the google tracker if it is not done already.
-   */
-  public static void initGoogleTracker()
+  public static void initAnalytics()
   {
-    if (useGA4)
-    {
-      GoogleAnalytics4.setEnabled(true);
+    Plausible.setEnabled(true);
 
-      String appName = ChannelProperties.getProperty("app_name")
-              + " Desktop";
-      String version = Cache.getProperty("VERSION") + "_"
-              + Cache.getDefault("BUILD_DATE", "unknown");
-      String path;
-      /* we don't want to encode ':' as "%3A" for backward compatibility with the UA setup
-      try
-      {
-        path = "/" + String.join("/", URLEncoder.encode(appName, "UTF-8"),
-                URLEncoder.encode(version, "UTF-8"),
-                URLEncoder.encode(APPLICATION_STARTED, "UTF-8"));
-      } catch (UnsupportedEncodingException e)
-      {
-      */
-      path = ("/" + String.join("/", appName, version, APPLICATION_STARTED))
-              .replace(' ', '+');
-      /*
-      }
-      */
-      GoogleAnalytics4 ga4 = GoogleAnalytics4.getInstance();
-
-      // This will add a page_view similar to the old UA analytics.
-      // We probably want to get rid of this once the application_launch event
-      // is being processed properly.
-      ga4.sendAnalytics("page_view", "page_location", path, "page_title",
-              APPLICATION_STARTED);
-
-      // This will send a new "application_launch" event with parameters
-      // including the old-style "path", the channel name and version
-      ga4.sendAnalytics("application_launch", true, "page_location", path);
-    }
-    else
+    String appName = ChannelProperties.getProperty("app_name") + " Desktop";
+    String version = Cache.getProperty("VERSION") + "_"
+            + Cache.getDefault("BUILD_DATE", "unknown");
+    String path;
+    /* we don't want to encode ':' as "%3A" for backward compatibility with the UA setup
+    try
     {
-      if (tracker == null)
-      {
-        if (jgoogleanalyticstracker == null)
-        {
-          // try to get the tracker class
-          try
-          {
-            jgoogleanalyticstracker = Cache.class.getClassLoader()
-                    .loadClass(
-                            "com.boxysystems.jgoogleanalytics.JGoogleAnalyticsTracker");
-            trackerfocus = Cache.class.getClassLoader().loadClass(
-                    "com.boxysystems.jgoogleanalytics.FocusPoint");
-          } catch (Exception e)
-          {
-            Console.debug(
-                    "com.boxysystems.jgoogleanalytics package is not present - tracking not enabled.");
-            tracker = null;
-            jgoogleanalyticstracker = null;
-            trackerfocus = null;
-            return;
-          }
-        }
-        // now initialise tracker
-        Exception re = null, ex = null;
-        Error err = null;
-        String vrs = "No Version Accessible";
-        try
-        {
-          // Google analytics tracking code for Library Finder
-          tracker = jgoogleanalyticstracker
-                  .getConstructor(new Class[]
-                  { String.class, String.class, String.class })
-                  .newInstance(new Object[]
-                  { ChannelProperties.getProperty("app_name") + " Desktop",
-                      (vrs = Cache.getProperty("VERSION") + "_"
-                              + Cache.getDefault("BUILD_DATE", "unknown")),
-                      "UA-9060947-1" });
-          jgoogleanalyticstracker
-                  .getMethod("trackAsynchronously", new Class[]
-                  { trackerfocus })
-                  .invoke(tracker, new Object[]
-                  { trackerfocus
-                          .getConstructor(new Class[]
-                          { String.class })
-                          .newInstance(new Object[]
-                          { APPLICATION_STARTED }) });
-        } catch (RuntimeException e)
-        {
-          re = e;
-        } catch (Exception e)
-        {
-          ex = e;
-        } catch (Error e)
-        {
-          err = e;
-        }
-        if (re != null || ex != null || err != null)
-        {
-          if (re != null)
-          {
-            Console.debug("Caught runtime exception in googletracker init:",
-                    re);
-          }
-          if (ex != null)
-          {
-            Console.warn(
-                    "Failed to initialise GoogleTracker for Jalview Desktop with version "
-                            + vrs,
-                    ex);
-          }
-          if (err != null)
-          {
-            Console.error(
-                    "Whilst initing GoogleTracker for Jalview Desktop version "
-                            + vrs,
-                    err);
-          }
-        }
-        else
-        {
-          Console.debug("Successfully initialised tracker.");
-        }
-      }
+      path = "/" + String.join("/", URLEncoder.encode(appName, "UTF-8"),
+              URLEncoder.encode(version, "UTF-8"),
+              URLEncoder.encode(APPLICATION_STARTED, "UTF-8"));
+    } catch (UnsupportedEncodingException e)
+    {
+    */
+    List<String> pathParts = new ArrayList<>();
+    pathParts.add(appName);
+    pathParts.add(version);
+    pathParts.add(APPLICATION_STARTED);
+    path = ("/" + String.join("/", pathParts)).replace(' ', '+');
+    /*
     }
+    */
+    Plausible plausible = Plausible.getInstance();
+
+    // This will send a new "application_launch" event with parameters
+    // including the old-style "path", the channel name and version
+    plausible.sendEvent("application_launch", path, true);
   }
 
-  private static final String APPLICATION_STARTED = "Application Started.";
+  private static final String APPLICATION_STARTED = "Application Started";
 
   /**
    * get the user's default colour if available
index 3a733f3..57f2575 100755 (executable)
@@ -633,8 +633,7 @@ public class Jalview
     {
       headless = true;
     }
-    System.setProperty("http.agent",
-            "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
+    System.setProperty("http.agent", HttpUtils.getUserAgent());
 
     try
     {
@@ -810,6 +809,7 @@ public class Jalview
     // Run Commands from cli
     cmds = new Commands(argparser, headlessArg);
     boolean commandsSuccess = cmds.argsWereParsed();
+
     if (commandsSuccess)
     {
       if (headlessArg)
@@ -1573,7 +1573,7 @@ public class Jalview
                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
                     + "-noquestionnaire\tTurn off questionnaire check.\n"
                     + "-nonews\tTurn off check for Jalview news.\n"
-                    + "-nousagestats\tTurn off google analytics tracking for this session.\n"
+                    + "-nousagestats\tTurn off analytics tracking for this session.\n"
                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
                     // +
                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
@@ -1596,16 +1596,15 @@ public class Jalview
      */
     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
             "USAGESTATS",
-            MessageManager.getString("prompt.google_analytics_title"),
-            MessageManager.getString("prompt.google_analytics"),
+            MessageManager.getString("prompt.plausible_analytics_title"),
+            MessageManager.getString("prompt.plausible_analytics"),
             new Runnable()
             {
               @Override
               public void run()
               {
-                Console.debug(
-                        "Initialising googletracker for usage stats.");
-                Cache.initGoogleTracker();
+                Console.debug("Initialising analytics for usage stats.");
+                Cache.initAnalytics();
                 Console.debug("Tracking enabled.");
               }
             }, new Runnable()
@@ -1613,7 +1612,7 @@ public class Jalview
               @Override
               public void run()
               {
-                Console.debug("Not enabling Google Tracking.");
+                Console.debug("Not enabling analytics.");
               }
             }, null, true);
     desktop.addDialogThread(prompter);
index 0454cab..8379777 100644 (file)
@@ -25,10 +25,11 @@ import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.ProtocolException;
 import java.net.URL;
-import java.util.List;
 
 import javax.ws.rs.HttpMethod;
 
+import jalview.bin.Cache;
+
 public class HttpUtils
 {
 
@@ -101,4 +102,46 @@ public class HttpUtils
     return connection.getResponseCode() == 200;
   }
 
+  public static String getUserAgent()
+  {
+    return getUserAgent(null);
+  }
+
+  public static String getUserAgent(String className)
+  {
+    StringBuilder sb = new StringBuilder();
+    sb.append("Jalview");
+    sb.append('/');
+    sb.append(Cache.getDefault("VERSION", "Unknown"));
+    sb.append(" (");
+    sb.append(System.getProperty("os.name"));
+    sb.append("; ");
+    sb.append(System.getProperty("os.arch"));
+    sb.append(' ');
+    sb.append(System.getProperty("os.name"));
+    sb.append(' ');
+    sb.append(System.getProperty("os.version"));
+    sb.append("; ");
+    sb.append("java/");
+    sb.append(System.getProperty("java.version"));
+    sb.append("; ");
+    sb.append("jalview/");
+    sb.append(ChannelProperties.getProperty("channel"));
+    if (className != null)
+    {
+      sb.append("; ");
+      sb.append(className);
+    }
+    String installation = Cache.applicationProperties
+            .getProperty("INSTALLATION");
+    if (installation != null)
+    {
+      sb.append("; ");
+      sb.append(installation);
+    }
+    sb.append(')');
+    sb.append(" help@jalview.org");
+    return sb.toString();
+  }
+
 }