Merge "Avoid real name in change messages"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 47162c3..94c4552 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -5416,7 +5416,13 @@
login as the 'Gerrit Code Review' user, required for the link:cmd-suexec.html[suexec]
command.
-The format is one Base-64 encoded public key per line.
+The format is one Base-64 encoded public key per line with optional comment, e.g.:
+----
+# Comments allowed at start of line
+AAAAC3...51R== john@example.net
+# Another comment
+AAAAB5...21S== jane@example.net
+----
=== Configurable Parameters
diff --git a/java/com/google/gerrit/server/ChangeMessagesUtil.java b/java/com/google/gerrit/server/ChangeMessagesUtil.java
index dd48b93..32edadb 100644
--- a/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -35,34 +35,38 @@
@Singleton
public class ChangeMessagesUtil {
public static final String AUTOGENERATED_TAG_PREFIX = "autogenerated:";
+ public static final String AUTOGENERATED_BY_GERRIT_TAG_PREFIX =
+ AUTOGENERATED_TAG_PREFIX + "gerrit:";
- public static final String TAG_ABANDON = AUTOGENERATED_TAG_PREFIX + "gerrit:abandon";
+ public static final String TAG_ABANDON = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "abandon";
public static final String TAG_CHERRY_PICK_CHANGE =
- AUTOGENERATED_TAG_PREFIX + "gerrit:cherryPickChange";
+ AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "cherryPickChange";
public static final String TAG_DELETE_ASSIGNEE =
- AUTOGENERATED_TAG_PREFIX + "gerrit:deleteAssignee";
+ AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "deleteAssignee";
public static final String TAG_DELETE_REVIEWER =
- AUTOGENERATED_TAG_PREFIX + "gerrit:deleteReviewer";
- public static final String TAG_DELETE_VOTE = AUTOGENERATED_TAG_PREFIX + "gerrit:deleteVote";
- public static final String TAG_MERGED = AUTOGENERATED_TAG_PREFIX + "gerrit:merged";
- public static final String TAG_MOVE = AUTOGENERATED_TAG_PREFIX + "gerrit:move";
- public static final String TAG_RESTORE = AUTOGENERATED_TAG_PREFIX + "gerrit:restore";
- public static final String TAG_REVERT = AUTOGENERATED_TAG_PREFIX + "gerrit:revert";
- public static final String TAG_SET_ASSIGNEE = AUTOGENERATED_TAG_PREFIX + "gerrit:setAssignee";
+ AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "deleteReviewer";
+ public static final String TAG_DELETE_VOTE = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "deleteVote";
+ public static final String TAG_MERGED = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "merged";
+ public static final String TAG_MOVE = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "move";
+ public static final String TAG_RESTORE = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "restore";
+ public static final String TAG_REVERT = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "revert";
+ public static final String TAG_SET_ASSIGNEE = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "setAssignee";
public static final String TAG_UPDATE_ATTENTION_SET =
- AUTOGENERATED_TAG_PREFIX + "gerrit:updateAttentionSet";
+ AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "updateAttentionSet";
public static final String TAG_SET_DESCRIPTION =
- AUTOGENERATED_TAG_PREFIX + "gerrit:setPsDescription";
- public static final String TAG_SET_HASHTAGS = AUTOGENERATED_TAG_PREFIX + "gerrit:setHashtag";
- public static final String TAG_SET_PRIVATE = AUTOGENERATED_TAG_PREFIX + "gerrit:setPrivate";
- public static final String TAG_SET_READY = AUTOGENERATED_TAG_PREFIX + "gerrit:setReadyForReview";
- public static final String TAG_SET_TOPIC = AUTOGENERATED_TAG_PREFIX + "gerrit:setTopic";
- public static final String TAG_SET_WIP = AUTOGENERATED_TAG_PREFIX + "gerrit:setWorkInProgress";
- public static final String TAG_UNSET_PRIVATE = AUTOGENERATED_TAG_PREFIX + "gerrit:unsetPrivate";
+ AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "setPsDescription";
+ public static final String TAG_SET_HASHTAGS = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "setHashtag";
+ public static final String TAG_SET_PRIVATE = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "setPrivate";
+ public static final String TAG_SET_READY =
+ AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "setReadyForReview";
+ public static final String TAG_SET_TOPIC = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "setTopic";
+ public static final String TAG_SET_WIP = AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "setWorkInProgress";
+ public static final String TAG_UNSET_PRIVATE =
+ AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "unsetPrivate";
public static final String TAG_UPLOADED_PATCH_SET =
- AUTOGENERATED_TAG_PREFIX + "gerrit:newPatchSet";
+ AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "newPatchSet";
public static final String TAG_UPLOADED_WIP_PATCH_SET =
- AUTOGENERATED_TAG_PREFIX + "gerrit:newWipPatchSet";
+ AUTOGENERATED_BY_GERRIT_TAG_PREFIX + "newWipPatchSet";
public static ChangeMessage newMessage(ChangeContext ctx, String body, @Nullable String tag) {
return newMessage(ctx.getChange().currentPatchSetId(), ctx.getUser(), ctx.getWhen(), body, tag);
@@ -122,6 +126,10 @@
return tag != null && tag.startsWith(AUTOGENERATED_TAG_PREFIX);
}
+ public static boolean isAutogeneratedByGerrit(@Nullable String tag) {
+ return tag != null && tag.startsWith(AUTOGENERATED_BY_GERRIT_TAG_PREFIX);
+ }
+
public static ChangeMessageInfo createChangeMessageInfo(
ChangeMessage message, AccountLoader accountLoader) {
PatchSet.Id patchNum = message.getPatchSetId();
diff --git a/java/com/google/gerrit/server/CommentsUtil.java b/java/com/google/gerrit/server/CommentsUtil.java
index 450cbe0..e8b44aa 100644
--- a/java/com/google/gerrit/server/CommentsUtil.java
+++ b/java/com/google/gerrit/server/CommentsUtil.java
@@ -272,7 +272,9 @@
}
private static boolean isAutoGenerated(ChangeMessage cm) {
- return ChangeMessagesUtil.isAutogenerated(cm.getTag());
+ // Ignore Gerrit auto-generated messages, allowing to link against human change messages that
+ // have an auto-generated tag
+ return ChangeMessagesUtil.isAutogeneratedByGerrit(cm.getTag());
}
private static boolean isAfter(CommentInfo c, ChangeMessage cm) {
diff --git a/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 916775d..6997d96 100644
--- a/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -18,6 +18,7 @@
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.common.base.Splitter;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.BaseEncoding;
import com.google.gerrit.common.FileUtil;
@@ -38,6 +39,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.apache.sshd.common.SshException;
@@ -197,9 +199,15 @@
continue;
}
+ List<String> parts = Splitter.on(' ').splitToList(line);
+ if (parts.size() > 2) {
+ throw new IllegalArgumentException(
+ "Invalid peer key file format, only <key [comment]> lines supported");
+ }
try {
byte[] bin =
- BaseEncoding.base64().decode(new String(line.getBytes(ISO_8859_1), ISO_8859_1));
+ BaseEncoding.base64()
+ .decode(new String(parts.get(0).getBytes(ISO_8859_1), ISO_8859_1));
keys.add(new ByteArrayBuffer(bin).getRawPublicKey());
} catch (RuntimeException | SshException e) {
logBadKey(path, line, e);
diff --git a/javatests/com/google/gerrit/integration/ssh/BUILD b/javatests/com/google/gerrit/integration/ssh/BUILD
new file mode 100644
index 0000000..dc8e68c
--- /dev/null
+++ b/javatests/com/google/gerrit/integration/ssh/BUILD
@@ -0,0 +1,7 @@
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+
+acceptance_tests(
+ srcs = ["PeerKeysAuthIT.java"],
+ group = "peer-keys-auth",
+ labels = ["ssh"],
+)
diff --git a/javatests/com/google/gerrit/integration/ssh/PeerKeysAuthIT.java b/javatests/com/google/gerrit/integration/ssh/PeerKeysAuthIT.java
new file mode 100644
index 0000000..a219cc2
--- /dev/null
+++ b/javatests/com/google/gerrit/integration/ssh/PeerKeysAuthIT.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.integration.ssh;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.GerritServer.TestSshServerAddress;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.StandaloneSiteTest;
+import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.common.Version;
+import com.google.gerrit.server.PeerDaemonUser;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import org.junit.Test;
+
+@NoHttpd
+@UseSsh
+public class PeerKeysAuthIT extends StandaloneSiteTest {
+ private static final String[] SSH_KEYGEN_CMD =
+ new String[] {"ssh-keygen", "-t", "rsa", "-q", "-P", "", "-f", "id_rsa"};
+ private static final String[] SSH_COMMAND =
+ new String[] {
+ "ssh",
+ "-o",
+ "UserKnownHostsFile=/dev/null",
+ "-o",
+ "StrictHostKeyChecking=no",
+ "-o",
+ "IdentitiesOnly=yes",
+ "-i"
+ };
+
+ @Inject private @TestSshServerAddress InetSocketAddress sshAddress;
+
+ @Test
+ public void test() throws Exception {
+ try (ServerContext ctx = startServer()) {
+ ctx.getInjector().injectMembers(this);
+ // Generate private/public key for user
+ execute(ImmutableList.<String>builder().add(SSH_KEYGEN_CMD).build());
+
+ String[] parts =
+ new String(Files.readAllBytes(sitePaths.data_dir.resolve("id_rsa.pub")), UTF_8)
+ .split(" ");
+
+ // Loose algorithm at index 0, verify the format: "key comment"
+ Files.write(
+ sitePaths.peer_keys, String.format("%s %s", parts[1], parts[2]).getBytes(ISO_8859_1));
+ assertContent(execGerritVersionCommand());
+
+ // Only preserve the key material: no algorithm and no comment
+ Files.write(sitePaths.peer_keys, parts[1].getBytes(ISO_8859_1));
+ assertContent(execGerritVersionCommand());
+
+ // Wipe out the content of the peer keys file
+ Files.delete(sitePaths.peer_keys);
+ assertThrows(IOException.class, () -> execGerritVersionCommand());
+ }
+ }
+
+ private String execGerritVersionCommand() throws Exception {
+ return execute(
+ ImmutableList.<String>builder()
+ .add(SSH_COMMAND)
+ .add(sitePaths.data_dir.resolve("id_rsa").toString())
+ .add("-p " + sshAddress.getPort())
+ .add(PeerDaemonUser.USER_NAME + "@" + sshAddress.getHostName())
+ .add("suexec")
+ .add("--as")
+ .add("admin")
+ .add("--")
+ .add("gerrit")
+ .add("version")
+ .build());
+ }
+
+ private String execute(ImmutableList<String> cmd) throws Exception {
+ return execute(cmd, sitePaths.data_dir.toFile(), ImmutableMap.of());
+ }
+
+ private static void assertContent(String result) {
+ assertThat(result).contains("gerrit version " + Version.getVersion());
+ }
+}
diff --git a/javatests/com/google/gerrit/server/restapi/change/ListChangeCommentsTest.java b/javatests/com/google/gerrit/server/restapi/change/ListChangeCommentsTest.java
index 5a28404..daefd7c 100644
--- a/javatests/com/google/gerrit/server/restapi/change/ListChangeCommentsTest.java
+++ b/javatests/com/google/gerrit/server/restapi/change/ListChangeCommentsTest.java
@@ -14,77 +14,126 @@
package com.google.gerrit.server.restapi.change;
+import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
-import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CommentsUtil;
import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ListChangeCommentsTest {
-
@Test
- public void commentsLinkedToChangeMessages() {
- /**
- * Human comments should not be linked to auto-generated messages. A comment is linked to the
- * nearest next change message in timestamp
- */
- CommentInfo c1 = getNewCommentInfo("c1", Timestamp.valueOf("2018-01-01 09:01:00"));
- CommentInfo c2 = getNewCommentInfo("c2", Timestamp.valueOf("2018-01-01 09:01:15"));
- CommentInfo c3 = getNewCommentInfo("c3", Timestamp.valueOf("2018-01-01 09:01:25"));
+ public void commentsLinkedToChangeMessagesIgnoreGerritAutoGenTaggedMessages() {
+ /* Comments should not be linked to Gerrit's autogenerated messages */
+ List<CommentInfo> comments = createComments("c1", "00", "c2", "10", "c3", "25");
+ List<ChangeMessage> changeMessages =
+ createChangeMessages("cm1", "00", "cm2", "16", "cm3", "30");
- ChangeMessage cm1 =
- getNewChangeMessage("cm1key", "cm1", Timestamp.valueOf("2018-01-01 00:00:00"), null);
- ChangeMessage cmIgnore =
- getNewChangeMessage(
- "cm2key",
- "cm2",
- Timestamp.valueOf("2018-01-01 09:01:15"),
- ChangeMessagesUtil.AUTOGENERATED_TAG_PREFIX);
- ChangeMessage cm2 =
- getNewChangeMessage("cm2key", "cm2", Timestamp.valueOf("2018-01-01 09:01:16"), null);
- ChangeMessage cm3 =
- getNewChangeMessage("cm3key", "cm3", Timestamp.valueOf("2018-01-01 09:01:27"), null);
-
- assertThat(c1.changeMessageId).isNull();
- assertThat(c2.changeMessageId).isNull();
- assertThat(c3.changeMessageId).isNull();
-
- ImmutableList<CommentInfo> comments = ImmutableList.of(c1, c2, c3);
- ImmutableList<ChangeMessage> changeMessages = ImmutableList.of(cm1, cmIgnore, cm2, cm3);
+ changeMessages.add(
+ newChangeMessage("ignore", "cmAutoGenByGerrit", "15", ChangeMessagesUtil.TAG_MERGED));
CommentsUtil.linkCommentsToChangeMessages(comments, changeMessages, true);
- assertThat(c1.changeMessageId).isEqualTo(changeMessageKey(cm2));
- /** comment 2 ignored the auto-generated change message */
- assertThat(c2.changeMessageId).isEqualTo(changeMessageKey(cm2));
- assertThat(c3.changeMessageId).isEqualTo(changeMessageKey(cm3));
+ assertThat(getComment(comments, "c1").changeMessageId)
+ .isEqualTo(getChangeMessage(changeMessages, "cm1").getKey().uuid());
+ /* comment 2 ignored the auto-generated message because it has a Gerrit tag */
+ assertThat(getComment(comments, "c2").changeMessageId)
+ .isEqualTo(getChangeMessage(changeMessages, "cm2").getKey().uuid());
+ assertThat(getComment(comments, "c3").changeMessageId)
+ .isEqualTo(getChangeMessage(changeMessages, "cm3").getKey().uuid());
+
+ // Make sure no comment is linked to the auto-gen message
+ assertThat(comments.stream().map(c -> c.changeMessageId).collect(Collectors.toSet()))
+ .doesNotContain(getChangeMessage(changeMessages, "cmAutoGenByGerrit"));
}
- private static CommentInfo getNewCommentInfo(String message, Timestamp ts) {
+ @Test
+ public void commentsLinkedToChangeMessagesAllowLinkingToAutoGenTaggedMessages() {
+ /* Human comments are allowed to be linked to autogenerated messages */
+ List<CommentInfo> comments = createComments("c1", "00", "c2", "10", "c3", "25");
+ List<ChangeMessage> changeMessages =
+ createChangeMessages("cm1", "00", "cm2", "16", "cm3", "30");
+
+ changeMessages.add(
+ newChangeMessage(
+ "cmAutoGen", "cmAutoGen", "15", ChangeMessagesUtil.AUTOGENERATED_TAG_PREFIX));
+
+ CommentsUtil.linkCommentsToChangeMessages(comments, changeMessages, true);
+
+ assertThat(getComment(comments, "c1").changeMessageId)
+ .isEqualTo(getChangeMessage(changeMessages, "cm1").getKey().uuid());
+ /* comment 2 did not ignore the auto-generated change message */
+ assertThat(getComment(comments, "c2").changeMessageId)
+ .isEqualTo(getChangeMessage(changeMessages, "cmAutoGen").getKey().uuid());
+ assertThat(getComment(comments, "c3").changeMessageId)
+ .isEqualTo(getChangeMessage(changeMessages, "cm3").getKey().uuid());
+ }
+
+ /**
+ * Create a list of comments from the specified args args should be passed as consecutive pairs of
+ * messages and timestamps example: (m1, t1, m2, t2, ...)
+ */
+ private static List<CommentInfo> createComments(String... args) {
+ List<CommentInfo> comments = new ArrayList<>();
+ for (int i = 0; i < args.length; i += 2) {
+ String message = args[i];
+ String ts = args[i + 1];
+ comments.add(newCommentInfo(message, ts));
+ }
+ return comments;
+ }
+
+ /**
+ * Create a list of change messages from the specified args args should be passed as consecutive
+ * pairs of messages and timestamps example: (m1, t1, m2, t2, ...). the tag parameter for the
+ * created change messages will be null.
+ */
+ private static List<ChangeMessage> createChangeMessages(String... args) {
+ List<ChangeMessage> changeMessages = new ArrayList<>();
+ for (int i = 0; i < args.length; i += 2) {
+ String key = args[i] + "Key";
+ String message = args[i];
+ String ts = args[i + 1];
+ changeMessages.add(newChangeMessage(key, message, ts, null));
+ }
+ return changeMessages;
+ }
+
+ /** Create a new CommentInfo with a given message and timestamp */
+ private static CommentInfo newCommentInfo(String message, String ts) {
CommentInfo c = new CommentInfo();
c.message = message;
- c.updated = ts;
+ c.updated = Timestamp.valueOf("2000-01-01 00:00:" + ts);
return c;
}
- private static ChangeMessage getNewChangeMessage(
- String id, String message, Timestamp ts, String tag) {
+ /** Create a new change message with an id, message, timestamp and tag */
+ private static ChangeMessage newChangeMessage(String id, String message, String ts, String tag) {
ChangeMessage.Key key = ChangeMessage.key(Change.id(1), id);
- ChangeMessage cm = new ChangeMessage(key, null, ts, null);
+ ChangeMessage cm =
+ new ChangeMessage(key, null, Timestamp.valueOf("2000-01-01 00:00:" + ts), null);
cm.setMessage(message);
cm.setTag(tag);
return cm;
}
- private static String changeMessageKey(ChangeMessage changeMessage) {
- return changeMessage.getKey().uuid();
+ /** Return the change message from the list of messages that has specific message text */
+ private static ChangeMessage getChangeMessage(List<ChangeMessage> messages, String messageText) {
+ return messages.stream().filter(m -> m.getMessage().equals(messageText)).collect(onlyElement());
+ }
+
+ /** Return the comment from the list of comments that has specific message text */
+ private CommentInfo getComment(List<CommentInfo> comments, String messageText) {
+ return comments.stream().filter(c -> c.message.equals(messageText)).collect(onlyElement());
}
}
diff --git a/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.js b/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.js
deleted file mode 100644
index 88c8835..0000000
--- a/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * @license
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/** @polymerBehavior Gerrit.FireBehavior */
-export const FireBehavior = {
- /**
- * Dispatches a custom event with an optional detail value.
- *
- * @param {string} type Name of event type.
- * @param {*=} detail Detail value containing event-specific
- * payload.
- * @param {{ bubbles: (boolean|undefined), cancelable: (boolean|undefined),
- * composed: (boolean|undefined) }=}
- * options Object specifying options. These may include:
- * `bubbles` (boolean, defaults to `true`),
- * `cancelable` (boolean, defaults to false), and
- * `composed` (boolean, defaults to true).
- * @return {!Event} The new event that was fired.
- * @override
- */
- fire(type, detail, options) {
- console.warn('\'fire\' is deprecated, please use dispatchEvent instead!');
- options = options || {};
- detail = (detail === null || detail === undefined) ? {} : detail;
- const event = new Event(type, {
- bubbles: options.bubbles === undefined ? true : options.bubbles,
- cancelable: Boolean(options.cancelable),
- composed: options.composed === undefined ? true: options.composed,
- });
- event.detail = detail;
- this.dispatchEvent(event);
- return event;
- },
-};
-
-// TODO(dmfilippov) Remove the following lines with assignments
-// Plugins can use the behavior because it was accessible with
-// the global Gerrit... variable. To avoid breaking changes in plugins
-// temporary assign global variables.
-window.Gerrit = window.Gerrit || {};
-window.Gerrit.FireBehavior = FireBehavior;
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js
index 09e3b72..ca31ee8 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js
@@ -171,6 +171,7 @@
TOGGLE_FILE_REVIEWED: 'TOGGLE_FILE_REVIEWED',
TOGGLE_ALL_INLINE_DIFFS: 'TOGGLE_ALL_INLINE_DIFFS',
TOGGLE_INLINE_DIFF: 'TOGGLE_INLINE_DIFF',
+ TOGGLE_HIDE_ALL_COMMENT_THREADS: 'TOGGLE_HIDE_ALL_COMMENT_THREADS',
OPEN_FIRST_FILE: 'OPEN_FIRST_FILE',
OPEN_LAST_FILE: 'OPEN_LAST_FILE',
@@ -251,6 +252,8 @@
'Expand all comment threads');
_describe(Shortcut.COLLAPSE_ALL_COMMENT_THREADS, ShortcutSection.DIFFS,
'Collapse all comment threads');
+_describe(Shortcut.TOGGLE_HIDE_ALL_COMMENT_THREADS, ShortcutSection.DIFFS,
+ 'Hide/Display all comment threads');
_describe(Shortcut.LEFT_PANE, ShortcutSection.DIFFS, 'Select left pane');
_describe(Shortcut.RIGHT_PANE, ShortcutSection.DIFFS, 'Select right pane');
_describe(Shortcut.TOGGLE_LEFT_PANE, ShortcutSection.DIFFS,
@@ -290,6 +293,8 @@
'Go to selected file');
_describe(Shortcut.TOGGLE_ALL_INLINE_DIFFS, ShortcutSection.FILE_LIST,
'Show/hide all inline diffs');
+_describe(Shortcut.TOGGLE_HIDE_ALL_COMMENT_THREADS, ShortcutSection.FILE_LIST,
+ 'Hide/Display all comment threads');
_describe(Shortcut.TOGGLE_INLINE_DIFF, ShortcutSection.FILE_LIST,
'Show/hide selected inline diff');
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 74b62a7..894f0cc 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -275,6 +275,8 @@
[this.Shortcut.RIGHT_PANE]: '_handleRightPane',
[this.Shortcut.TOGGLE_INLINE_DIFF]: '_handleToggleInlineDiff',
[this.Shortcut.TOGGLE_ALL_INLINE_DIFFS]: '_handleToggleAllInlineDiffs',
+ [this.Shortcut.TOGGLE_HIDE_ALL_COMMENT_THREADS]:
+ '_handleToggleHideAllCommentThreads',
[this.Shortcut.CURSOR_NEXT_FILE]: '_handleCursorNext',
[this.Shortcut.CURSOR_PREV_FILE]: '_handleCursorPrev',
[this.Shortcut.NEXT_LINE]: '_handleCursorNext',
@@ -808,6 +810,15 @@
this._toggleInlineDiffs();
}
+ _handleToggleHideAllCommentThreads(e) {
+ if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ this.toggleClass('hideComments');
+ }
+
_handleCursorNext(e) {
if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) {
return;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.js
index 4ac4bb1..df7cb00 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.js
@@ -310,6 +310,9 @@
display: none;
}
}
+ :host(.hideComments) {
+ --gr-comment-thread-display: none;
+ }
</style>
<div
id="container"
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index b808bd9..e37c84a 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -295,6 +295,19 @@
this.addEventListener('comment-editing-changed', e => {
this._commentEditing = e.detail;
});
+
+ // Plugins on reply-reviewers endpoint can take advantage of these
+ // events to add / remove reviewers
+
+ this.addEventListener('add-reviewer', e => {
+ // Only support account type, see more from:
+ // elements/shared/gr-account-list/gr-account-list.js#addAccountItem
+ this.$.reviewers.addAccountItem({account: e.detail.reviewer});
+ });
+
+ this.addEventListener('remove-reviewer', e => {
+ this.$.reviewers.removeAccount(e.detail.reviewer);
+ });
}
/** @override */
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.js
index de11712..79f38a6 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.js
@@ -97,13 +97,10 @@
}
.textareaContainer,
#textarea,
- gr-endpoint-decorator {
+ gr-endpoint-decorator[name='reply-text'] {
display: flex;
width: 100%;
}
- gr-endpoint-decorator[name='reply-label-scores'] {
- display: block;
- }
.previewContainer gr-formatted-text {
background: var(--table-header-background-color);
padding: var(--spacing-l);
@@ -140,20 +137,25 @@
</style>
<div class="container" tabindex="-1">
<section class="peopleContainer">
- <div class="peopleList">
- <div class="peopleListLabel">Reviewers</div>
- <gr-account-list
- id="reviewers"
- accounts="{{_reviewers}}"
- removable-values="[[change.removable_reviewers]]"
- filter="[[filterReviewerSuggestion]]"
- pending-confirmation="{{_reviewerPendingConfirmation}}"
- placeholder="Add reviewer..."
- on-account-text-changed="_handleAccountTextEntry"
- suggestions-provider="[[_getReviewerSuggestionsProvider(change)]]"
- >
- </gr-account-list>
- </div>
+ <gr-endpoint-decorator name="reply-reviewers">
+ <gr-endpoint-param name="change" value="[[change]]"></gr-endpoint-param>
+ <div class="peopleList">
+ <div class="peopleListLabel">Reviewers</div>
+ <gr-account-list
+ id="reviewers"
+ accounts="{{_reviewers}}"
+ removable-values="[[change.removable_reviewers]]"
+ filter="[[filterReviewerSuggestion]]"
+ pending-confirmation="{{_reviewerPendingConfirmation}}"
+ placeholder="Add reviewer..."
+ on-account-text-changed="_handleAccountTextEntry"
+ suggestions-provider="[[_getReviewerSuggestionsProvider(change)]]"
+ >
+ </gr-account-list>
+ <gr-endpoint-slot name="right"></gr-endpoint-slot>
+ </div>
+ <gr-endpoint-slot name="below"></gr-endpoint-slot>
+ </gr-endpoint-decorator>
<div class="peopleList">
<div class="peopleListLabel">CC</div>
<gr-account-list
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 5c77b0e..19abdaf 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -298,6 +298,8 @@
[this.Shortcut.EXPAND_ALL_DIFF_CONTEXT]: '_handleExpandAllDiffContext',
[this.Shortcut.NEXT_UNREVIEWED_FILE]: '_handleNextUnreviewedFile',
[this.Shortcut.TOGGLE_BLAME]: '_handleToggleBlame',
+ [this.Shortcut.TOGGLE_HIDE_ALL_COMMENT_THREADS]:
+ '_handleToggleHideAllCommentThreads',
// Final two are actually handled by gr-comment-thread.
[this.Shortcut.EXPAND_ALL_COMMENT_THREADS]: null,
@@ -740,6 +742,7 @@
this._initCursor(this.params);
this._changeNum = value.changeNum;
+ this.classList.remove('hideComments');
this._path = value.path;
this._patchRange = {
patchNum: value.patchNum,
@@ -1277,6 +1280,12 @@
this._toggleBlame();
}
+ _handleToggleHideAllCommentThreads(e) {
+ if (this.shouldSuppressKeyboardShortcut(e) ||
+ this.modifierPressed(e)) { return; }
+ this.toggleClass('hideComments');
+ }
+
_computeBlameLoaderClass(isImageDiff, path) {
return !this.isMagicPath(path) && !isImageDiff ? 'show' : '';
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.js
index e735153..3d30f77 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.js
@@ -196,6 +196,9 @@
}
}
}
+ :host(.hideComments) {
+ --gr-comment-thread-display: none;
+ }
</style>
<gr-fixed-panel
class$="[[_computeContainerClass(_editMode)]]"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 98371dc..21d1144 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -71,6 +71,7 @@
kb.bindShortcut(kb.Shortcut.TOGGLE_FILE_REVIEWED, 'r');
kb.bindShortcut(kb.Shortcut.EXPAND_ALL_DIFF_CONTEXT, 'shift+x');
kb.bindShortcut(kb.Shortcut.EXPAND_ALL_COMMENT_THREADS, 'e');
+ kb.bindShortcut(kb.Shortcut.TOGGLE_HIDE_ALL_COMMENT_THREADS, 'h');
kb.bindShortcut(kb.Shortcut.COLLAPSE_ALL_COMMENT_THREADS, 'shift+e');
kb.bindShortcut(kb.Shortcut.NEXT_UNREVIEWED_FILE, 'shift+m');
kb.bindShortcut(kb.Shortcut.TOGGLE_BLAME, 'b');
diff --git a/polygerrit-ui/app/elements/gr-app-element.js b/polygerrit-ui/app/elements/gr-app-element.js
index 10ca963..ecb40a5 100644
--- a/polygerrit-ui/app/elements/gr-app-element.js
+++ b/polygerrit-ui/app/elements/gr-app-element.js
@@ -350,6 +350,8 @@
this.Shortcut.TOGGLE_INLINE_DIFF, 'i:keyup');
this.bindShortcut(
this.Shortcut.TOGGLE_BLAME, 'b');
+ this.bindShortcut(
+ this.Shortcut.TOGGLE_HIDE_ALL_COMMENT_THREADS, 'h');
this.bindShortcut(
this.Shortcut.OPEN_FIRST_FILE, ']');
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.js b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.js
index 52c4f4e..3f8aa44 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.js
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.js
@@ -14,13 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-attribute-helper">
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
/** @constructor */
export function GrAttributeHelper(element) {
diff --git a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.js b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.js
index ea60dc6..89d8ec2 100644
--- a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.js
+++ b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.js
@@ -14,13 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-change-metadata-api">
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
/** @constructor */
export function GrChangeMetadataApi(plugin) {
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js
index 41d5fd5..4822163 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js
@@ -14,14 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-dom-hooks">
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
+import {PolymerElement} from '@polymer/polymer/polymer-element.js';
/** @constructor */
export function GrDomHooksManager(plugin) {
@@ -61,13 +54,18 @@
}
GrDomHook.prototype._createPlaceholder = function(hookName) {
- Polymer({
- is: hookName,
- properties: {
- plugin: Object,
- content: Object,
- },
- });
+ class HookPlaceholder extends PolymerElement {
+ static get is() { return hookName; }
+
+ static get properties() {
+ return {
+ plugin: Object,
+ content: Object,
+ };
+ }
+ }
+
+ customElements.define(HookPlaceholder.is, HookPlaceholder);
};
GrDomHook.prototype.handleInstanceDetached = function(instance) {
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
index da85b9e..466f84a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
@@ -14,13 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-event-helper">
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
/** @constructor */
export function GrEventHelper(element) {
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js
index 738b276..e2dd047 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js
@@ -16,13 +16,6 @@
*/
import './gr-plugin-popup.js';
import {dom, flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-popup-interface">
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
/**
* Plugin popup API.
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.js b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.js
index 445356d..04408f8 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.js
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.js
@@ -15,13 +15,6 @@
* limitations under the License.
*/
import './gr-plugin-repo-command.js';
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-repo-api">
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
/** @constructor */
export function GrRepoApi(plugin) {
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.js b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.js
index 35396cf..8050cd6 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.js
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.js
@@ -16,13 +16,6 @@
*/
import '../../settings/gr-settings-view/gr-settings-item.js';
import '../../settings/gr-settings-view/gr-settings-menu-item.js';
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-settings-api">
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
/** @constructor */
export function GrSettingsApi(plugin) {
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.js b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.js
index ae8b8ab..1e37603 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.js
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.js
@@ -14,10 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
+import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-Polymer({
- _template: html`
+
+class CustomPluginHeader extends PolymerElement {
+ static get is() {
+ return 'gr-custom-plugin-header';
+ }
+
+ static get properties() {
+ return {
+ logoUrl: String,
+ title: String,
+ };
+ }
+
+ static get template() {
+ return html`
<style>
img {
width: 1em;
@@ -32,12 +45,8 @@
<img src="[[logoUrl]]" hidden\$="[[!logoUrl]]">
<span class="title">[[title]]</span>
</span>
-`,
+`;
+ }
+}
- is: 'gr-custom-plugin-header',
-
- properties: {
- logoUrl: String,
- title: String,
- },
-});
+customElements.define(CustomPluginHeader.is, CustomPluginHeader);
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.js b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.js
index be427ec..48b14d3 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.js
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.js
@@ -15,13 +15,6 @@
* limitations under the License.
*/
import './gr-custom-plugin-header.js';
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-theme-api">
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
/** @constructor */
export function GrThemeApi(plugin) {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
index abe5206..6f5f9e4 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
@@ -158,10 +158,10 @@
}
_handleAdd(e) {
- this._addAccountItem(e.detail.value);
+ this.addAccountItem(e.detail.value);
}
- _addAccountItem(item) {
+ addAccountItem(item) {
// Append new account or group to the accounts property. We add our own
// internal properties to the account/group here, so we clone the object
// to avoid cluttering up the shared change object.
@@ -248,11 +248,11 @@
_handleRemove(e) {
const toRemove = e.detail.account;
- this._removeAccount(toRemove);
+ this.removeAccount(toRemove);
this.$.entry.focus();
}
- _removeAccount(toRemove) {
+ removeAccount(toRemove) {
if (!toRemove || !this._computeRemovable(toRemove, this.readonly)) {
return;
}
@@ -286,7 +286,7 @@
}
switch (e.detail.keyCode) {
case 8: // Backspace
- this._removeAccount(this.accounts[this.accounts.length - 1]);
+ this.removeAccount(this.accounts[this.accounts.length - 1]);
break;
case 37: // Left arrow
if (this.accountChips[this.accountChips.length - 1]) {
@@ -305,7 +305,7 @@
case 13: // Enter
case 32: // Spacebar
case 46: // Delete
- this._removeAccount(chip.account);
+ this.removeAccount(chip.account);
// Splice from this array to avoid inconsistent ordering of
// event handling.
chips.splice(index, 1);
@@ -345,7 +345,7 @@
submitEntryText() {
const text = this.$.entry.getText();
if (!text.length) { return true; }
- const wasSubmitted = this._addAccountItem(text);
+ const wasSubmitted = this.addAccountItem(text);
if (wasSubmitted) { this.$.entry.clear(); }
return wasSubmitted;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
index 2efc6e8..41158a8 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
@@ -351,7 +351,7 @@
element.readonly = true;
const acct = makeAccount();
element.accounts = [acct];
- element._removeAccount(acct);
+ element.removeAccount(acct);
assert.equal(element.accounts.length, 1);
});
@@ -537,7 +537,7 @@
element.accounts = [makeAccount(), makeAccount()];
flush(() => {
const focusSpy = sandbox.spy(element.accountChips[1], 'focus');
- const removeSpy = sandbox.spy(element, '_removeAccount');
+ const removeSpy = sandbox.spy(element, 'removeAccount');
MockInteractions.pressAndReleaseKeyOn(
element.accountChips[0], 8); // Backspace
assert.isTrue(focusSpy.called);
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.js b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.js
index 7c5c5c4c..2bb5b66ea 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.js
@@ -37,7 +37,7 @@
#container {
background-color: var(--comment-background-color);
color: var(--comment-text-color);
- display: block;
+ display: var(--gr-comment-thread-display, block);
margin: 0 var(--spacing-s) var(--spacing-s);
white-space: normal;
box-shadow: var(--elevation-level-2);
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator.js
index 7402e22..a4adbac 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator.js
@@ -14,13 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-etag-decorator">
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
// Limit cache size because /change/detail responses may be large.
const MAX_CACHE_SIZE = 30;
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
index c89378c..9b4bbaf 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
@@ -17,14 +17,7 @@
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-const $_documentContainer = document.createElement('template');
-
-$_documentContainer.innerHTML = `<dom-module id="gr-select">
- <slot></slot>
-
-</dom-module>`;
-
-document.head.appendChild($_documentContainer.content);
+import {html} from '@polymer/polymer/lib/utils/html-tag.js';
/**
* @extends PolymerElement
@@ -33,6 +26,12 @@
LegacyElementMixin(PolymerElement)) {
static get is() { return 'gr-select'; }
+ static get template() {
+ return html`
+ <slot></slot>
+ `;
+ }
+
static get properties() {
return {
bindValue: {
diff --git a/polygerrit-ui/app/samples/add-from-favorite-reviewers.js b/polygerrit-ui/app/samples/add-from-favorite-reviewers.js
new file mode 100644
index 0000000..76b2787
--- /dev/null
+++ b/polygerrit-ui/app/samples/add-from-favorite-reviewers.js
@@ -0,0 +1,145 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * This plugin will a button to quickly add favorite reviewers to
+ * reviewers in reply dialog.
+ */
+
+const onToggleButtonClicks = [];
+function toggleButtonClicked(expanded) {
+ onToggleButtonClicks.forEach(cb => {
+ cb(expanded);
+ });
+}
+
+class ReviewerShortcut extends Polymer.Element {
+ static get is() { return 'reviewer-shortcut'; }
+
+ static get properties() {
+ return {
+ change: Object,
+ expanded: {
+ type: Boolean,
+ value: false,
+ },
+ };
+ }
+
+ static get template() {
+ return Polymer.html`
+ <button on-click="toggleControlContent">
+ [[computeButtonText(expanded)]]
+ </button>
+ `;
+ }
+
+ toggleControlContent() {
+ this.expanded = !this.expanded;
+ toggleButtonClicked(this.expanded);
+ }
+
+ computeButtonText(expanded) {
+ return expanded ? 'Collapse' : 'Add favorite reviewers';
+ }
+}
+
+customElements.define(ReviewerShortcut.is, ReviewerShortcut);
+
+class ReviewerShortcutContent extends Polymer.Element {
+ static get is() { return 'reviewer-shortcut-content'; }
+
+ static get properties() {
+ return {
+ change: Object,
+ hidden: {
+ type: Boolean,
+ value: true,
+ reflectToAttribute: true,
+ },
+ };
+ }
+
+ static get template() {
+ return Polymer.html`
+ <style>
+ :host([hidden]) {
+ display: none;
+ }
+ :host {
+ display: block;
+ }
+ </style>
+ <ul>
+ <li><button on-click="addApple">Apple</button></li>
+ <li><button on-click="addBanana">Banana</button></li>
+ <li><button on-click="addCherry">Cherry</button></li>
+ </ul>
+ `;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ onToggleButtonClicks.push(expanded => {
+ this.hidden = !expanded;
+ });
+ }
+
+ addApple() {
+ this.dispatchEvent(new CustomEvent('add-reviewer', {detail: {
+ reviewer: {
+ display_name: 'Apple',
+ email: 'apple@gmail.com',
+ name: 'Apple',
+ _account_id: 0,
+ },
+ },
+ composed: true, bubbles: true}));
+ }
+
+ addBanana() {
+ this.dispatchEvent(new CustomEvent('add-reviewer', {detail: {
+ reviewer: {
+ display_name: 'Banana',
+ email: 'banana@gmail.com',
+ name: 'B',
+ _account_id: 1,
+ },
+ },
+ composed: true, bubbles: true}));
+ }
+
+ addCherry() {
+ this.dispatchEvent(new CustomEvent('add-reviewer', {detail: {
+ reviewer: {
+ display_name: 'Cherry',
+ email: 'cherry@gmail.com',
+ name: 'C',
+ _account_id: 2,
+ },
+ },
+ composed: true, bubbles: true}));
+ }
+}
+
+customElements.define(ReviewerShortcutContent.is, ReviewerShortcutContent);
+
+Gerrit.install(plugin => {
+ plugin.registerCustomComponent(
+ 'reply-reviewers', ReviewerShortcut.is, {slot: 'right'});
+ plugin.registerCustomComponent(
+ 'reply-reviewers', ReviewerShortcutContent.is, {slot: 'below'});
+});
\ No newline at end of file