Merge "Ship initally required data in index.html"
This commit is contained in:
@@ -14,12 +14,21 @@
|
|||||||
|
|
||||||
package com.google.gerrit.httpd.raw;
|
package com.google.gerrit.httpd.raw;
|
||||||
|
|
||||||
|
import static com.google.template.soy.data.ordainers.GsonOrdainer.serializeObject;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
import com.google.gerrit.common.Nullable;
|
import com.google.gerrit.common.Nullable;
|
||||||
|
import com.google.gerrit.extensions.api.GerritApi;
|
||||||
|
import com.google.gerrit.extensions.api.accounts.AccountApi;
|
||||||
|
import com.google.gerrit.extensions.api.config.Server;
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
|
import com.google.gerrit.json.OutputFormat;
|
||||||
|
import com.google.gson.Gson;
|
||||||
import com.google.template.soy.SoyFileSet;
|
import com.google.template.soy.SoyFileSet;
|
||||||
import com.google.template.soy.data.SanitizedContent;
|
import com.google.template.soy.data.SanitizedContent;
|
||||||
import com.google.template.soy.data.SoyMapData;
|
import com.google.template.soy.data.SoyMapData;
|
||||||
@@ -29,37 +38,58 @@ import java.io.IOException;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
public class IndexServlet extends HttpServlet {
|
public class IndexServlet extends HttpServlet {
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
protected final byte[] indexSource;
|
|
||||||
|
@Nullable private final String canonicalUrl;
|
||||||
|
@Nullable private final String cdnPath;
|
||||||
|
@Nullable private final String faviconPath;
|
||||||
|
private final GerritApi gerritApi;
|
||||||
|
private final SoyTofu soyTofu;
|
||||||
|
|
||||||
IndexServlet(
|
IndexServlet(
|
||||||
@Nullable String canonicalURL, @Nullable String cdnPath, @Nullable String faviconPath)
|
@Nullable String canonicalUrl,
|
||||||
throws URISyntaxException {
|
@Nullable String cdnPath,
|
||||||
String resourcePath = "com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy";
|
@Nullable String faviconPath,
|
||||||
SoyFileSet.Builder builder = SoyFileSet.builder();
|
GerritApi gerritApi) {
|
||||||
builder.add(Resources.getResource(resourcePath));
|
this.canonicalUrl = canonicalUrl;
|
||||||
SoyTofu.Renderer renderer =
|
this.cdnPath = cdnPath;
|
||||||
builder
|
this.faviconPath = faviconPath;
|
||||||
|
this.gerritApi = gerritApi;
|
||||||
|
this.soyTofu =
|
||||||
|
SoyFileSet.builder()
|
||||||
|
.add(Resources.getResource("com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy"))
|
||||||
.build()
|
.build()
|
||||||
.compileToTofu()
|
.compileToTofu();
|
||||||
.newRenderer("com.google.gerrit.httpd.raw.Index")
|
|
||||||
.setContentKind(SanitizedContent.ContentKind.HTML)
|
|
||||||
.setData(getTemplateData(canonicalURL, cdnPath, faviconPath));
|
|
||||||
indexSource = renderer.render().getBytes(UTF_8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
|
protected void doGet(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
|
||||||
|
SoyTofu.Renderer renderer;
|
||||||
|
try {
|
||||||
|
SoyMapData templateData = getStaticTemplateData(canonicalUrl, cdnPath, faviconPath);
|
||||||
|
templateData.put("gerritInitialData", getInitialData());
|
||||||
|
renderer =
|
||||||
|
soyTofu
|
||||||
|
.newRenderer("com.google.gerrit.httpd.raw.Index")
|
||||||
|
.setContentKind(SanitizedContent.ContentKind.HTML)
|
||||||
|
.setData(templateData);
|
||||||
|
} catch (URISyntaxException | RestApiException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
|
||||||
rsp.setCharacterEncoding(UTF_8.name());
|
rsp.setCharacterEncoding(UTF_8.name());
|
||||||
rsp.setContentType("text/html");
|
rsp.setContentType("text/html");
|
||||||
rsp.setStatus(SC_OK);
|
rsp.setStatus(SC_OK);
|
||||||
try (OutputStream w = rsp.getOutputStream()) {
|
try (OutputStream w = rsp.getOutputStream()) {
|
||||||
w.write(indexSource);
|
w.write(renderer.render().getBytes(UTF_8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +104,7 @@ public class IndexServlet extends HttpServlet {
|
|||||||
return uri.getPath().replaceAll("/$", "");
|
return uri.getPath().replaceAll("/$", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
static SoyMapData getTemplateData(String canonicalURL, String cdnPath, String faviconPath)
|
static SoyMapData getStaticTemplateData(String canonicalURL, String cdnPath, String faviconPath)
|
||||||
throws URISyntaxException {
|
throws URISyntaxException {
|
||||||
String canonicalPath = computeCanonicalPath(canonicalURL);
|
String canonicalPath = computeCanonicalPath(canonicalURL);
|
||||||
|
|
||||||
@@ -96,4 +126,33 @@ public class IndexServlet extends HttpServlet {
|
|||||||
"staticResourcePath", sanitizedStaticPath,
|
"staticResourcePath", sanitizedStaticPath,
|
||||||
"faviconPath", faviconPath);
|
"faviconPath", faviconPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, SanitizedContent> getInitialData() throws RestApiException {
|
||||||
|
Gson gson = OutputFormat.JSON_COMPACT.newGson();
|
||||||
|
Map<String, SanitizedContent> initialData = new HashMap<>();
|
||||||
|
Server serverApi = gerritApi.config().server();
|
||||||
|
initialData.put("\"/config/server/info\"", serializeObject(gson, serverApi.getInfo()));
|
||||||
|
initialData.put("\"/config/server/version\"", serializeObject(gson, serverApi.getVersion()));
|
||||||
|
initialData.put("\"/config/server/top-menus\"", serializeObject(gson, serverApi.topMenus()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
AccountApi accountApi = gerritApi.accounts().self();
|
||||||
|
initialData.put("\"/accounts/self/detail\"", serializeObject(gson, accountApi.get()));
|
||||||
|
initialData.put(
|
||||||
|
"\"/accounts/self/preferences\"", serializeObject(gson, accountApi.getPreferences()));
|
||||||
|
initialData.put(
|
||||||
|
"\"/accounts/self/preferences.diff\"",
|
||||||
|
serializeObject(gson, accountApi.getDiffPreferences()));
|
||||||
|
initialData.put(
|
||||||
|
"\"/accounts/self/preferences.edit\"",
|
||||||
|
serializeObject(gson, accountApi.getEditPreferences()));
|
||||||
|
} catch (AuthException e) {
|
||||||
|
logger.atFine().withCause(e).log(
|
||||||
|
"Can't inline account-related data because user is unauthenticated");
|
||||||
|
// Don't render data
|
||||||
|
// TODO(hiesel): Tell the client that the user is not authenticated so that it doesn't have to
|
||||||
|
// fetch anyway. This requires more client side modifications.
|
||||||
|
}
|
||||||
|
return initialData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ import com.google.common.cache.Cache;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import com.google.gerrit.common.Nullable;
|
import com.google.gerrit.common.Nullable;
|
||||||
|
import com.google.gerrit.extensions.api.GerritApi;
|
||||||
import com.google.gerrit.httpd.XsrfCookieFilter;
|
import com.google.gerrit.httpd.XsrfCookieFilter;
|
||||||
import com.google.gerrit.httpd.raw.ResourceServlet.Resource;
|
import com.google.gerrit.httpd.raw.ResourceServlet.Resource;
|
||||||
import com.google.gerrit.launcher.GerritLauncher;
|
import com.google.gerrit.launcher.GerritLauncher;
|
||||||
@@ -41,7 +42,6 @@ import com.google.inject.servlet.ServletModule;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
@@ -218,11 +218,12 @@ public class StaticModule extends ServletModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Named(POLYGERRIT_INDEX_SERVLET)
|
@Named(POLYGERRIT_INDEX_SERVLET)
|
||||||
HttpServlet getPolyGerritUiIndexServlet(
|
HttpServlet getPolyGerritUiIndexServlet(
|
||||||
@CanonicalWebUrl @Nullable String canonicalUrl, @GerritServerConfig Config cfg)
|
@CanonicalWebUrl @Nullable String canonicalUrl,
|
||||||
throws URISyntaxException {
|
@GerritServerConfig Config cfg,
|
||||||
|
GerritApi gerritApi) {
|
||||||
String cdnPath = cfg.getString("gerrit", null, "cdnPath");
|
String cdnPath = cfg.getString("gerrit", null, "cdnPath");
|
||||||
String faviconPath = cfg.getString("gerrit", null, "faviconPath");
|
String faviconPath = cfg.getString("gerrit", null, "faviconPath");
|
||||||
return new IndexServlet(canonicalUrl, cdnPath, faviconPath);
|
return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@@ -15,53 +15,52 @@
|
|||||||
package com.google.gerrit.httpd.raw;
|
package com.google.gerrit.httpd.raw;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static org.easymock.EasyMock.createMock;
|
||||||
|
import static org.easymock.EasyMock.expect;
|
||||||
|
import static org.easymock.EasyMock.replay;
|
||||||
|
import static org.easymock.EasyMock.verify;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.gerrit.extensions.api.GerritApi;
|
||||||
|
import com.google.gerrit.extensions.api.accounts.Accounts;
|
||||||
|
import com.google.gerrit.extensions.api.config.Config;
|
||||||
|
import com.google.gerrit.extensions.api.config.Server;
|
||||||
|
import com.google.gerrit.extensions.common.ServerInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
|
||||||
|
import com.google.gerrit.util.http.testutil.FakeHttpServletResponse;
|
||||||
import com.google.template.soy.data.SoyMapData;
|
import com.google.template.soy.data.SoyMapData;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class IndexServletTest {
|
public class IndexServletTest {
|
||||||
static class TestIndexServlet extends IndexServlet {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
TestIndexServlet(String canonicalURL, String cdnPath, String faviconPath)
|
|
||||||
throws URISyntaxException {
|
|
||||||
super(canonicalURL, cdnPath, faviconPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getIndexSource() {
|
|
||||||
return new String(indexSource, UTF_8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noPathAndNoCDN() throws URISyntaxException {
|
public void noPathAndNoCDN() throws Exception {
|
||||||
SoyMapData data = IndexServlet.getTemplateData("http://example.com/", null, null);
|
SoyMapData data = IndexServlet.getStaticTemplateData("http://example.com/", null, null);
|
||||||
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("");
|
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("");
|
||||||
assertThat(data.getSingle("staticResourcePath").stringValue()).isEqualTo("");
|
assertThat(data.getSingle("staticResourcePath").stringValue()).isEqualTo("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void pathAndNoCDN() throws URISyntaxException {
|
public void pathAndNoCDN() throws Exception {
|
||||||
SoyMapData data = IndexServlet.getTemplateData("http://example.com/gerrit/", null, null);
|
SoyMapData data = IndexServlet.getStaticTemplateData("http://example.com/gerrit/", null, null);
|
||||||
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("/gerrit");
|
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("/gerrit");
|
||||||
assertThat(data.getSingle("staticResourcePath").stringValue()).isEqualTo("/gerrit");
|
assertThat(data.getSingle("staticResourcePath").stringValue()).isEqualTo("/gerrit");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noPathAndCDN() throws URISyntaxException {
|
public void noPathAndCDN() throws Exception {
|
||||||
SoyMapData data =
|
SoyMapData data =
|
||||||
IndexServlet.getTemplateData("http://example.com/", "http://my-cdn.com/foo/bar/", null);
|
IndexServlet.getStaticTemplateData(
|
||||||
|
"http://example.com/", "http://my-cdn.com/foo/bar/", null);
|
||||||
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("");
|
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("");
|
||||||
assertThat(data.getSingle("staticResourcePath").stringValue())
|
assertThat(data.getSingle("staticResourcePath").stringValue())
|
||||||
.isEqualTo("http://my-cdn.com/foo/bar/");
|
.isEqualTo("http://my-cdn.com/foo/bar/");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void pathAndCDN() throws URISyntaxException {
|
public void pathAndCDN() throws Exception {
|
||||||
SoyMapData data =
|
SoyMapData data =
|
||||||
IndexServlet.getTemplateData(
|
IndexServlet.getStaticTemplateData(
|
||||||
"http://example.com/gerrit", "http://my-cdn.com/foo/bar/", null);
|
"http://example.com/gerrit", "http://my-cdn.com/foo/bar/", null);
|
||||||
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("/gerrit");
|
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("/gerrit");
|
||||||
assertThat(data.getSingle("staticResourcePath").stringValue())
|
assertThat(data.getSingle("staticResourcePath").stringValue())
|
||||||
@@ -69,12 +68,45 @@ public class IndexServletTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void renderTemplate() throws URISyntaxException {
|
public void renderTemplate() throws Exception {
|
||||||
|
Accounts accountsApi = createMock(Accounts.class);
|
||||||
|
expect(accountsApi.self()).andThrow(new AuthException("user needs to be authenticated"));
|
||||||
|
|
||||||
|
Server serverApi = createMock(Server.class);
|
||||||
|
expect(serverApi.getVersion()).andReturn("123");
|
||||||
|
expect(serverApi.topMenus()).andReturn(ImmutableList.of());
|
||||||
|
ServerInfo serverInfo = new ServerInfo();
|
||||||
|
serverInfo.defaultTheme = "my-default-theme";
|
||||||
|
expect(serverApi.getInfo()).andReturn(serverInfo);
|
||||||
|
|
||||||
|
Config configApi = createMock(Config.class);
|
||||||
|
expect(configApi.server()).andReturn(serverApi);
|
||||||
|
|
||||||
|
GerritApi gerritApi = createMock(GerritApi.class);
|
||||||
|
expect(gerritApi.accounts()).andReturn(accountsApi);
|
||||||
|
expect(gerritApi.config()).andReturn(configApi);
|
||||||
|
|
||||||
String testCanonicalUrl = "foo-url";
|
String testCanonicalUrl = "foo-url";
|
||||||
String testCdnPath = "bar-cdn";
|
String testCdnPath = "bar-cdn";
|
||||||
String testFaviconURL = "zaz-url";
|
String testFaviconURL = "zaz-url";
|
||||||
TestIndexServlet servlet = new TestIndexServlet(testCanonicalUrl, testCdnPath, testFaviconURL);
|
IndexServlet servlet =
|
||||||
String output = servlet.getIndexSource();
|
new IndexServlet(testCanonicalUrl, testCdnPath, testFaviconURL, gerritApi);
|
||||||
|
|
||||||
|
FakeHttpServletResponse response = new FakeHttpServletResponse();
|
||||||
|
|
||||||
|
replay(gerritApi);
|
||||||
|
replay(configApi);
|
||||||
|
replay(serverApi);
|
||||||
|
replay(accountsApi);
|
||||||
|
|
||||||
|
servlet.doGet(new FakeHttpServletRequest(), response);
|
||||||
|
|
||||||
|
verify(gerritApi);
|
||||||
|
verify(configApi);
|
||||||
|
verify(serverApi);
|
||||||
|
verify(accountsApi);
|
||||||
|
|
||||||
|
String output = response.getActualBodyString();
|
||||||
assertThat(output).contains("<!DOCTYPE html>");
|
assertThat(output).contains("<!DOCTYPE html>");
|
||||||
assertThat(output).contains("window.CANONICAL_PATH = '" + testCanonicalUrl);
|
assertThat(output).contains("window.CANONICAL_PATH = '" + testCanonicalUrl);
|
||||||
assertThat(output).contains("<link rel=\"preload\" href=\"" + testCdnPath);
|
assertThat(output).contains("<link rel=\"preload\" href=\"" + testCdnPath);
|
||||||
@@ -84,5 +116,12 @@ public class IndexServletTest {
|
|||||||
+ testCanonicalUrl
|
+ testCanonicalUrl
|
||||||
+ "/"
|
+ "/"
|
||||||
+ testFaviconURL);
|
+ testFaviconURL);
|
||||||
|
assertThat(output)
|
||||||
|
.contains(
|
||||||
|
"window.INITIAL_DATA = JSON.parse("
|
||||||
|
+ "'\\x7b\\x22\\/config\\/server\\/version\\x22: \\x22123\\x22, "
|
||||||
|
+ "\\x22\\/config\\/server\\/info\\x22: \\x7b\\x22default_theme\\x22:"
|
||||||
|
+ "\\x22my-default-theme\\x22\\x7d, \\x22\\/config\\/server\\/top-menus\\x22: "
|
||||||
|
+ "\\x5b\\x5d\\x7d');</script>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -144,6 +144,14 @@
|
|||||||
constructor() {
|
constructor() {
|
||||||
// Container of per-canonical-path caches.
|
// Container of per-canonical-path caches.
|
||||||
this._data = new Map();
|
this._data = new Map();
|
||||||
|
if (window.INITIAL_DATA != undefined) {
|
||||||
|
// Put all data shipped with index.html into the cache. This makes it
|
||||||
|
// so that we spare more round trips to the server when the app loads
|
||||||
|
// initially.
|
||||||
|
Object
|
||||||
|
.entries(window.INITIAL_DATA)
|
||||||
|
.forEach(e => this._cache().set(e[0], e[1]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the cache for the current canonical path.
|
// Returns the cache for the current canonical path.
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
{template .Index}
|
{template .Index}
|
||||||
{@param canonicalPath: ?}
|
{@param canonicalPath: ?}
|
||||||
{@param staticResourcePath: ?}
|
{@param staticResourcePath: ?}
|
||||||
|
{@param gerritInitialData: /** {string} map of REST endpoint to response for startup. */ ?}
|
||||||
{@param? assetsPath: ?} /** {string} URL to static assets root, if served from CDN. */
|
{@param? assetsPath: ?} /** {string} URL to static assets root, if served from CDN. */
|
||||||
{@param? assetsBundle: ?} /** {string} Assets bundle .html file, served from $assetsPath. */
|
{@param? assetsBundle: ?} /** {string} Assets bundle .html file, served from $assetsPath. */
|
||||||
{@param? faviconPath: ?}
|
{@param? faviconPath: ?}
|
||||||
@@ -42,6 +43,17 @@
|
|||||||
{if $staticResourcePath != ''}window.STATIC_RESOURCE_PATH = '{$staticResourcePath}';{/if}
|
{if $staticResourcePath != ''}window.STATIC_RESOURCE_PATH = '{$staticResourcePath}';{/if}
|
||||||
{if $assetsPath}window.ASSETS_PATH = '{$assetsPath}';{/if}
|
{if $assetsPath}window.ASSETS_PATH = '{$assetsPath}';{/if}
|
||||||
{if $polymer2}window.POLYMER2 = true;{/if}
|
{if $polymer2}window.POLYMER2 = true;{/if}
|
||||||
|
{if $gerritInitialData}
|
||||||
|
// INITIAL_DATA is a string that represents a JSON map. It's inlined here so that we can
|
||||||
|
// spare calls to the API when starting up the app.
|
||||||
|
// The map maps from endpoint to returned value. This matches Gerrit's REST API 1:1, so the
|
||||||
|
// values here can be used as a drop-in replacement for calls to the API.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// '/config/server/version' => '3.0.0-468-g0757b52a7d'
|
||||||
|
// '/accounts/self/detail' => { 'username' : 'gerrit-user' }
|
||||||
|
window.INITIAL_DATA = JSON.parse({$gerritInitialData});
|
||||||
|
{/if}
|
||||||
</script>{\n}
|
</script>{\n}
|
||||||
|
|
||||||
{if $faviconPath}
|
{if $faviconPath}
|
||||||
|
Reference in New Issue
Block a user