Merge changes I2b470424,I8b45988a,Ib1f79cf4 into stable-3.3

* changes:
  Ship a list of default experiments from the backend and allow disabling
  Revert "Clean up comment experiment flags"
  Make UI experiments configurable from gerrit.config
This commit is contained in:
Patrick Hiesel
2020-12-22 10:55:00 +00:00
committed by Gerrit Code Review
8 changed files with 108 additions and 21 deletions

View File

@@ -3358,6 +3358,37 @@ Defaults to all available options minus `CHANGE_ACTIONS`,
config is backwards compatible with what the default was before the config config is backwards compatible with what the default was before the config
was added. was added.
[[experiments]]
=== Section experiments
This section covers experimental new features. Gerrit's frontend uses experiments
to research new behavior. Once the research is done, the experimental feature
either stays and the experimentation flag gets removed, or the feature as a whole
gets removed
[[experiments.enabled]]experiments.enabled::
+
List of experiments that are currently enabled. The release notes contain currently
available experiments.
+
We will not remove experiments in stable patch releases. They are likely to be
removed in the next stable version.
----
[experiments]
enabled = ExperimentKey
----
[[experiments.disabled]]experiments.disabled::
+
List of experiments that are currently disabled. The release notes contain currently
available experiments. This list disables experiments with the given key that are
either enabled by default or explicitly in the config.
----
[experiments]
disabled = ExperimentKey
----
[[ldap]] [[ldap]]
=== Section ldap === Section ldap

View File

