diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java index 41d2f83975..323567b4a4 100644 --- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java +++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java @@ -42,6 +42,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; import java.util.regex.Matcher; @@ -110,7 +111,8 @@ public class IndexHtmlUtil { String faviconPath, Map urlParameterMap, Function urlInScriptTagOrdainer, - String requestedURL) + String requestedURL, + TitleComputer titleComputer) throws URISyntaxException, RestApiException { ImmutableMap.Builder data = ImmutableMap.builder(); data.putAll( @@ -121,7 +123,7 @@ public class IndexHtmlUtil { urlParameterMap, urlInScriptTagOrdainer, requestedURL)) - .putAll(dynamicTemplateData(gerritApi)); + .putAll(dynamicTemplateData(gerritApi, requestedURL, titleComputer)); Set enabledExperiments = experimentData(urlParameterMap); if (!enabledExperiments.isEmpty()) { @@ -131,7 +133,9 @@ public class IndexHtmlUtil { } /** Returns dynamic parameters of {@code index.html}. */ - public static ImmutableMap dynamicTemplateData(GerritApi gerritApi) + public static ImmutableMap dynamicTemplateData(GerritApi gerritApi, + String requestedURL, + TitleComputer titleComputer) throws RestApiException { ImmutableMap.Builder data = ImmutableMap.builder(); Map initialData = new HashMap<>(); @@ -159,6 +163,10 @@ public class IndexHtmlUtil { } data.put("gerritInitialData", initialData); + + Optional 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 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 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 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 414a120194..e1b6fb082d 100644 --- a/java/com/google/gerrit/httpd/raw/StaticModule.java +++ b/java/com/google/gerrit/httpd/raw/StaticModule.java @@ -220,11 +220,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..efee24607c --- /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 changes) { + this.changes = changes; + } + + public Optional 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 changeId = tryExtractChange(url.getPath()); + if (changeId.isPresent()) { + return titleFromChangeId(changeId.get()); + } + + return Optional.empty(); + } + + private static final Pattern extractChangeIdRegex = Pattern.compile("^/(?:c/.*/\\+/)?(?[0-9]+)(?:/[0-9]+)?$"); + private final Provider changes; + + private Optional tryExtractChange(String path) { + Matcher m = extractChangeIdRegex.matcher(path); + if (!m.matches()) { + return Optional.empty(); + } + return Change.Id.tryParse(m.group("changeId")); + } + + private Optional 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 d162714399..0ba228ad00 100644 --- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy +++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy @@ -33,10 +33,12 @@ {@param? preloadChangePage: ?} {@param? preloadDiffPage: ?} {@param? userIsAuthenticated: ?} + {@param? title: ?} {\n} {\n} {\n} - {\n} + {if $title}{$title} · Gerrit Code Review{\n}{/if} + {\n} {\n} {\n}