about summary refs log tree commit diff
path: root/third_party/gerrit/0004-Add-titles-to-CLs-over-HTTP.patch
From ad4564c31401a900702fca6e3f673badbfbdf9d9 Mon Sep 17 00:00:00 2001
From: Luke Granger-Brown <git@lukegb.com>
Date: Thu, 2 Jul 2020 23:03:02 +0100
Subject: [PATCH 4/6] Add titles to CLs over HTTP

---
 .../gerrit/httpd/raw/IndexHtmlUtil.java       | 13 +++-
 .../google/gerrit/httpd/raw/IndexServlet.java |  7 +-
 .../google/gerrit/httpd/raw/StaticModule.java |  5 +-
 .../gerrit/httpd/raw/TitleComputer.java       | 67 +++++++++++++++++++
 .../gerrit/httpd/raw/PolyGerritIndexHtml.soy  |  4 +-
 5 files changed, 88 insertions(+), 8 deletions(-)
 create mode 100644 java/com/google/gerrit/httpd/raw/TitleComputer.java

diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
index 77d02c16d0..3b2cae702a 100644
--- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
@@ -38,6 +38,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -58,13 +59,14 @@ public class IndexHtmlUtil {
       String faviconPath,
       Map<String, String[]> urlParameterMap,
       Function<String, SanitizedContent> urlInScriptTagOrdainer,
-      String requestedURL)
+      String requestedURL,
+      TitleComputer titleComputer)
       throws URISyntaxException, RestApiException {
     ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
     data.putAll(
             staticTemplateData(
                 canonicalURL, cdnPath, faviconPath, urlParameterMap, urlInScriptTagOrdainer))
-        .putAll(dynamicTemplateData(gerritApi, requestedURL));
+        .putAll(dynamicTemplateData(gerritApi, requestedURL, titleComputer));
 
     Set<String> enabledExperiments = experimentData(urlParameterMap);
     if (!enabledExperiments.isEmpty()) {
@@ -75,7 +77,9 @@ public class IndexHtmlUtil {
 
   /** Returns dynamic parameters of {@code index.html}. */
   public static ImmutableMap<String, Object> dynamicTemplateData(
-      GerritApi gerritApi, String requestedURL) throws RestApiException, URISyntaxException {
+      GerritApi gerritApi,
+      String requestedURL,
+      TitleComputer titleComputer) throws RestApiException, URISyntaxException {
     ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
     Map<String, SanitizedContent> initialData = new HashMap<>();
     Server serverApi = gerritApi.config().server();
@@ -126,6 +130,9 @@ public class IndexHtmlUtil {
       // Don't render data
     }
 
+    Optional<String> title = titleComputer.computeTitle(requestedURL);
+    title.ifPresent(s -> data.put("title", s));
+
     data.put("gerritInitialData", initialData);
     return data.build();
   }
diff --git a/java/com/google/gerrit/httpd/raw/IndexServlet.java b/java/com/google/gerrit/httpd/raw/IndexServlet.java
index 97d22701de..089ef4725f 100644
--- a/java/com/google/gerrit/httpd/raw/IndexServlet.java
+++ b/java/com/google/gerrit/httpd/raw/IndexServlet.java
@@ -44,12 +44,14 @@ public class IndexServlet extends HttpServlet {
   private final GerritApi gerritApi;
   private final SoySauce soySauce;
   private final Function<String, SanitizedContent> urlOrdainer;
+  private TitleComputer titleComputer;
 
   IndexServlet(
       @Nullable String canonicalUrl,
       @Nullable String cdnPath,
       @Nullable String faviconPath,
-      GerritApi gerritApi) {
+      GerritApi gerritApi,
+      TitleComputer titleComputer) {
     this.canonicalUrl = canonicalUrl;
     this.cdnPath = cdnPath;
     this.faviconPath = faviconPath;
@@ -63,6 +65,7 @@ public class IndexServlet extends HttpServlet {
         (s) ->
             UnsafeSanitizedContentOrdainer.ordainAsSafe(
                 s, SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI);
+    this.titleComputer = titleComputer;
   }
 
   @Override
@@ -74,7 +77,7 @@ public class IndexServlet extends HttpServlet {
       // TODO(hiesel): Remove URL ordainer as parameter once Soy is consistent
       ImmutableMap<String, Object> templateData =
           IndexHtmlUtil.templateData(
-              gerritApi, canonicalUrl, cdnPath, faviconPath, parameterMap, urlOrdainer, requestUrl);
+              gerritApi, canonicalUrl, cdnPath, faviconPath, parameterMap, urlOrdainer, requestUrl, titleComputer);
       renderer = soySauce.renderTemplate("com.google.gerrit.httpd.raw.Index").setData(templateData);
     } catch (URISyntaxException | RestApiException e) {
       throw new IOException(e);
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index 4b2c8a9733..466fbaa27c 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -221,11 +221,12 @@ public class StaticModule extends ServletModule {
     HttpServlet getPolyGerritUiIndexServlet(
         @CanonicalWebUrl @Nullable String canonicalUrl,
         @GerritServerConfig Config cfg,
-        GerritApi gerritApi) {
+        GerritApi gerritApi,
+        TitleComputer titleComputer) {
       String cdnPath =
           options.useDevCdn() ? options.devCdn() : cfg.getString("gerrit", null, "cdnPath");
       String faviconPath = cfg.getString("gerrit", null, "faviconPath");
-      return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi);
+      return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi, titleComputer);
     }
 
     @Provides
diff --git a/java/com/google/gerrit/httpd/raw/TitleComputer.java b/java/com/google/gerrit/httpd/raw/TitleComputer.java
new file mode 100644
index 0000000000..8fd2053ad0
--- /dev/null
+++ b/java/com/google/gerrit/httpd/raw/TitleComputer.java
@@ -0,0 +1,67 @@
+package com.google.gerrit.httpd.raw;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.restapi.change.ChangesCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Singleton
+public class TitleComputer {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  @Inject
+  public TitleComputer(Provider<ChangesCollection> changes) {
+    this.changes = changes;
+  }
+
+  public Optional<String> computeTitle(String requestedURI) {
+    URL url = null;
+    try {
+      url = new URL(requestedURI);
+    } catch (MalformedURLException e) {
+      logger.atWarning().log("Failed to turn %s into a URL.", requestedURI);
+      return Optional.empty();
+    }
+
+    // Try to turn this into a change.
+    Optional<Change.Id> changeId = tryExtractChange(url.getPath());
+    if (changeId.isPresent()) {
+      return titleFromChangeId(changeId.get());
+    }
+
+    return Optional.empty();
+  }
+
+  private static final Pattern extractChangeIdRegex = Pattern.compile("^/(?:c/.*/\\+/)?(?<changeId>[0-9]+)(?:/[0-9]+)?(?:/.*)?$");
+  private final Provider<ChangesCollection> changes;
+
+  private Optional<Change.Id> tryExtractChange(String path) {
+    Matcher m = extractChangeIdRegex.matcher(path);
+    if (!m.matches()) {
+      return Optional.empty();
+    }
+    return Change.Id.tryParse(m.group("changeId"));
+  }
+
+  private Optional<String> titleFromChangeId(Change.Id changeId) {
+    ChangesCollection changesCollection = changes.get();
+    try {
+      ChangeResource changeResource = changesCollection.parse(changeId);
+      return Optional.of(changeResource.getChange().getSubject());
+    } catch (ResourceConflictException | ResourceNotFoundException | PermissionBackendException e) {
+      return Optional.empty();
+    }
+  }
+}
diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index 000f4e2b5f..fcc26f7f54 100644
--- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -33,10 +33,12 @@
   {@param? defaultDashboardHex: ?}
   {@param? dashboardQuery: ?}
   {@param? userIsAuthenticated: ?}
+  {@param? title: ?}
   <!DOCTYPE html>{\n}
   <html lang="en">{\n}
   <meta charset="utf-8">{\n}
-  <meta name="description" content="Gerrit Code Review">{\n}
+  {if $title}<title>{$title} · Gerrit Code Review</title>{\n}{/if}
+  <meta name="description" content="{if $title}{$title} · {/if}Gerrit Code Review">{\n}
   <meta name="referrer" content="never">{\n}
   <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">{\n}
 
-- 
2.29.2