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:
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user