From 585077e751107729b7a2dd495a3b17c677a3f528 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 3/3] Add titles to CLs over HTTP --- .../gerrit/httpd/raw/IndexHtmlUtil.java | 13 +++- .../google/gerrit/httpd/raw/IndexServlet.java | 8 ++- .../google/gerrit/httpd/raw/StaticModule.java | 5 +- .../gerrit/httpd/raw/TitleComputer.java | 67 +++++++++++++++++++ .../gerrit/httpd/raw/PolyGerritIndexHtml.soy | 4 +- 5 files changed, 89 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 ce22ae8e59..952ba1fef4 100644 --- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java +++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java @@ -40,6 +40,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; @@ -61,13 +62,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 = experimentFeatures.getEnabledExperimentFeatures(); if (!enabledExperiments.isEmpty()) { @@ -78,7 +80,8 @@ 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(); @@ -131,6 +134,10 @@ public class IndexHtmlUtil { } data.put("gerritInitialData", initialData); + + Optional<String> title = titleComputer.computeTitle(requestedURL); + title.ifPresent(s -> data.put("title", s)); + return data.build(); } diff --git a/java/com/google/gerrit/httpd/raw/IndexServlet.java b/java/com/google/gerrit/httpd/raw/IndexServlet.java index fcb821e5ae..36eb0c990c 100644 --- a/java/com/google/gerrit/httpd/raw/IndexServlet.java +++ b/java/com/google/gerrit/httpd/raw/IndexServlet.java @@ -48,13 +48,15 @@ public class IndexServlet extends HttpServlet { private final ExperimentFeatures experimentFeatures; 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, - ExperimentFeatures experimentFeatures) { + ExperimentFeatures experimentFeatures, + TitleComputer titleComputer) { this.canonicalUrl = canonicalUrl; this.cdnPath = cdnPath; this.faviconPath = faviconPath; @@ -69,6 +71,7 @@ public class IndexServlet extends HttpServlet { (s) -> UnsafeSanitizedContentOrdainer.ordainAsSafe( s, SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI); + this.titleComputer = titleComputer; } @Override @@ -86,7 +89,8 @@ public class IndexServlet extends HttpServlet { faviconPath, parameterMap, urlOrdainer, - getRequestUrl(req)); + getRequestUrl(req), + 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 8e8a9d27f0..a0fb168554 100644 --- a/java/com/google/gerrit/httpd/raw/StaticModule.java +++ b/java/com/google/gerrit/httpd/raw/StaticModule.java @@ -226,10 +226,11 @@ public class StaticModule extends ServletModule { @CanonicalWebUrl @Nullable String canonicalUrl, @GerritServerConfig Config cfg, GerritApi gerritApi, - ExperimentFeatures experimentFeatures) { + ExperimentFeatures experimentFeatures, + TitleComputer titleComputer) { String cdnPath = options.devCdn().orElse(cfg.getString("gerrit", null, "cdnPath")); String faviconPath = cfg.getString("gerrit", null, "faviconPath"); - return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi, experimentFeatures); + return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi, experimentFeatures, 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 8c97a49e81..129092dc7e 100644 --- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy +++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy @@ -32,10 +32,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.36.0