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