From 32bf13d8316f93828d2ff47ccfca38d4e7a634b1 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/7] 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 | 6 +- .../gerrit/httpd/raw/TitleComputer.java | 67 +++++++++++++++++++ .../gerrit/httpd/raw/PolyGerritIndexHtml.soy | 4 +- 5 files changed, 90 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 8d52f5ad50..a9cfceb3b6 100644 --- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java +++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java @@ -39,6 +39,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; @@ -60,13 +61,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()) { @@ -77,7 +79,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(); @@ -128,6 +132,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 3f2c2028ae..7861c007df 100644 --- a/java/com/google/gerrit/httpd/raw/IndexServlet.java +++ b/java/com/google/gerrit/httpd/raw/IndexServlet.java @@ -46,13 +46,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; @@ -67,6 +69,7 @@ public class IndexServlet extends HttpServlet { (s) -> UnsafeSanitizedContentOrdainer.ordainAsSafe( s, SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI); + this.titleComputer = titleComputer; } @Override @@ -85,7 +88,8 @@ public class IndexServlet extends HttpServlet { faviconPath, parameterMap, urlOrdainer, - requestUrl); + 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 bb1eb92525..6b20c504d2 100644 --- a/java/com/google/gerrit/httpd/raw/StaticModule.java +++ b/java/com/google/gerrit/httpd/raw/StaticModule.java @@ -224,11 +224,13 @@ public class StaticModule extends ServletModule { @CanonicalWebUrl @Nullable String canonicalUrl, @GerritServerConfig Config cfg, GerritApi gerritApi, - ExperimentFeatures experimentFeatures) { + ExperimentFeatures experimentFeatures, + 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, 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 11717fb8a4..1ae9046360 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.32.0