@@ -20,6 +20,7 @@ import static java.util.stream.Collectors.toSet;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
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.common.UsedAt; import com.google.gerrit.common.UsedAt;
@@ -37,15 +38,21 @@ import java.net.URISyntaxException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import org.eclipse.jgit.lib.Config;
/** Helper for generating parts of {@code index.html}. */ /** Helper for generating parts of {@code index.html}. */
@UsedAt(Project.GOOGLE) @UsedAt(Project.GOOGLE)
public class IndexHtmlUtil { public class IndexHtmlUtil {
private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final ImmutableSet<String> DEFAULT_EXPERIMENTS =
ImmutableSet.of(
"UiFeature__patchset_comments", "UiFeature__patchset_choice_for_comment_links");
private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson(); private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
/** /**
* Returns both static and dynamic parameters of {@code index.html}. The result is to be used when * Returns both static and dynamic parameters of {@code index.html}. The result is to be used when
@@ -53,6 +60,7 @@ public class IndexHtmlUtil {
*/ */
public static ImmutableMap<String, Object> templateData( public static ImmutableMap<String, Object> templateData(
GerritApi gerritApi, GerritApi gerritApi,
Config gerritServerConfig,
String canonicalURL, String canonicalURL,
String cdnPath, String cdnPath,
String faviconPath, String faviconPath,
@@ -66,7 +74,13 @@ public class IndexHtmlUtil {
canonicalURL, cdnPath, faviconPath, urlParameterMap, urlInScriptTagOrdainer)) canonicalURL, cdnPath, faviconPath, urlParameterMap, urlInScriptTagOrdainer))
.putAll(dynamicTemplateData(gerritApi, requestedURL)); .putAll(dynamicTemplateData(gerritApi, requestedURL));
Set<String> enabledExperiments = experimentData(urlParameterMap); Set<String> enabledExperiments = new HashSet<>();
Arrays.stream(gerritServerConfig.getStringList("experiments", null, "enabled"))
.forEach(enabledExperiments::add);
DEFAULT_EXPERIMENTS.forEach(enabledExperiments::add);
Arrays.stream(gerritServerConfig.getStringList("experiments", null, "disabled"))
.forEach(enabledExperiments::remove);
experimentData(urlParameterMap).forEach(enabledExperiments::add);
if (!enabledExperiments.isEmpty()) { if (!enabledExperiments.isEmpty()) {
data.put("enabledExperiments", serializeObject(GSON, enabledExperiments).toString()); data.put("enabledExperiments", serializeObject(GSON, enabledExperiments).toString());
} }

View File

@@ -34,6 +34,7 @@ import java.util.function.Function;
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;
import org.eclipse.jgit.lib.Config;
public class IndexServlet extends HttpServlet { public class IndexServlet extends HttpServlet {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -42,6 +43,7 @@ public class IndexServlet extends HttpServlet {
@Nullable private final String cdnPath; @Nullable private final String cdnPath;
@Nullable private final String faviconPath; @Nullable private final String faviconPath;
private final GerritApi gerritApi; private final GerritApi gerritApi;
private final Config gerritServerConfig;
private final SoySauce soySauce; private final SoySauce soySauce;
private final Function<String, SanitizedContent> urlOrdainer; private final Function<String, SanitizedContent> urlOrdainer;
@@ -49,11 +51,13 @@ public class IndexServlet extends HttpServlet {
@Nullable String canonicalUrl, @Nullable String canonicalUrl,
@Nullable String cdnPath, @Nullable String cdnPath,
@Nullable String faviconPath, @Nullable String faviconPath,
GerritApi gerritApi) { GerritApi gerritApi,
Config gerritServerConfig) {
this.canonicalUrl = canonicalUrl; this.canonicalUrl = canonicalUrl;
this.cdnPath = cdnPath; this.cdnPath = cdnPath;
this.faviconPath = faviconPath; this.faviconPath = faviconPath;
this.gerritApi = gerritApi; this.gerritApi = gerritApi;
this.gerritServerConfig = gerritServerConfig;
this.soySauce = this.soySauce =
SoyFileSet.builder() SoyFileSet.builder()
.add(Resources.getResource("com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy")) .add(Resources.getResource("com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy"))
@@ -74,7 +78,14 @@ public class IndexServlet extends HttpServlet {
// TODO(hiesel): Remove URL ordainer as parameter once Soy is consistent // TODO(hiesel): Remove URL ordainer as parameter once Soy is consistent
ImmutableMap<String, Object> templateData = ImmutableMap<String, Object> templateData =
IndexHtmlUtil.templateData( IndexHtmlUtil.templateData(
gerritApi, canonicalUrl, cdnPath, faviconPath, parameterMap, urlOrdainer, requestUrl); gerritApi,
gerritServerConfig,
canonicalUrl,
cdnPath,
faviconPath,
parameterMap,
urlOrdainer,
requestUrl);
renderer = soySauce.renderTemplate("com.google.gerrit.httpd.raw.Index").setData(templateData); renderer = soySauce.renderTemplate("com.google.gerrit.httpd.raw.Index").setData(templateData);
} catch (URISyntaxException | RestApiException e) { } catch (URISyntaxException | RestApiException e) {
throw new IOException(e); throw new IOException(e);

View File

@@ -225,7 +225,7 @@ public class StaticModule extends ServletModule {
String cdnPath = String cdnPath =
options.useDevCdn() ? options.devCdn() : cfg.getString("gerrit", null, "cdnPath"); options.useDevCdn() ? options.devCdn() : 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, gerritApi); return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi, cfg);
} }
@Provides @Provides

View File

@@ -15,6 +15,7 @@
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.util.stream.Collectors.joining;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -53,8 +54,15 @@ public class IndexServletTest {
String testCanonicalUrl = "foo-url"; String testCanonicalUrl = "foo-url";
String testCdnPath = "bar-cdn"; String testCdnPath = "bar-cdn";
String testFaviconURL = "zaz-url"; String testFaviconURL = "zaz-url";
String disabledDefault = IndexHtmlUtil.DEFAULT_EXPERIMENTS.asList().get(0);
org.eclipse.jgit.lib.Config serverConfig = new org.eclipse.jgit.lib.Config();
serverConfig.setStringList(
"experiments", null, "enabled", ImmutableList.of("NewFeature", "DisabledFeature"));
serverConfig.setStringList(
"experiments", null, "disabled", ImmutableList.of("DisabledFeature", disabledDefault));
IndexServlet servlet = IndexServlet servlet =
new IndexServlet(testCanonicalUrl, testCdnPath, testFaviconURL, gerritApi); new IndexServlet(testCanonicalUrl, testCdnPath, testFaviconURL, gerritApi, serverConfig);
FakeHttpServletResponse response = new FakeHttpServletResponse(); FakeHttpServletResponse response = new FakeHttpServletResponse();
@@ -76,6 +84,15 @@ public class IndexServletTest {
+ "'\\x7b\\x22\\/config\\/server\\/version\\x22: \\x22123\\x22, " + "'\\x7b\\x22\\/config\\/server\\/version\\x22: \\x22123\\x22, "
+ "\\x22\\/config\\/server\\/info\\x22: \\x7b\\x22default_theme\\x22:" + "\\x22\\/config\\/server\\/info\\x22: \\x7b\\x22default_theme\\x22:"
+ "\\x22my-default-theme\\x22\\x7d, \\x22\\/config\\/server\\/top-menus\\x22: " + "\\x22my-default-theme\\x22\\x7d, \\x22\\/config\\/server\\/top-menus\\x22: "
+ "\\x5b\\x5d\\x7d');</script>"); + "\\x5b\\x5d\\x7d');");
String enabledDefaults =
IndexHtmlUtil.DEFAULT_EXPERIMENTS.stream()
.filter(e -> !e.equals(disabledDefault))
.collect(joining("\\x22,"));
assertThat(output)
.contains(
"window.ENABLED_EXPERIMENTS = JSON.parse('\\x5b\\x22NewFeature\\x22,\\x22"
+ enabledDefaults
+ "\\x22\\x5d');</script>");
} }
} }

View File

@@ -43,6 +43,7 @@ import {
ReviewerState, ReviewerState,
SpecialFilePath, SpecialFilePath,
} from '../../../constants/constants'; } from '../../../constants/constants';
import {KnownExperimentId} from '../../../services/flags/flags';
import {fetchChangeUpdates} from '../../../utils/patch-set-util'; import {fetchChangeUpdates} from '../../../utils/patch-set-util';
import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin'; import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {accountKey, removeServiceUsers} from '../../../utils/account-util'; import {accountKey, removeServiceUsers} from '../../../utils/account-util';
@@ -379,6 +380,8 @@ export class GrReplyDialog extends KeyboardShortcutMixin(
}; };
} }
_isPatchsetCommentsExperimentEnabled = false;
constructor() { constructor() {
super(); super();
this.filterReviewerSuggestion = this._filterReviewerSuggestionGenerator( this.filterReviewerSuggestion = this._filterReviewerSuggestionGenerator(
@@ -418,6 +421,9 @@ export class GrReplyDialog extends KeyboardShortcutMixin(
/** @override */ /** @override */
ready() { ready() {
super.ready(); super.ready();
this._isPatchsetCommentsExperimentEnabled = this.flagsService.isEnabled(
KnownExperimentId.PATCHSET_COMMENTS
);
this.$.jsAPI.addElement(TargetElement.REPLY_DIALOG, this); this.$.jsAPI.addElement(TargetElement.REPLY_DIALOG, this);
} }
@@ -680,6 +686,7 @@ export class GrReplyDialog extends KeyboardShortcutMixin(
} }
if (this.draft) { if (this.draft) {
if (this._isPatchsetCommentsExperimentEnabled) {
const comment: CommentInput = { const comment: CommentInput = {
message: this.draft, message: this.draft,
unresolved: !this._isResolvedPatchsetLevelComment, unresolved: !this._isResolvedPatchsetLevelComment,
@@ -687,6 +694,9 @@ export class GrReplyDialog extends KeyboardShortcutMixin(
reviewInput.comments = { reviewInput.comments = {
[SpecialFilePath.PATCHSET_LEVEL_COMMENTS]: [comment], [SpecialFilePath.PATCHSET_LEVEL_COMMENTS]: [comment],
}; };
} else {
reviewInput.message = this.draft;
}
} }
const accountAdditions = new Map<AccountId | EmailAddress, boolean>(); const accountAdditions = new Map<AccountId | EmailAddress, boolean>();

View File

@@ -303,6 +303,7 @@ export const htmlTemplate = html`
</gr-endpoint-decorator> </gr-endpoint-decorator>
</section> </section>
<section class="previewContainer"> <section class="previewContainer">
<template is="dom-if" if="[[_isPatchsetCommentsExperimentEnabled]]">
<label> <label>
<input <input
id="resolvedPatchsetLevelCommentCheckbox" id="resolvedPatchsetLevelCommentCheckbox"
@@ -311,6 +312,7 @@ export const htmlTemplate = html`
/> />
Resolved Resolved
</label> </label>
</template>
<label class="preview-formatting"> <label class="preview-formatting">
<input type="checkbox" checked="{{_previewFormatting::change}}" /> <input type="checkbox" checked="{{_previewFormatting::change}}" />
Preview formatting Preview formatting

View File

@@ -24,5 +24,7 @@ export interface FlagsService {
* @desc Experiment ids used in Gerrit. * @desc Experiment ids used in Gerrit.
*/ */
export enum KnownExperimentId { export enum KnownExperimentId {
PATCHSET_COMMENTS = 'UiFeature__patchset_comments',
PATCHSET_CHOICE_FOR_COMMENT_LINKS = 'UiFeature__patchset_choice_for_comment_links',
NEW_CONTEXT_CONTROLS = 'UiFeature__new_context_controls', NEW_CONTEXT_CONTROLS = 'UiFeature__new_context_controls',
} }