Merge "Update designdoc to current state"
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 9d29980..8197550 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -41,14 +41,8 @@
[[java]]
=== Java
-==== MacOS
-
-On MacOS, ensure that "Java for MacOS X 10.5 Update 4" (or higher) is installed
-and that `JAVA_HOME` is set to the
-link:install.html#Requirements[required Java version].
-
-Java installations can typically be found in
-"/System/Library/Frameworks/JavaVM.framework/Versions".
+Ensure that the link:install.html#Requirements[required Java version]
+is installed and that `JAVA_HOME` is set to it.
To check the installed version of Java, open a terminal window and run:
diff --git a/Documentation/dev-rest-api.txt b/Documentation/dev-rest-api.txt
index fec9c97..a28e230 100644
--- a/Documentation/dev-rest-api.txt
+++ b/Documentation/dev-rest-api.txt
@@ -50,6 +50,11 @@
curl -X PUT --user john:2LlAB3K9B0PF --data-binary @project-desc.txt --header "Content-Type: application/json; charset=UTF-8" http://localhost:8080/a/projects/myproject/description
----
+[[pretty-json]]
+=== Pretty JSON
+
+By default any JSON in responses is compacted. To get pretty-printed JSON add `pp=1` to the request.
+
=== Authentication
To test APIs that require authentication, the username and password must be specified on
diff --git a/Documentation/install.txt b/Documentation/install.txt
index 94a576c..6e1a9bd 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -10,39 +10,6 @@
+
Gerrit is not yet compatible with Java 13 or newer at this time.
-[[cryptography]]
-== Configure Java for Strong Cryptography
-
-Support for extra strength cryptographic ciphers: _AES128CTR_, _AES256CTR_,
-_ARCFOUR256_, and _ARCFOUR128_ can be enabled by downloading the _Java
-Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files_
-from Oracle and installing them into your JRE.
-
-[NOTE]
-Installing JCE extensions is optional and export restrictions may apply.
-
-. Download the unlimited strength JCE policy files.
-+
-- link:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html[JDK7 JCE policy files,role=external,window=_blank]
-- link:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html[JDK8 JCE policy files,role=external,window=_blank]
-. Uncompress and extract the downloaded file.
-+
-The downloaded file contains the following files:
-+
-[cols="2"]
-|===
-|README.txt
-|Information about JCE and installation guide
-
-|local_policy.jar
-|Unlimited strength local policy file
-
-|US_export_policy.jar
-|Unlimited strength US export policy file
-|===
-. Install the unlimited strength policy JAR files by following instructions
-found in `README.txt`.
-
[[download]]
== Download Gerrit
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt
index 7ef9473..0f3f350 100644
--- a/Documentation/js_licenses.txt
+++ b/Documentation/js_licenses.txt
@@ -371,6 +371,7 @@
* @polymer/paper-listbox
* @polymer/paper-tabs
* @polymer/paper-toggle-button
+* @polymer/paper-tooltip
[[Polymer-2015_license]]
----
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 5f0fb65..5db997d 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -3328,6 +3328,7 @@
* @polymer/paper-listbox
* @polymer/paper-tabs
* @polymer/paper-toggle-button
+* @polymer/paper-tooltip
[[Polymer-2015_license]]
----
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 3a5fb96..f66d430 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -6718,6 +6718,10 @@
A list of link:#context-line[ContextLine] containing the lines of the source
file where the comment was written. Available only if the "enable-context"
parameter (see link:#list-change-comments[List Change Comments]) is set.
+|`source_content_type` |optional|
+Mime type of the file where the comment is written. Available only if the
+"enable-context" parameter (see link:#list-change-comments[List Change Comments])
+is set.
|===========================
diff --git a/Documentation/user-attention-set.txt b/Documentation/user-attention-set.txt
index 5e2906f..4697afc 100644
--- a/Documentation/user-attention-set.txt
+++ b/Documentation/user-attention-set.txt
@@ -48,6 +48,7 @@
* For merged and abandoned changes the owner is added only when a human creates
an unresolved comment.
* Only owner, uploader, reviewers and ccs can be in the attention set.
+* The rules for service accounts are different, see link:#bots[Bots].
*!IMPORTANT!* These rules are not meant to be super smart and to always do the
right thing, e.g. if the change owner sends a reply, then they are often
@@ -85,7 +86,7 @@
image::images/user-attention-set-reply-select.png["reply dialog section for selecting users", align="center"]
-=== Bots
+=== Bots [[bots]]
The attention set is meant for human reviews only. Triggering bots and reacting
to their results is a different workflow and not in scope of the attenion set.
diff --git a/WORKSPACE b/WORKSPACE
index c24d4f9..cffbc8d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -564,36 +564,36 @@
sha1 = "5e3bda828a80c7a21dfbe2308d1755759c2fd7b4",
)
-OW2_VERS = "7.2"
+OW2_VERS = "9.0"
maven_jar(
name = "ow2-asm",
artifact = "org.ow2.asm:asm:" + OW2_VERS,
- sha1 = "fa637eb67eb7628c915d73762b681ae7ff0b9731",
+ sha1 = "af582ff60bc567c42d931500c3fdc20e0141ddf9",
)
maven_jar(
name = "ow2-asm-analysis",
artifact = "org.ow2.asm:asm-analysis:" + OW2_VERS,
- sha1 = "b6e6abe057f23630113f4167c34bda7086691258",
+ sha1 = "4630afefbb43939c739445dde0af1a5729a0fb4e",
)
maven_jar(
name = "ow2-asm-commons",
artifact = "org.ow2.asm:asm-commons:" + OW2_VERS,
- sha1 = "ca2954e8d92a05bacc28ff465b25c70e0f512497",
+ sha1 = "5a34a3a9ac44f362f35d1b27932380b0031a3334",
)
maven_jar(
name = "ow2-asm-tree",
artifact = "org.ow2.asm:asm-tree:" + OW2_VERS,
- sha1 = "3a23cc36edaf8fc5a89cb100182758ccb5991487",
+ sha1 = "9df939f25c556b0c7efe00701d47e77a49837f24",
)
maven_jar(
name = "ow2-asm-util",
artifact = "org.ow2.asm:asm-util:" + OW2_VERS,
- sha1 = "a3ae34e57fa8a4040e28247291d0cc3d6b8c7bcf",
+ sha1 = "7c059a94ab5eed3347bf954e27fab58e52968848",
)
AUTO_VALUE_VERSION = "1.7.4"
@@ -913,28 +913,28 @@
maven_jar(
name = "mockito",
- artifact = "org.mockito:mockito-core:2.24.0",
- sha1 = "969a7bcb6f16e076904336ebc7ca171d412cc1f9",
+ artifact = "org.mockito:mockito-core:3.3.3",
+ sha1 = "4878395d4e63173f3825e17e5e0690e8054445f1",
)
-BYTE_BUDDY_VERSION = "1.9.7"
+BYTE_BUDDY_VERSION = "1.10.7"
maven_jar(
name = "bytebuddy",
artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
- sha1 = "8fea78fea6449e1738b675cb155ce8422661e237",
+ sha1 = "1eefb7dd1b032b33c773ca0a17d5cc9e6b56ea1a",
)
maven_jar(
name = "bytebuddy-agent",
artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
- sha1 = "8e7d1b599f4943851ffea125fd9780e572727fc0",
+ sha1 = "c472fad33f617228601172682aa64f8b78508045",
)
maven_jar(
name = "objenesis",
- artifact = "org.objenesis:objenesis:2.6",
- sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
+ artifact = "org.objenesis:objenesis:3.0.1",
+ sha1 = "11cfac598df9dc48bb9ed9357ed04212694b7808",
)
load("@build_bazel_rules_nodejs//:index.bzl", "yarn_install")
diff --git a/contrib/populate-fixture-data.py b/contrib/populate-fixture-data.py
index 4c6769c..2341f6c 100755
--- a/contrib/populate-fixture-data.py
+++ b/contrib/populate-fixture-data.py
@@ -157,7 +157,7 @@
BASE_URL + "groups/?suggest=ad&p=All-Projects",
headers=HEADERS,
auth=ADMIN_BASIC_AUTH).text))
- admin_group_name = r.keys()[0]
+ admin_group_name = list(r.keys())[0]
GROUP_ADMIN = r[admin_group_name]
GROUP_ADMIN["name"] = admin_group_name
@@ -305,7 +305,7 @@
project_names = create_gerrit_projects(group_names)
for idx, u in enumerate(gerrit_users):
- for _ in xrange(random.randint(1, 5)):
- create_change(u, project_names[4 * idx / len(gerrit_users)])
+ for _ in range(random.randint(1, 5)):
+ create_change(u, project_names[4 * idx // len(gerrit_users)])
main()
diff --git a/java/com/google/gerrit/acceptance/PushOneCommit.java b/java/com/google/gerrit/acceptance/PushOneCommit.java
index 4215255..67e26ec 100644
--- a/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -47,6 +47,7 @@
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -294,6 +295,23 @@
return this;
}
+ public PushOneCommit addGitSubmodule(String modulePath, ObjectId commitId) {
+ commitBuilder.edit(
+ new PathEdit(modulePath) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.GITLINK);
+ ent.setObjectId(commitId);
+ }
+ });
+ return this;
+ }
+
+ public PushOneCommit rmFile(String filename) {
+ commitBuilder.rm(filename);
+ return this;
+ }
+
public Result to(String ref) throws Exception {
for (Map.Entry<String, String> e : files.entrySet()) {
commitBuilder.add(e.getKey(), e.getValue());
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
index f6e5de3..e7354ab 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
@@ -138,6 +138,9 @@
throws IOException, ConfigInvalidException {
try (MetaDataUpdate metaDataUpdate = metaDataUpdateFactory.create(nameKey)) {
ProjectConfig projectConfig = projectConfigFactory.read(metaDataUpdate);
+ if (projectUpdate.removeAllAccessSections()) {
+ projectConfig.getAccessSections().forEach(as -> projectConfig.remove(as));
+ }
removePermissions(projectConfig, projectUpdate.removedPermissions());
addCapabilities(projectConfig, projectUpdate.addedCapabilities());
addPermissions(projectConfig, projectUpdate.addedPermissions());
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
index ea20931..9a9a21a 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
@@ -294,7 +294,8 @@
return new AutoValue_TestProjectUpdate.Builder()
.nameKey(nameKey)
.allProjectsName(allProjectsName)
- .projectUpdater(projectUpdater);
+ .projectUpdater(projectUpdater)
+ .removeAllAccessSections(false);
}
/** Builder for {@link TestProjectUpdate}. */
@@ -314,6 +315,16 @@
abstract ImmutableMap.Builder<TestPermissionKey, Boolean> exclusiveGroupPermissionsBuilder();
+ abstract Builder removeAllAccessSections(boolean value);
+
+ /**
+ * Removes all access sections. Useful when testing against a specific set of access sections or
+ * permissions.
+ */
+ public Builder removeAllAccessSections() {
+ return removeAllAccessSections(true);
+ }
+
/** Adds a permission to be included in this update. */
public Builder add(TestPermission testPermission) {
addedPermissionsBuilder().add(testPermission);
@@ -418,6 +429,8 @@
abstract ThrowingConsumer<TestProjectUpdate> projectUpdater();
+ abstract boolean removeAllAccessSections();
+
boolean hasCapabilityUpdates() {
return !addedCapabilities().isEmpty()
|| removedPermissions().stream().anyMatch(k -> k.section().equals(GLOBAL_CAPABILITIES));
diff --git a/java/com/google/gerrit/common/data/PatchScript.java b/java/com/google/gerrit/common/data/PatchScript.java
index e3c0ba6..1ba5592 100644
--- a/java/com/google/gerrit/common/data/PatchScript.java
+++ b/java/com/google/gerrit/common/data/PatchScript.java
@@ -34,7 +34,17 @@
public enum FileMode {
FILE,
SYMLINK,
- GITLINK
+ GITLINK;
+
+ public static FileMode fromJgitFileMode(org.eclipse.jgit.lib.FileMode jgitFileMode) {
+ PatchScript.FileMode fileMode = PatchScript.FileMode.FILE;
+ if (jgitFileMode == org.eclipse.jgit.lib.FileMode.SYMLINK) {
+ fileMode = FileMode.SYMLINK;
+ } else if (jgitFileMode == org.eclipse.jgit.lib.FileMode.GITLINK) {
+ fileMode = FileMode.GITLINK;
+ }
+ return fileMode;
+ }
}
public static class PatchScriptFileInfo {
diff --git a/java/com/google/gerrit/entities/CachedProjectConfig.java b/java/com/google/gerrit/entities/CachedProjectConfig.java
index 0b755b7..2a94bc8 100644
--- a/java/com/google/gerrit/entities/CachedProjectConfig.java
+++ b/java/com/google/gerrit/entities/CachedProjectConfig.java
@@ -14,19 +14,17 @@
package com.google.gerrit.entities;
-import static com.google.common.base.Preconditions.checkState;
-
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
/**
@@ -37,6 +35,8 @@
*/
@AutoValue
public abstract class CachedProjectConfig {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public abstract Project getProject();
public abstract ImmutableMap<AccountGroup.UUID, GroupReference> getGroups();
@@ -126,34 +126,10 @@
public abstract ImmutableMap<String, String> getPluginConfigs();
- /**
- * Returns the {@link Config} that got parsed from the specified {@code fileName} on {@code
- * refs/meta/config}. The returned instance is a defensive copy of the cached value.
- *
- * @param fileName the name of the file. Must end in {@code .config}.
- * @return an {@link Optional} of the {@link Config}. {@link Optional#empty()} if the file was not
- * found or could not be parsed. {@link com.google.gerrit.server.project.ProjectConfig} will
- * surface validation errors in case of a parsing issue.
- */
- public Optional<Config> getProjectLevelConfig(String fileName) {
- checkState(fileName.endsWith(".config"), "file name must end in .config");
- if (getProjectLevelConfigs().containsKey(fileName)) {
- Config config = new Config();
- try {
- config.fromText(getProjectLevelConfigs().get(fileName));
- } catch (ConfigInvalidException e) {
- // This is OK to propagate as IllegalStateException because it's a programmer error.
- // The config was converted to a String using Config#toText. So #fromText must not
- // throw a ConfigInvalidException
- throw new IllegalStateException("invalid config for " + fileName, e);
- }
- return Optional.of(config);
- }
- return Optional.empty();
- }
-
public abstract ImmutableMap<String, String> getProjectLevelConfigs();
+ public abstract ImmutableMap<String, ImmutableConfig> getParsedProjectLevelConfigs();
+
public static Builder builder() {
return new AutoValue_CachedProjectConfig.Builder();
}
@@ -235,8 +211,15 @@
abstract ImmutableMap.Builder<String, String> projectLevelConfigsBuilder();
+ abstract ImmutableMap.Builder<String, ImmutableConfig> parsedProjectLevelConfigsBuilder();
+
public Builder addProjectLevelConfig(String configFileName, String config) {
projectLevelConfigsBuilder().put(configFileName, config);
+ try {
+ parsedProjectLevelConfigsBuilder().put(configFileName, ImmutableConfig.parse(config));
+ } catch (ConfigInvalidException e) {
+ logger.atInfo().withCause(e).log("Config for " + configFileName + " not parsable");
+ }
return this;
}
diff --git a/java/com/google/gerrit/entities/CommentContext.java b/java/com/google/gerrit/entities/CommentContext.java
index c8c8a76..68d779c 100644
--- a/java/com/google/gerrit/entities/CommentContext.java
+++ b/java/com/google/gerrit/entities/CommentContext.java
@@ -20,15 +20,24 @@
/** An entity class representing all context lines of a comment. */
@AutoValue
public abstract class CommentContext {
- private static final CommentContext EMPTY = new AutoValue_CommentContext(ImmutableMap.of());
+ private static final CommentContext EMPTY = new AutoValue_CommentContext(ImmutableMap.of(), "");
- public static CommentContext create(ImmutableMap<Integer, String> lines) {
- return new AutoValue_CommentContext(lines);
+ public static CommentContext create(ImmutableMap<Integer, String> lines, String contentType) {
+ return new AutoValue_CommentContext(lines, contentType);
}
/** Map of {line number, line text} of the context lines of a comment */
public abstract ImmutableMap<Integer, String> lines();
+ /**
+ * Content type of the source file. Useful for syntax highlighting.
+ *
+ * @return text/x-gerrit-commit-message if the file is a commit message.
+ * <p>text/x-gerrit-merge-list if the file is a merge list.
+ * <p>The content/mime type, e.g. text/x-c++src otherwise.
+ */
+ public abstract String contentType();
+
public static CommentContext empty() {
return EMPTY;
}
diff --git a/java/com/google/gerrit/entities/ImmutableConfig.java b/java/com/google/gerrit/entities/ImmutableConfig.java
new file mode 100644
index 0000000..a5efc14
--- /dev/null
+++ b/java/com/google/gerrit/entities/ImmutableConfig.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2021 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.entities;
+
+import java.util.Set;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Immutable parsed representation of a {@link org.eclipse.jgit.lib.Config} that can be cached.
+ * Supports only a limited set of operations.
+ */
+public class ImmutableConfig {
+ public static final ImmutableConfig EMPTY = new ImmutableConfig("", new Config());
+
+ private final String stringCfg;
+ private final Config cfg;
+
+ private ImmutableConfig(String stringCfg, Config cfg) {
+ this.stringCfg = stringCfg;
+ this.cfg = cfg;
+ }
+
+ public static ImmutableConfig parse(String stringCfg) throws ConfigInvalidException {
+ Config cfg = new Config();
+ cfg.fromText(stringCfg);
+ return new ImmutableConfig(stringCfg, cfg);
+ }
+
+ /** Returns a mutable copy of this config. */
+ public Config mutableCopy() {
+ Config cfg = new Config();
+ try {
+ cfg.fromText(this.cfg.toText());
+ } catch (ConfigInvalidException e) {
+ // Can't happen as we used JGit to format that config.
+ throw new IllegalStateException(e);
+ }
+ return cfg;
+ }
+
+ /** @see Config#getSections() */
+ public Set<String> getSections() {
+ return cfg.getSections();
+ }
+
+ /** @see Config#getNames(String) */
+ public Set<String> getNames(String section) {
+ return cfg.getNames(section);
+ }
+
+ /** @see Config#getNames(String, String) */
+ public Set<String> getNames(String section, String subsection) {
+ return cfg.getNames(section, subsection);
+ }
+
+ /** @see Config#getStringList(String, String, String) */
+ public String[] getStringList(String section, String subsection, String name) {
+ return cfg.getStringList(section, subsection, name);
+ }
+
+ /** @see Config#getSubsections(String) */
+ public Set<String> getSubsections(String section) {
+ return cfg.getSubsections(section);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ImmutableConfig)) {
+ return false;
+ }
+ return ((ImmutableConfig) o).stringCfg.equals(stringCfg);
+ }
+
+ @Override
+ public int hashCode() {
+ return stringCfg.hashCode();
+ }
+}
diff --git a/java/com/google/gerrit/entities/SubmitRecord.java b/java/com/google/gerrit/entities/SubmitRecord.java
index 67c6007..1fd0864 100644
--- a/java/com/google/gerrit/entities/SubmitRecord.java
+++ b/java/com/google/gerrit/entities/SubmitRecord.java
@@ -14,6 +14,9 @@
package com.google.gerrit.entities;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -107,6 +110,17 @@
public Status status;
public Account.Id appliedBy;
+ /**
+ * Returns a new instance of {@link Label} that contains a new instance for each mutable field.
+ */
+ public Label deepCopy() {
+ Label copy = new Label();
+ copy.label = label;
+ copy.status = status;
+ copy.appliedBy = appliedBy;
+ return copy;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -134,6 +148,23 @@
}
}
+ /**
+ * Returns a new instance of {@link SubmitRecord} that contains a new instance for each mutable
+ * field.
+ */
+ public SubmitRecord deepCopy() {
+ SubmitRecord copy = new SubmitRecord();
+ copy.status = status;
+ copy.errorMessage = errorMessage;
+ if (labels != null) {
+ copy.labels = labels.stream().map(Label::deepCopy).collect(toImmutableList());
+ }
+ if (requirements != null) {
+ copy.requirements = ImmutableList.copyOf(requirements);
+ }
+ return copy;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfo.java b/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 528efe3..b5f40ce 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -14,6 +14,8 @@
package com.google.gerrit.extensions.common;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.client.SubmitType;
@@ -110,4 +112,14 @@
public List<PluginDefinedInfo> plugins;
public Collection<TrackingIdInfo> trackingIds;
public Collection<SubmitRequirementInfo> requirements;
+
+ public ChangeInfo() {}
+
+ public ChangeInfo(ChangeMessageInfo... messages) {
+ this.messages = ImmutableList.copyOf(messages);
+ }
+
+ public ChangeInfo(Map<String, RevisionInfo> revisions) {
+ this.revisions = ImmutableMap.copyOf(revisions);
+ }
}
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
new file mode 100644
index 0000000..647dead
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
@@ -0,0 +1,191 @@
+// Copyright (C) 2021 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.extensions.common;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.groupingBy;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Gets the differences between two {@link ChangeInfo}s.
+ *
+ * <p>This must be in package {@code com.google.gerrit.extensions.common} for access to protected
+ * constructors.
+ *
+ * <p>This assumes that every class reachable from {@link ChangeInfo} has a non-private constructor
+ * with zero parameters and overrides the equals method.
+ */
+public final class ChangeInfoDiffer {
+
+ /**
+ * Returns the difference between two instances of {@link ChangeInfo}.
+ *
+ * <p>The {@link ChangeInfoDifference} returned has the following properties:
+ *
+ * <p>Unrepeated fields are present in the difference returned when they differ between {@code
+ * oldChangeInfo} and {@code newChangeInfo}. When there's an unrepeated field that's not a {@link
+ * String}, primitive, or enum, its fields are only returned when they differ.
+ *
+ * <p>Entries in {@link Map} fields are returned when a key is present in {@code newChangeInfo}
+ * and not {@code oldChangeInfo}. If a key is present in both, the diff of the value is returned.
+ *
+ * <p>{@link Collection} fields in {@link ChangeInfoDifference#added()} contain only items found
+ * in {@code newChangeInfo} and not {@code oldChangeInfo}.
+ *
+ * <p>{@link Collection} fields in {@link ChangeInfoDifference#removed()} contain only items found
+ * in {@code oldChangeInfo} and not {@code newChangeInfo}.
+ *
+ * @param oldChangeInfo the previous {@link ChangeInfo} to diff against {@code newChangeInfo}
+ * @param newChangeInfo the {@link ChangeInfo} to diff against {@code oldChangeInfo}
+ * @return the difference between the given {@link ChangeInfo}s
+ */
+ public static ChangeInfoDifference getDifference(
+ ChangeInfo oldChangeInfo, ChangeInfo newChangeInfo) {
+ return ChangeInfoDifference.create(
+ /* added= */ getAdded(oldChangeInfo, newChangeInfo),
+ /* removed= */ getAdded(newChangeInfo, oldChangeInfo));
+ }
+
+ @SuppressWarnings("unchecked") // reflection is used to construct instances of T
+ private static <T> T getAdded(T oldValue, T newValue) {
+ T toPopulate = (T) construct(newValue.getClass());
+ if (toPopulate == null) {
+ return null;
+ }
+
+ for (Field field : newValue.getClass().getDeclaredFields()) {
+ Object newFieldObj = get(field, newValue);
+ if (oldValue == null || newFieldObj == null) {
+ set(field, toPopulate, newFieldObj);
+ continue;
+ }
+
+ Object oldFieldObj = get(field, oldValue);
+ if (newFieldObj.equals(oldFieldObj)) {
+ continue;
+ }
+
+ if (isSimple(field.getType()) || oldFieldObj == null) {
+ set(field, toPopulate, newFieldObj);
+ } else if (newFieldObj instanceof Collection) {
+ set(
+ field,
+ toPopulate,
+ getAddedForCollection((Collection<?>) oldFieldObj, (Collection<?>) newFieldObj));
+ } else if (newFieldObj instanceof Map) {
+ set(field, toPopulate, getAddedForMap((Map<?, ?>) oldFieldObj, (Map<?, ?>) newFieldObj));
+ } else {
+ // Recurse to set all fields in the non-primitive object.
+ set(field, toPopulate, getAdded(oldFieldObj, newFieldObj));
+ }
+ }
+ return toPopulate;
+ }
+
+ @VisibleForTesting
+ static boolean isSimple(Class<?> c) {
+ return c.isPrimitive()
+ || c.isEnum()
+ || String.class.isAssignableFrom(c)
+ || Number.class.isAssignableFrom(c)
+ || Boolean.class.isAssignableFrom(c)
+ || Timestamp.class.isAssignableFrom(c);
+ }
+
+ @VisibleForTesting
+ static Object construct(Class<?> c) {
+ // Only use constructors without parameters because we can't determine what values to pass.
+ return stream(c.getDeclaredConstructors())
+ .filter(constructor -> constructor.getParameterCount() == 0)
+ .findAny()
+ .map(ChangeInfoDiffer::construct)
+ .orElseThrow(
+ () ->
+ new IllegalStateException("Class " + c + " must have a zero argument constructor"));
+ }
+
+ private static Object construct(Constructor<?> constructor) {
+ try {
+ return constructor.newInstance();
+ } catch (ReflectiveOperationException e) {
+ throw new IllegalStateException("Failed to construct class " + constructor.getName(), e);
+ }
+ }
+
+ /** @return {@code null} if nothing has been added to {@code oldCollection} */
+ private static ImmutableList<?> getAddedForCollection(
+ Collection<?> oldCollection, Collection<?> newCollection) {
+ ImmutableList<?> notInOldCollection = getAdditions(oldCollection, newCollection);
+ return notInOldCollection.isEmpty() ? null : notInOldCollection;
+ }
+
+ private static ImmutableList<Object> getAdditions(
+ Collection<?> oldCollection, Collection<?> newCollection) {
+ Map<Object, List<Object>> duplicatesMap = newCollection.stream().collect(groupingBy(v -> v));
+ oldCollection.forEach(
+ v -> {
+ if (duplicatesMap.containsKey(v)) {
+ duplicatesMap.get(v).remove(v);
+ }
+ });
+ return duplicatesMap.values().stream().flatMap(Collection::stream).collect(toImmutableList());
+ }
+
+ /** @return {@code null} if nothing has been added to {@code oldMap} */
+ private static ImmutableMap<Object, Object> getAddedForMap(Map<?, ?> oldMap, Map<?, ?> newMap) {
+ ImmutableMap.Builder<Object, Object> additionsBuilder = ImmutableMap.builder();
+ for (Map.Entry<?, ?> entry : newMap.entrySet()) {
+ Object added = getAdded(oldMap.get(entry.getKey()), entry.getValue());
+ if (added != null) {
+ additionsBuilder.put(entry.getKey(), added);
+ }
+ }
+ ImmutableMap<Object, Object> additions = additionsBuilder.build();
+ return additions.isEmpty() ? null : additions;
+ }
+
+ private static Object get(Field field, Object obj) {
+ try {
+ return field.get(obj);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(
+ String.format("Access denied getting field %s in %s", field.getName(), obj.getClass()),
+ e);
+ }
+ }
+
+ private static void set(Field field, Object obj, Object value) {
+ try {
+ field.set(obj, value);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(
+ String.format(
+ "Access denied setting field %s in %s", field.getName(), obj.getClass().getName()),
+ e);
+ }
+ }
+
+ private ChangeInfoDiffer() {}
+}
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java b/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java
new file mode 100644
index 0000000..269c673
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 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.extensions.common;
+
+import com.google.auto.value.AutoValue;
+
+/** The difference between two {@link ChangeInfo}s returned by {@link ChangeInfoDiffer}. */
+@AutoValue
+public abstract class ChangeInfoDifference {
+
+ public abstract ChangeInfo added();
+
+ public abstract ChangeInfo removed();
+
+ public static ChangeInfoDifference create(ChangeInfo added, ChangeInfo removed) {
+ return new AutoValue_ChangeInfoDifference(added, removed);
+ }
+}
diff --git a/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java b/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java
index 07ad71b..10456ff 100644
--- a/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java
@@ -26,6 +26,12 @@
public String message;
public Integer _revisionNumber;
+ public ChangeMessageInfo() {}
+
+ public ChangeMessageInfo(String message) {
+ this.message = message;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof ChangeMessageInfo) {
diff --git a/java/com/google/gerrit/extensions/common/CommentInfo.java b/java/com/google/gerrit/extensions/common/CommentInfo.java
index fcce2b3..35587a0 100644
--- a/java/com/google/gerrit/extensions/common/CommentInfo.java
+++ b/java/com/google/gerrit/extensions/common/CommentInfo.java
@@ -30,6 +30,9 @@
*/
public List<ContextLineInfo> contextLines;
+ /** Mime type of the underlying source file. Only available if context lines are requested. */
+ public String sourceContentType;
+
@Override
public boolean equals(Object o) {
if (super.equals(o)) {
diff --git a/java/com/google/gerrit/extensions/common/RevisionInfo.java b/java/com/google/gerrit/extensions/common/RevisionInfo.java
index ea61f31..f710ab7 100644
--- a/java/com/google/gerrit/extensions/common/RevisionInfo.java
+++ b/java/com/google/gerrit/extensions/common/RevisionInfo.java
@@ -36,6 +36,21 @@
public PushCertificateInfo pushCertificate;
public String description;
+ public RevisionInfo() {}
+
+ public RevisionInfo(String ref) {
+ this.ref = ref;
+ }
+
+ public RevisionInfo(String ref, int number) {
+ this.ref = ref;
+ _number = number;
+ }
+
+ public RevisionInfo(AccountInfo uploader) {
+ this.uploader = uploader;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof RevisionInfo) {
diff --git a/java/com/google/gerrit/server/ApprovalInference.java b/java/com/google/gerrit/server/ApprovalInference.java
index 40bf249..d77427a 100644
--- a/java/com/google/gerrit/server/ApprovalInference.java
+++ b/java/com/google/gerrit/server/ApprovalInference.java
@@ -25,6 +25,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.LabelType;
+import com.google.gerrit.entities.LabelTypes;
import com.google.gerrit.entities.Patch.ChangeType;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.PatchSetApproval;
@@ -368,11 +369,12 @@
"change kind for patch set %d of change %d against prior patch set %s is %s",
ps.id().get(), ps.id().changeId().get(), priorPatchSet.getValue().id().changeId(), kind);
PatchList patchList = null;
+ LabelTypes labelTypes = project.getLabelTypes();
for (PatchSetApproval psa : priorApprovals) {
if (resultByUser.contains(psa.label(), psa.accountId())) {
continue;
}
- LabelType type = project.getLabelTypes().byLabel(psa.labelId());
+ LabelType type = labelTypes.byLabel(psa.labelId());
// Only compute patchList if there is a relevant label, since this is expensive.
if (patchList == null && type != null && type.isCopyAllScoresIfListOfFilesDidNotChange()) {
patchList = getPatchList(project, ps, priorPatchSet);
diff --git a/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java b/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java
index 3d75349..0e34e36 100644
--- a/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java
+++ b/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java
@@ -68,7 +68,7 @@
@Override
protected void configure() {
persist(CACHE_NAME, CommentContextKey.class, CommentContext.class)
- .version(2)
+ .version(4)
.diskLimit(1 << 30) // limit the total cache size to 1 GB
.maximumWeight(1 << 23) // Limit the size of the in-memory cache to 8 MB
.weigher(CommentContextWeigher.class)
@@ -149,6 +149,7 @@
@Override
public byte[] serialize(CommentContext commentContext) {
AllCommentContextProto.Builder allBuilder = AllCommentContextProto.newBuilder();
+ allBuilder.setContentType(commentContext.contentType());
commentContext
.lines()
@@ -165,9 +166,10 @@
@Override
public CommentContext deserialize(byte[] in) {
ImmutableMap.Builder<Integer, String> contextLinesMap = ImmutableMap.builder();
- Protos.parseUnchecked(AllCommentContextProto.parser(), in).getContextList().stream()
+ AllCommentContextProto proto = Protos.parseUnchecked(AllCommentContextProto.parser(), in);
+ proto.getContextList().stream()
.forEach(c -> contextLinesMap.put(c.getLineNumber(), c.getContextLine()));
- return CommentContext.create(contextLinesMap.build());
+ return CommentContext.create(contextLinesMap.build(), proto.getContentType());
}
}
diff --git a/java/com/google/gerrit/server/comment/CommentContextLoader.java b/java/com/google/gerrit/server/comment/CommentContextLoader.java
index c93f4b1..a5aca48 100644
--- a/java/com/google/gerrit/server/comment/CommentContextLoader.java
+++ b/java/com/google/gerrit/server/comment/CommentContextLoader.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.entities.Patch.COMMIT_MSG;
import static com.google.gerrit.entities.Patch.MERGE_LIST;
+import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static java.util.stream.Collectors.groupingBy;
import com.google.auto.value.AutoValue;
@@ -23,21 +24,28 @@
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.entities.Comment;
import com.google.gerrit.entities.CommentContext;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.ContextLineInfo;
+import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.patch.ComparisonType;
+import com.google.gerrit.server.patch.SrcContentResolver;
import com.google.gerrit.server.patch.Text;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import eu.medsea.mimeutil.MimeType;
+import eu.medsea.mimeutil.MimeUtil2;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
@@ -52,17 +60,25 @@
public class CommentContextLoader {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private final FileTypeRegistry registry;
private final GitRepositoryManager repoManager;
private final Project.NameKey project;
+ private final ProjectState projectState;
public interface Factory {
CommentContextLoader create(Project.NameKey project);
}
@Inject
- CommentContextLoader(GitRepositoryManager repoManager, @Assisted Project.NameKey project) {
+ CommentContextLoader(
+ FileTypeRegistry registry,
+ GitRepositoryManager repoManager,
+ ProjectCache projectCache,
+ @Assisted Project.NameKey project) {
+ this.registry = registry;
this.repoManager = repoManager;
this.project = project;
+ projectState = projectCache.get(project).orElseThrow(illegalState(project));
}
/**
@@ -122,7 +138,8 @@
ObjectReader reader, RevCommit commit, Range commentRange, int contextPadding)
throws IOException {
Text text = Text.forCommit(reader, commit);
- return createContext(text, commentRange, contextPadding);
+ return createContext(
+ text, commentRange, contextPadding, FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE);
}
private CommentContext getContextForMergeList(
@@ -130,7 +147,8 @@
throws IOException {
ComparisonType cmp = ComparisonType.againstParent(1);
Text text = Text.forMergeList(cmp, reader, commit);
- return createContext(text, commentRange, contextPadding);
+ return createContext(
+ text, commentRange, contextPadding, FileContentUtil.TEXT_X_GERRIT_MERGE_LIST);
}
private CommentContext getContextForFilePath(
@@ -150,12 +168,25 @@
return CommentContext.empty();
}
ObjectId id = tw.getObjectId(0);
- Text src = new Text(repo.open(id, Constants.OBJ_BLOB));
- return createContext(src, commentRange, contextPadding);
+ byte[] sourceContent = SrcContentResolver.getSourceContent(repo, id, tw.getFileMode(0));
+ Text textSrc = new Text(sourceContent);
+ String contentType = getContentType(tw, filePath, textSrc);
+ return createContext(textSrc, commentRange, contextPadding, contentType);
}
}
- private static CommentContext createContext(Text src, Range commentRange, int contextPadding) {
+ private String getContentType(TreeWalk tw, String filePath, Text src) {
+ PatchScript.FileMode fileMode = PatchScript.FileMode.fromJgitFileMode(tw.getFileMode(0));
+ String mimeType = MimeUtil2.UNKNOWN_MIME_TYPE.toString();
+ if (src.size() > 0 && PatchScript.FileMode.SYMLINK != fileMode) {
+ MimeType registryMimeType = registry.getMimeType(filePath, src.getContent());
+ mimeType = registryMimeType.toString();
+ }
+ return FileContentUtil.resolveContentType(projectState, filePath, fileMode, mimeType);
+ }
+
+ private static CommentContext createContext(
+ Text src, Range commentRange, int contextPadding, String contentType) {
if (commentRange.start() < 1 || commentRange.end() - 1 > src.size()) {
// TODO(ghareeb): We should throw an exception in this case. See
// https://bugs.chromium.org/p/gerrit/issues/detail?id=14102 which is an example where the
@@ -168,7 +199,7 @@
for (int i = commentRange.start(); i < commentRange.end(); i++) {
context.put(i, src.getString(i - 1));
}
- return CommentContext.create(context.build());
+ return CommentContext.create(context.build(), contentType);
}
/**
diff --git a/java/com/google/gerrit/server/git/validators/CommentCumulativeSizeValidator.java b/java/com/google/gerrit/server/git/validators/CommentCumulativeSizeValidator.java
index d507531..6e640f3 100644
--- a/java/com/google/gerrit/server/git/validators/CommentCumulativeSizeValidator.java
+++ b/java/com/google/gerrit/server/git/validators/CommentCumulativeSizeValidator.java
@@ -23,6 +23,7 @@
import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidationFailure;
import com.google.gerrit.extensions.validators.CommentValidator;
+import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.inject.Inject;
@@ -34,6 +35,8 @@
* issues. Note that autogenerated change messages are not subject to validation.
*/
public class CommentCumulativeSizeValidator implements CommentValidator {
+ public static final int DEFAULT_CUMULATIVE_COMMENT_SIZE_LIMIT = 3 << 20;
+
private final int maxCumulativeSize;
private final ChangeNotes.Factory notesFactory;
@@ -41,7 +44,9 @@
CommentCumulativeSizeValidator(
@GerritServerConfig Config serverConfig, ChangeNotes.Factory notesFactory) {
this.notesFactory = notesFactory;
- maxCumulativeSize = serverConfig.getInt("change", "cumulativeCommentSizeLimit", 3 << 20);
+ maxCumulativeSize =
+ serverConfig.getInt(
+ "change", "cumulativeCommentSizeLimit", DEFAULT_CUMULATIVE_COMMENT_SIZE_LIMIT);
}
@Override
@@ -55,7 +60,13 @@
notes.getRobotComments().values().stream())
.mapToInt(Comment::getApproximateSize)
.sum()
- + notes.getChangeMessages().stream().mapToInt(cm -> cm.getMessage().length()).sum();
+ + notes.getChangeMessages().stream()
+ // Auto-generated change messages are not counted for the limit. This method is not
+ // called when those change messages are created, but we should also skip them when
+ // counting the size for unrelated messages.
+ .filter(cm -> !ChangeMessagesUtil.isAutogenerated(cm.getTag()))
+ .mapToInt(cm -> cm.getMessage().length())
+ .sum();
int newCumulativeSize =
comments.stream().mapToInt(CommentForValidation::getApproximateSize).sum();
ImmutableList.Builder<CommentValidationFailure> failures = ImmutableList.builder();
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index c67df8b..1fde48c 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -234,7 +234,7 @@
List<CommitValidationMessage> messages = new ArrayList<>();
try {
for (CommitValidationListener commitValidator : validators) {
- try (TraceTimer traceTimer =
+ try (TraceTimer ignored =
TraceContext.newTimer(
"Running CommitValidationListener",
Metadata.builder()
@@ -330,7 +330,7 @@
} else if (idList.size() > 1) {
throw new CommitValidationException(MULTIPLE_CHANGE_ID_MSG, messages);
} else {
- String v = idList.get(idList.size() - 1).trim();
+ String v = idList.get(0).trim();
// Reject Change-Ids with wrong format and invalid placeholder ID from
// Egit (I0000000000000000000000000000000000000000).
if (!CHANGE_ID.matcher(v).matches() || v.matches("^I00*$")) {
@@ -450,10 +450,11 @@
private long countChangedFiles(CommitReceivedEvent receiveEvent) throws IOException {
try (Repository repository = repoManager.openRepository(receiveEvent.project.getNameKey());
- RevWalk revWalk = new RevWalk(repository);
DiffFormatter diffFormatter = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
- diffFormatter.setReader(revWalk.getObjectReader(), repository.getConfig());
- diffFormatter.setDetectRenames(true);
+ diffFormatter.setRepository(repository);
+ // Do not detect renames; that would require reading file contents, which is slow for large
+ // files.
+ diffFormatter.setDetectRenames(false);
// For merge commits, i.e. >1 parents, we use parent #0 by convention.
List<DiffEntry> diffEntries =
diffFormatter.scan(
@@ -554,7 +555,7 @@
/** Execute commit validation plug-ins */
public static class PluginCommitValidationListener implements CommitValidationListener {
- private boolean skipValidation;
+ private final boolean skipValidation;
private final PluginSetContext<CommitValidationListener> commitValidationListeners;
public PluginCommitValidationListener(
@@ -596,7 +597,8 @@
@Override
public boolean shouldValidateAllCommits() {
- return commitValidationListeners.stream().anyMatch(v -> v.shouldValidateAllCommits());
+ return commitValidationListeners.stream()
+ .anyMatch(CommitValidationListener::shouldValidateAllCommits);
}
}
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 59600e0..346d2a6 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -815,7 +815,7 @@
});
public static final SubmitRuleOptions SUBMIT_RULE_OPTIONS_LENIENT =
- SubmitRuleOptions.builder().allowClosed(true).build();
+ SubmitRuleOptions.builder().recomputeOnClosedChanges(true).build();
public static final SubmitRuleOptions SUBMIT_RULE_OPTIONS_STRICT =
SubmitRuleOptions.builder().build();
diff --git a/java/com/google/gerrit/server/patch/ComparisonType.java b/java/com/google/gerrit/server/patch/ComparisonType.java
index 260c507..eca2658 100644
--- a/java/com/google/gerrit/server/patch/ComparisonType.java
+++ b/java/com/google/gerrit/server/patch/ComparisonType.java
@@ -16,34 +16,40 @@
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
-import static java.util.Objects.requireNonNull;
+import com.google.auto.value.AutoValue;
+import com.google.gerrit.server.cache.proto.Cache.FileDiffOutputProto;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Optional;
-public class ComparisonType {
+/** Relation between the old and new commits used in the diff. */
+@AutoValue
+public abstract class ComparisonType {
- /** 1-based parent */
- private final Integer parentNum;
+ /**
+ * 1-based parent. Available if the old commit is the parent of the new commit and old commit is
+ * not the auto-merge.
+ */
+ abstract Optional<Integer> parentNum();
- private final boolean autoMerge;
+ abstract boolean autoMerge();
public static ComparisonType againstOtherPatchSet() {
- return new ComparisonType(null, false);
+ return new AutoValue_ComparisonType(Optional.empty(), false);
}
public static ComparisonType againstParent(int parentNum) {
- return new ComparisonType(parentNum, false);
+ return new AutoValue_ComparisonType(Optional.of(parentNum), false);
}
public static ComparisonType againstAutoMerge() {
- return new ComparisonType(null, true);
+ return new AutoValue_ComparisonType(Optional.empty(), true);
}
- private ComparisonType(Integer parentNum, boolean autoMerge) {
- this.parentNum = parentNum;
- this.autoMerge = autoMerge;
+ private static ComparisonType create(Optional<Integer> parent, boolean automerge) {
+ return new AutoValue_ComparisonType(parent, automerge);
}
public boolean isAgainstParentOrAutoMerge() {
@@ -51,27 +57,43 @@
}
public boolean isAgainstParent() {
- return parentNum != null;
+ return parentNum().isPresent();
}
public boolean isAgainstAutoMerge() {
- return autoMerge;
+ return autoMerge();
}
- public int getParentNum() {
- requireNonNull(parentNum);
- return parentNum;
+ public Optional<Integer> getParentNum() {
+ return parentNum();
}
void writeTo(OutputStream out) throws IOException {
- writeVarInt32(out, parentNum != null ? parentNum : 0);
- writeVarInt32(out, autoMerge ? 1 : 0);
+ writeVarInt32(out, isAgainstParent() ? parentNum().get() : 0);
+ writeVarInt32(out, autoMerge() ? 1 : 0);
}
static ComparisonType readFrom(InputStream in) throws IOException {
int p = readVarInt32(in);
- Integer parentNum = p > 0 ? p : null;
+ Optional<Integer> parentNum = p > 0 ? Optional.of(p) : Optional.empty();
boolean autoMerge = readVarInt32(in) != 0;
- return new ComparisonType(parentNum, autoMerge);
+ return create(parentNum, autoMerge);
+ }
+
+ public FileDiffOutputProto.ComparisonType toProto() {
+ FileDiffOutputProto.ComparisonType.Builder builder =
+ FileDiffOutputProto.ComparisonType.newBuilder().setAutoMerge(autoMerge());
+ if (parentNum().isPresent()) {
+ builder.setParentNum(parentNum().get());
+ }
+ return builder.build();
+ }
+
+ public static ComparisonType fromProto(FileDiffOutputProto.ComparisonType proto) {
+ Optional<Integer> parentNum = Optional.empty();
+ if (proto.hasField(FileDiffOutputProto.ComparisonType.getDescriptor().findFieldByNumber(1))) {
+ parentNum = Optional.of(proto.getParentNum());
+ }
+ return create(parentNum, proto.getAutoMerge());
}
}
diff --git a/java/com/google/gerrit/server/patch/DiffOperations.java b/java/com/google/gerrit/server/patch/DiffOperations.java
index 8b90531..93aefff 100644
--- a/java/com/google/gerrit/server/patch/DiffOperations.java
+++ b/java/com/google/gerrit/server/patch/DiffOperations.java
@@ -74,8 +74,9 @@
/**
* Returns the diff for a single file between a patchset commit against its parent or the
* auto-merge commit. For deleted files, the {@code fileName} parameter should contain the old
- * name of the file. This method will return {@link FileDiffOutput#empty(String)} if the requested
- * file identified by {@code fileName} has unchanged content or does not exist at both commits.
+ * name of the file. This method will return {@link FileDiffOutput#empty(String, ObjectId,
+ * ObjectId)} if the requested file identified by {@code fileName} has unchanged content or does
+ * not exist at both commits.
*
* @param project a project name representing a git repository.
* @param newCommit 20 bytes SHA-1 of the new commit used in the diff.
@@ -98,8 +99,8 @@
/**
* Returns the diff for a single file between two patchset commits. For deleted files, the {@code
* fileName} parameter should contain the old name of the file. This method will return {@link
- * FileDiffOutput#empty(String)} if the requested file identified by {@code fileName} has
- * unchanged content or does not exist at both commits.
+ * FileDiffOutput#empty(String, ObjectId, ObjectId)} if the requested file identified by {@code
+ * fileName} has unchanged content or does not exist at both commits.
*
* @param project a project name representing a git repository.
* @param oldCommit 20 bytes SHA-1 of the old commit used in the diff.
diff --git a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
index 16bd135..efb64bc 100644
--- a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
+++ b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
@@ -125,7 +125,9 @@
FileDiffCacheKey key =
createFileDiffCacheKey(project, diffParams.baseCommit(), newCommit, fileName, whitespace);
Map<String, FileDiffOutput> result = getModifiedFilesForKeys(ImmutableList.of(key));
- return result.containsKey(fileName) ? result.get(fileName) : FileDiffOutput.empty(fileName);
+ return result.containsKey(fileName)
+ ? result.get(fileName)
+ : FileDiffOutput.empty(fileName, key.oldCommit(), key.newCommit());
} catch (IOException e) {
throw new DiffNotAvailableException(
"Failed to evaluate the parent/base commit for commit " + newCommit, e);
@@ -143,7 +145,9 @@
FileDiffCacheKey key =
createFileDiffCacheKey(project, oldCommit, newCommit, fileName, whitespace);
Map<String, FileDiffOutput> result = getModifiedFilesForKeys(ImmutableList.of(key));
- return result.containsKey(fileName) ? result.get(fileName) : FileDiffOutput.empty(fileName);
+ return result.containsKey(fileName)
+ ? result.get(fileName)
+ : FileDiffOutput.empty(fileName, oldCommit, newCommit);
}
private Map<String, FileDiffOutput> getModifiedFiles(
diff --git a/java/com/google/gerrit/server/patch/MagicFile.java b/java/com/google/gerrit/server/patch/MagicFile.java
index aa6b11f..e42dd8c 100644
--- a/java/com/google/gerrit/server/patch/MagicFile.java
+++ b/java/com/google/gerrit/server/patch/MagicFile.java
@@ -93,7 +93,7 @@
}
default:
int uninterestingParent =
- comparisonType.isAgainstParent() ? comparisonType.getParentNum() : 1;
+ comparisonType.isAgainstParent() ? comparisonType.getParentNum().get() : 1;
b.append("Merge List:\n\n");
for (RevCommit commit : MergeListBuilder.build(rw, c, uninterestingParent)) {
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index c6f7acf..ccc8565 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -14,7 +14,8 @@
package com.google.gerrit.server.patch;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -31,6 +32,8 @@
import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.patch.DiffContentCalculator.DiffCalculatorResult;
import com.google.gerrit.server.patch.DiffContentCalculator.TextSource;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
+import com.google.gerrit.server.patch.filediff.TaggedEdit;
import com.google.inject.Inject;
import eu.medsea.mimeutil.MimeType;
import eu.medsea.mimeutil.MimeUtil2;
@@ -68,7 +71,8 @@
intralineDiffCalculator = calculator;
}
- PatchScript toPatchScript(Repository git, PatchList list, PatchListEntry content)
+ /** Convert into {@link PatchScript} using the old diff cache output. */
+ PatchScript toPatchScriptOld(Repository git, PatchList list, PatchListEntry content)
throws IOException {
PatchFileChange change =
@@ -87,6 +91,68 @@
return build(sides.a, sides.b, change);
}
+ /** Convert into {@link PatchScript} using the new diff cache output. */
+ PatchScript toPatchScriptNew(Repository git, FileDiffOutput content) throws IOException {
+ PatchFileChange change =
+ new PatchFileChange(
+ content.edits().stream().map(TaggedEdit::jgitEdit).collect(toImmutableList()),
+ content.edits().stream()
+ .filter(TaggedEdit::dueToRebase)
+ .map(TaggedEdit::jgitEdit)
+ .collect(toImmutableSet()),
+ content.headerLines(),
+ getOldName(content.oldPath(), content.changeType()),
+ getNewName(content.oldPath(), content.newPath(), content.changeType()),
+ content.changeType(),
+ content.patchType().orElse(null));
+ SidesResolver sidesResolver = new SidesResolver(git, content.comparisonType());
+ ResolvedSides sides =
+ resolveSides(
+ git,
+ sidesResolver,
+ oldName(change),
+ newName(change),
+ content.oldCommitId(),
+ content.newCommitId());
+ return build(sides.a, sides.b, change);
+ }
+
+ private String getOldName(Optional<String> oldName, ChangeType changeType) {
+ // TODO(ghareeb): We adapt the new diff cache output so that it's compatible with the old diff
+ // cache behaviour. Later on we can cleanup this logic a bit.
+ switch (changeType) {
+ case DELETED:
+ case ADDED:
+ case MODIFIED:
+ case REWRITE:
+ return null;
+ case COPIED:
+ case RENAMED:
+ return oldName.get();
+ default:
+ throw new IllegalArgumentException("Unsupported type " + changeType);
+ }
+ }
+
+ private String getNewName(
+ Optional<String> oldName, Optional<String> newName, ChangeType changeType) {
+ // TODO(ghareeb): logic for new path is confusing. We adapt the new diff cache output so that
+ // it's compatible with the existing behaviour of Get Diff. Later on we can cleanup this logic a
+ // bit.
+ switch (changeType) {
+ case DELETED:
+ return oldName.get();
+ case ADDED:
+ case MODIFIED:
+ case REWRITE:
+ case COPIED:
+ case RENAMED:
+ return newName.get();
+ default:
+ throw new IllegalArgumentException("Unsupported type " + changeType);
+ }
+ }
+
private ResolvedSides resolveSides(
Repository git,
SidesResolver sidesResolver,
@@ -352,16 +418,8 @@
byte[] srcContent;
if (reuse) {
srcContent = other.srcContent;
-
- } else if (mode.getObjectType() == Constants.OBJ_BLOB) {
- srcContent = Text.asByteArray(db.open(id, Constants.OBJ_BLOB));
-
- } else if (mode.getObjectType() == Constants.OBJ_COMMIT) {
- String strContent = "Subproject commit " + ObjectId.toString(id);
- srcContent = strContent.getBytes(UTF_8);
-
} else {
- srcContent = Text.NO_BYTES;
+ srcContent = SrcContentResolver.getSourceContent(db, id, mode);
}
String mimeType = MimeUtil2.UNKNOWN_MIME_TYPE.toString();
DisplayMethod displayMethod = DisplayMethod.DIFF;
@@ -405,12 +463,7 @@
if (mode == FileMode.MISSING) {
displayMethod = DisplayMethod.NONE;
}
- PatchScript.FileMode fileMode = PatchScript.FileMode.FILE;
- if (mode == FileMode.SYMLINK) {
- fileMode = PatchScript.FileMode.SYMLINK;
- } else if (mode == FileMode.GITLINK) {
- fileMode = PatchScript.FileMode.GITLINK;
- }
+ PatchScript.FileMode fileMode = PatchScript.FileMode.fromJgitFileMode(mode);
return new PatchSide(
treeId, path, id, mode, srcContent, src, mimeType, displayMethod, fileMode);
}
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index 826198f..91dc6e3 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -24,17 +24,25 @@
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.metrics.Counter1;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Field;
+import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LargeObjectException;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchScriptBuilder.IntraLineDiffCalculatorResult;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -42,15 +50,23 @@
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.IOException;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.lang.exception.ExceptionUtils;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@@ -66,7 +82,8 @@
@Assisted("patchSetA") PatchSet.Id patchSetA,
@Assisted("patchSetB") PatchSet.Id patchSetB,
DiffPreferencesInfo diffPrefs,
- CurrentUser currentUser);
+ CurrentUser currentUser,
+ boolean useNewDiffCache);
PatchScriptFactory create(
ChangeNotes notes,
@@ -74,13 +91,37 @@
int parentNum,
PatchSet.Id patchSetB,
DiffPreferencesInfo diffPrefs,
- CurrentUser currentUser);
+ CurrentUser currentUser,
+ boolean useNewDiffCache);
+ }
+
+ /** These metrics are temporary for launching the new redesigned diff cache. */
+ @Singleton
+ static class Metrics {
+ final Counter1<String> diffs;
+ static final String MATCH = "match";
+ static final String MISMATCH = "mismatch";
+ static final String ERROR = "error";
+
+ @Inject
+ Metrics(MetricMaker metricMaker) {
+ diffs =
+ metricMaker.newCounter(
+ "diff/get_diff/dark_launch",
+ new Description(
+ "Total number of matching, non-matching, or error in diffs in the old and new diff cache implementations.")
+ .setRate()
+ .setUnit("count"),
+ Field.ofString("type", Metadata.Builder::eventType).build());
+ }
}
private final GitRepositoryManager repoManager;
private final PatchSetUtil psUtil;
private final Provider<PatchScriptBuilder> builderFactory;
private final PatchListCache patchListCache;
+ private final Metrics metrics;
+ private final ExecutorService executor;
private final String fileName;
@Nullable private final PatchSet.Id psa;
@@ -92,11 +133,17 @@
private final ChangeEditUtil editReader;
private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
+ private final DiffOperations diffOperations;
private final Change.Id changeId;
private ChangeNotes notes;
+ private final boolean runNewDiffCacheAsync;
+
+ // TODO(ghareeb): temporary field used for testing. Please remove.
+ private final boolean useNewDiffCache;
+
@AssistedInject
PatchScriptFactory(
GitRepositoryManager grm,
@@ -106,12 +153,17 @@
ChangeEditUtil editReader,
PermissionBackend permissionBackend,
ProjectCache projectCache,
+ DiffOperations diffOperations,
+ Metrics metrics,
+ @DiffExecutor ExecutorService executor,
+ @GerritServerConfig Config cfg,
@Assisted ChangeNotes notes,
@Assisted String fileName,
@Assisted("patchSetA") @Nullable PatchSet.Id patchSetA,
@Assisted("patchSetB") PatchSet.Id patchSetB,
@Assisted DiffPreferencesInfo diffPrefs,
- @Assisted CurrentUser currentUser) {
+ @Assisted CurrentUser currentUser,
+ @Assisted boolean useNewDiffCache) {
this.repoManager = grm;
this.psUtil = psUtil;
this.builderFactory = builderFactory;
@@ -120,6 +172,9 @@
this.editReader = editReader;
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
+ this.diffOperations = diffOperations;
+ this.metrics = metrics;
+ this.executor = executor;
this.fileName = fileName;
this.psa = patchSetA;
@@ -128,6 +183,10 @@
this.diffPrefs = diffPrefs;
this.currentUser = currentUser;
+ this.runNewDiffCacheAsync =
+ cfg.getBoolean("cache", "diff_cache", "runNewDiffCacheAsync_getDiff", false);
+ this.useNewDiffCache = useNewDiffCache;
+
changeId = patchSetB.changeId();
}
@@ -140,12 +199,17 @@
ChangeEditUtil editReader,
PermissionBackend permissionBackend,
ProjectCache projectCache,
+ DiffOperations diffOperations,
+ Metrics metrics,
+ @DiffExecutor ExecutorService executor,
+ @GerritServerConfig Config cfg,
@Assisted ChangeNotes notes,
@Assisted String fileName,
@Assisted int parentNum,
@Assisted PatchSet.Id patchSetB,
@Assisted DiffPreferencesInfo diffPrefs,
- @Assisted CurrentUser currentUser) {
+ @Assisted CurrentUser currentUser,
+ @Assisted boolean useNewDiffCache) {
this.repoManager = grm;
this.psUtil = psUtil;
this.builderFactory = builderFactory;
@@ -154,6 +218,9 @@
this.editReader = editReader;
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
+ this.diffOperations = diffOperations;
+ this.metrics = metrics;
+ this.executor = executor;
this.fileName = fileName;
this.psa = null;
@@ -162,6 +229,10 @@
this.diffPrefs = diffPrefs;
this.currentUser = currentUser;
+ this.runNewDiffCacheAsync =
+ cfg.getBoolean("cache", "diff_cache", "runNewDiffCacheAsync_getDiff", false);
+ this.useNewDiffCache = useNewDiffCache;
+
changeId = patchSetB.changeId();
checkArgument(parentNum >= 0, "parentNum must be >= 0");
}
@@ -200,13 +271,31 @@
bId = edit.get().getEditCommit();
}
- final PatchList list = listFor(keyFor(aId, bId, diffPrefs.ignoreWhitespace));
- final PatchScriptBuilder b = newBuilder();
- final PatchListEntry content = list.get(fileName);
-
- return b.toPatchScript(git, list, content);
+ if (useNewDiffCache) {
+ FileDiffOutput fileDiffOutput =
+ aId == null
+ ? diffOperations.getModifiedFileAgainstParent(
+ notes.getProjectName(),
+ bId,
+ parentNum == -1 ? null : parentNum + 1,
+ fileName,
+ diffPrefs.ignoreWhitespace)
+ : diffOperations.getModifiedFile(
+ notes.getProjectName(), aId, bId, fileName, diffPrefs.ignoreWhitespace);
+ return newBuilder().toPatchScriptNew(git, fileDiffOutput);
+ }
+ PatchScriptBuilder patchScriptBuilder = newBuilder();
+ PatchList list = listFor(keyFor(aId, bId, diffPrefs.ignoreWhitespace));
+ PatchListEntry content = list.get(fileName);
+ PatchScript patchScript = patchScriptBuilder.toPatchScriptOld(git, list, content);
+ if (runNewDiffCacheAsync) {
+ runNewDiffCacheAsyncAndExportMetrics(git, aId, bId, patchScript);
+ }
+ return patchScript;
} catch (PatchListNotAvailableException e) {
throw new NoSuchChangeException(changeId, e);
+ } catch (DiffNotAvailableException e) {
+ throw new StorageException(e);
} catch (IOException e) {
logger.atSevere().withCause(e).log("File content unavailable");
throw new NoSuchChangeException(changeId, e);
@@ -222,6 +311,98 @@
}
}
+ private void runNewDiffCacheAsyncAndExportMetrics(
+ Repository git, ObjectId aId, ObjectId bId, PatchScript expected) {
+ @SuppressWarnings("unused")
+ Future<?> possiblyIgnoredError =
+ executor.submit(
+ () -> {
+ try {
+ FileDiffOutput fileDiffOutput =
+ aId == null
+ ? diffOperations.getModifiedFileAgainstParent(
+ notes.getProjectName(),
+ bId,
+ parentNum == -1 ? null : parentNum + 1,
+ fileName,
+ diffPrefs.ignoreWhitespace)
+ : diffOperations.getModifiedFile(
+ notes.getProjectName(), aId, bId, fileName, diffPrefs.ignoreWhitespace);
+ PatchScript patchScript = newBuilder().toPatchScriptNew(git, fileDiffOutput);
+ if (areEqualPatchscripts(patchScript, expected)) {
+ metrics.diffs.increment(metrics.MATCH);
+ } else {
+ metrics.diffs.increment(metrics.MISMATCH);
+ logger.atWarning().atMostEvery(10, TimeUnit.SECONDS).log(
+ "Mismatching diff for change %s, old commit ID: %s, new commit ID: %s, file name: %s.",
+ changeId.toString(), aId, bId, fileName);
+ }
+ } catch (DiffNotAvailableException | IOException e) {
+ metrics.diffs.increment(metrics.ERROR);
+ logger.atSevere().atMostEvery(10, TimeUnit.SECONDS).log(
+ String.format(
+ "Error computing new diff for change %s, old commit ID: %s, new commit ID: %s.\n",
+ changeId.toString(), aId, bId)
+ + ExceptionUtils.getStackTrace(e));
+ }
+ });
+ }
+
+ /**
+ * The comparison is not exhaustive but is using the most important fields. Comparing all fields
+ * will require some work in {@link PatchScript} to, e.g., convert it to autovalue. This
+ * comparison method shall give a strong signal that both patchscripts are almost identical.
+ */
+ private static boolean areEqualPatchscripts(PatchScript ps1, PatchScript ps2) {
+ boolean equal = true;
+ if (!ps1.getChangeType().equals(ps2.getChangeType())) {
+ equal = false;
+ logger.atWarning().log(
+ "Mismatching change type: old = %s, new = %s.", ps1.getChangeType(), ps2.getChangeType());
+ }
+ if (!ps1.getPatchHeader().equals(ps2.getPatchHeader())) {
+ equal = false;
+ logger.atWarning().log(
+ "Mismatching patch header: old = %s, new = %s.",
+ ps1.getPatchHeader(), ps2.getPatchHeader());
+ }
+ if (!Objects.equals(ps1.getOldName(), ps2.getOldName())) {
+ equal = false;
+ logger.atWarning().log(
+ "Mismatching old name: old = %s, new = %s.", ps1.getOldName(), ps2.getOldName());
+ }
+ if (!Objects.equals(ps1.getNewName(), ps2.getNewName())) {
+ equal = false;
+ logger.atWarning().log(
+ "Mismatching new name: old = %s, new = %s.", ps1.getNewName(), ps2.getNewName());
+ }
+ if (!ps1.getEdits().containsAll(ps2.getEdits())) {
+ equal = false;
+ logger.atWarning().log(
+ "Mismatching edits: old = %s, new = %s.", ps1.getEdits(), ps2.getEdits());
+ }
+ if (!ps2.getEdits().containsAll(ps1.getEdits())) {
+ equal = false;
+ logger.atWarning().log(
+ "Mismatching edits: old = %s, new = %s.", ps1.getEdits(), ps2.getEdits());
+ }
+ if (!ps1.getEditsDueToRebase().equals(ps2.getEditsDueToRebase())) {
+ equal = false;
+ logger.atWarning().log(
+ "Mismatching edits due to rebase: old = %s, new = %s.",
+ ps1.getEditsDueToRebase(), ps2.getEditsDueToRebase());
+ }
+ if (!ps1.getA().equals(ps2.getA())) {
+ equal = false;
+ logger.atWarning().log("Mismatching sparse file content in old commit.");
+ }
+ if (!ps1.getB().equals(ps2.getB())) {
+ equal = false;
+ logger.atWarning().log("Mismatching sparse file content in new commit.");
+ }
+ return equal;
+ }
+
private Optional<ObjectId> getAId() {
if (psa == null) {
return Optional.empty();
diff --git a/java/com/google/gerrit/server/patch/SrcContentResolver.java b/java/com/google/gerrit/server/patch/SrcContentResolver.java
new file mode 100644
index 0000000..9cd11d2
--- /dev/null
+++ b/java/com/google/gerrit/server/patch/SrcContentResolver.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 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.server.patch;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+/** Resolver of the source content of a specific file */
+public class SrcContentResolver {
+
+ private SrcContentResolver() {}
+
+ /**
+ * Return the source content of a specific file.
+ *
+ * @param repo Git repository.
+ * @param id Git Object ID of the file blob.
+ * @param fileMode File mode of the underlying file as recognized by Git.
+ * @return byte[] source content of the underlying file if the {@code id} is of type blob, or a
+ * textual representation of the file if it is a git submodule.
+ * @throws IOException the object ID does not exist in the repository or cannot be accessed.
+ */
+ public static byte[] getSourceContent(Repository repo, ObjectId id, FileMode fileMode)
+ throws IOException {
+ if (fileMode.getObjectType() == Constants.OBJ_BLOB) {
+ return Text.asByteArray(repo.open(id, Constants.OBJ_BLOB));
+ }
+ if (fileMode.getObjectType() == Constants.OBJ_COMMIT) {
+ String strContent = "Subproject commit " + ObjectId.toString(id);
+ return strContent.getBytes(UTF_8);
+ }
+ return Text.NO_BYTES;
+ }
+}
diff --git a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
index 63e0b7a..24c03c7 100644
--- a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
+++ b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
@@ -30,7 +30,9 @@
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.prettify.common.SparseFileContent.Accessor;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.LargeObjectException;
+import com.google.gerrit.server.git.validators.CommentCumulativeSizeValidator;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -41,6 +43,7 @@
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.lib.Config;
/**
* This class is used on submit to compute the diff between the latest approved patch-set, and the
@@ -58,15 +61,22 @@
private final ProjectCache projectCache;
private final PatchScriptFactory.Factory patchScriptFactoryFactory;
private final PatchListCache patchListCache;
+ private final int maxCumulativeSize;
@Inject
SubmitWithStickyApprovalDiff(
ProjectCache projectCache,
PatchScriptFactory.Factory patchScriptFactoryFactory,
- PatchListCache patchListCache) {
+ PatchListCache patchListCache,
+ @GerritServerConfig Config serverConfig) {
this.projectCache = projectCache;
this.patchScriptFactoryFactory = patchScriptFactoryFactory;
this.patchListCache = patchListCache;
+ maxCumulativeSize =
+ serverConfig.getInt(
+ "change",
+ "cumulativeCommentSizeLimit",
+ CommentCumulativeSizeValidator.DEFAULT_CUMULATIVE_COMMENT_SIZE_LIMIT);
}
public String apply(ChangeNotes notes, CurrentUser currentUser)
@@ -117,6 +127,16 @@
getDiffForFile(
notes, currentPatchset.id(), latestApprovedPatchsetId, patchListEntry, currentUser));
}
+ if (diff.length() > maxCumulativeSize) {
+ // The diff length is not counted as part of the limit (for technical reasons, since we'd
+ // have to call CommentCumulativeSizeValidator), but it's best not to post an extra large
+ // change message here.
+ return String.format(
+ "\n\n%d is the latest approved patch-set.\nThe change was submitted "
+ + "with many unreviewed changes (the diff is too large to show). Please review the "
+ + "diff.",
+ latestApprovedPatchsetId.get());
+ }
return diff.toString();
}
@@ -143,7 +163,8 @@
latestApprovedPatchsetId,
currentPatchsetId,
diffPreferencesInfo,
- currentUser);
+ currentUser,
+ /* useNewDiffCache= */ false);
PatchScript patchScript = null;
try {
patchScript = patchScriptFactory.call();
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
index 63f311b..1bb407d 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
@@ -93,6 +93,7 @@
persist(DIFF, FileDiffCacheKey.class, FileDiffOutput.class)
.maximumWeight(10 << 20)
.weigher(FileDiffWeigher.class)
+ .version(2)
.keySerializer(FileDiffCacheKey.Serializer.INSTANCE)
.valueSerializer(FileDiffOutput.Serializer.INSTANCE)
.loader(FileDiffLoader.class);
@@ -219,19 +220,16 @@
RawTextComparator cmp = comparatorFor(key.whitespace());
ComparisonType comparisonType =
getComparisonType(rw, reader, key.oldCommit(), key.newCommit());
- RevCommit aCommit =
- comparisonType.isAgainstParentOrAutoMerge()
- ? null
- : DiffUtil.getRevCommit(rw, key.oldCommit());
+ RevCommit aCommit = DiffUtil.getRevCommit(rw, key.oldCommit());
RevCommit bCommit = DiffUtil.getRevCommit(rw, key.newCommit());
return magicPath == MagicPath.COMMIT
- ? createCommitEntry(reader, aCommit, bCommit, cmp, key.diffAlgorithm())
+ ? createCommitEntry(reader, aCommit, bCommit, comparisonType, cmp, key.diffAlgorithm())
: createMergeListEntry(
reader, aCommit, bCommit, comparisonType, cmp, key.diffAlgorithm());
} catch (IOException e) {
logger.atWarning().log("Failed to compute commit entry for key %s", key);
}
- return FileDiffOutput.empty(key.newFilePath());
+ return FileDiffOutput.empty(key.newFilePath(), key.oldCommit(), key.newCommit());
}
private static RawTextComparator comparatorFor(Whitespace ws) {
@@ -255,13 +253,24 @@
ObjectReader reader,
RevCommit oldCommit,
RevCommit newCommit,
+ ComparisonType comparisonType,
RawTextComparator rawTextComparator,
GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm)
throws IOException {
- Text aText = oldCommit != null ? Text.forCommit(reader, oldCommit) : Text.EMPTY;
+ Text aText =
+ comparisonType.isAgainstParentOrAutoMerge()
+ ? Text.EMPTY
+ : Text.forCommit(reader, oldCommit);
Text bText = Text.forCommit(reader, newCommit);
return createMagicFileDiffOutput(
- rawTextComparator, oldCommit, aText, bText, Patch.COMMIT_MSG, diffAlgorithm);
+ oldCommit,
+ newCommit,
+ comparisonType,
+ rawTextComparator,
+ aText,
+ bText,
+ Patch.COMMIT_MSG,
+ diffAlgorithm);
}
private FileDiffOutput createMergeListEntry(
@@ -273,20 +282,31 @@
GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm)
throws IOException {
Text aText =
- oldCommit != null ? Text.forMergeList(comparisonType, reader, oldCommit) : Text.EMPTY;
+ comparisonType.isAgainstParentOrAutoMerge()
+ ? Text.EMPTY
+ : Text.forMergeList(comparisonType, reader, oldCommit);
Text bText = Text.forMergeList(comparisonType, reader, newCommit);
return createMagicFileDiffOutput(
- rawTextComparator, oldCommit, aText, bText, Patch.MERGE_LIST, diffAlgorithm);
+ oldCommit,
+ newCommit,
+ comparisonType,
+ rawTextComparator,
+ aText,
+ bText,
+ Patch.MERGE_LIST,
+ diffAlgorithm);
}
private static FileDiffOutput createMagicFileDiffOutput(
+ ObjectId oldCommit,
+ ObjectId newCommit,
+ ComparisonType comparisonType,
RawTextComparator rawTextComparator,
- RevCommit aCommit,
Text aText,
Text bText,
String fileName,
GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm) {
- byte[] rawHdr = getRawHeader(aCommit != null, fileName);
+ byte[] rawHdr = getRawHeader(!comparisonType.isAgainstParentOrAutoMerge(), fileName);
byte[] aContent = aText.getContent();
byte[] bContent = bText.getContent();
long size = bContent.length;
@@ -298,6 +318,9 @@
FileHeader fileHeader = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
Patch.ChangeType changeType = FileHeaderUtil.getChangeType(fileHeader);
return FileDiffOutput.builder()
+ .oldCommitId(oldCommit)
+ .newCommitId(newCommit)
+ .comparisonType(comparisonType)
.oldPath(FileHeaderUtil.getOldPath(fileHeader))
.newPath(FileHeaderUtil.getNewPath(fileHeader))
.changeType(changeType)
@@ -370,8 +393,13 @@
mainGitDiff.newPath().get())
: 0;
+ ObjectId oldCommit = augmentedKey.key().oldCommit();
+ ObjectId newCommit = augmentedKey.key().newCommit();
FileDiffOutput fileDiff =
FileDiffOutput.builder()
+ .oldCommitId(oldCommit)
+ .newCommitId(newCommit)
+ .comparisonType(getComparisonType(rw, reader, oldCommit, newCommit))
.changeType(mainGitDiff.changeType())
.patchType(mainGitDiff.patchType())
.oldPath(mainGitDiff.oldPath())
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java b/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
index 3348033..e7f47ef 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
@@ -24,16 +24,28 @@
import com.google.gerrit.proto.Protos;
import com.google.gerrit.server.cache.proto.Cache.FileDiffOutputProto;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
+import com.google.gerrit.server.patch.ComparisonType;
import com.google.protobuf.Descriptors.FieldDescriptor;
import java.io.Serializable;
import java.util.Optional;
import java.util.stream.Collectors;
+import org.eclipse.jgit.lib.ObjectId;
/** File diff for a single file path. Produced as output of the {@link FileDiffCache}. */
@AutoValue
public abstract class FileDiffOutput implements Serializable {
private static final long serialVersionUID = 1L;
+ /** The 20 bytes SHA-1 object ID of the old git commit used in the diff. */
+ public abstract ObjectId oldCommitId();
+
+ /** The 20 bytes SHA-1 object ID of the new git commit used in the diff. */
+ public abstract ObjectId newCommitId();
+
+ /** Comparison type of old and new commits: against another patchset, parent or auto-merge. */
+ public abstract ComparisonType comparisonType();
+
/**
* The file path at the old commit. Returns an empty Optional if {@link #changeType()} is equal to
* {@link ChangeType#ADDED}.
@@ -95,8 +107,11 @@
}
/** Returns an entity representing an unchanged file between two commits. */
- public static FileDiffOutput empty(String filePath) {
+ public static FileDiffOutput empty(String filePath, ObjectId oldCommitId, ObjectId newCommitId) {
return builder()
+ .oldCommitId(oldCommitId)
+ .newCommitId(newCommitId)
+ .comparisonType(ComparisonType.againstOtherPatchSet()) // not important
.oldPath(Optional.empty())
.newPath(Optional.of(filePath))
.changeType(ChangeType.MODIFIED)
@@ -124,6 +139,8 @@
if (newPath().isPresent()) {
result += stringSize(newPath().get());
}
+ result += 20 + 20; // old and new commit IDs
+ result += 4; // comparison type
result += 4; // changeType
if (patchType().isPresent()) {
result += 4;
@@ -140,6 +157,12 @@
@AutoValue.Builder
public abstract static class Builder {
+ public abstract Builder oldCommitId(ObjectId value);
+
+ public abstract Builder newCommitId(ObjectId value);
+
+ public abstract Builder comparisonType(ComparisonType value);
+
public abstract Builder oldPath(Optional<String> value);
public abstract Builder newPath(Optional<String> value);
@@ -173,8 +196,12 @@
@Override
public byte[] serialize(FileDiffOutput fileDiff) {
+ ObjectIdConverter idConverter = ObjectIdConverter.create();
FileDiffOutputProto.Builder builder =
FileDiffOutputProto.newBuilder()
+ .setOldCommit(idConverter.toByteString(fileDiff.oldCommitId().toObjectId()))
+ .setNewCommit(idConverter.toByteString(fileDiff.newCommitId().toObjectId()))
+ .setComparisonType(fileDiff.comparisonType().toProto())
.setSize(fileDiff.size())
.setSizeDelta(fileDiff.sizeDelta())
.addAllHeaderLines(fileDiff.headerLines())
@@ -212,9 +239,13 @@
@Override
public FileDiffOutput deserialize(byte[] in) {
+ ObjectIdConverter idConverter = ObjectIdConverter.create();
FileDiffOutputProto proto = Protos.parseUnchecked(FileDiffOutputProto.parser(), in);
FileDiffOutput.Builder builder = FileDiffOutput.builder();
builder
+ .oldCommitId(idConverter.fromByteString(proto.getOldCommit()))
+ .newCommitId(idConverter.fromByteString(proto.getNewCommit()))
+ .comparisonType(ComparisonType.fromProto(proto.getComparisonType()))
.size(proto.getSize())
.sizeDelta(proto.getSizeDelta())
.headerLines(proto.getHeaderLinesList().stream().collect(ImmutableList.toImmutableList()))
diff --git a/java/com/google/gerrit/server/patch/filediff/TaggedEdit.java b/java/com/google/gerrit/server/patch/filediff/TaggedEdit.java
index aef2f63..3720680 100644
--- a/java/com/google/gerrit/server/patch/filediff/TaggedEdit.java
+++ b/java/com/google/gerrit/server/patch/filediff/TaggedEdit.java
@@ -27,7 +27,11 @@
return new AutoValue_TaggedEdit(edit, dueToRebase);
}
- abstract Edit edit();
+ public abstract Edit edit();
- abstract boolean dueToRebase();
+ public org.eclipse.jgit.diff.Edit jgitEdit() {
+ return Edit.toJGitEdit(edit());
+ }
+
+ public abstract boolean dueToRebase();
}
diff --git a/java/com/google/gerrit/server/project/ProjectLevelConfig.java b/java/com/google/gerrit/server/project/ProjectLevelConfig.java
index 4825233..d82a318 100644
--- a/java/com/google/gerrit/server/project/ProjectLevelConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectLevelConfig.java
@@ -16,14 +16,15 @@
import static java.util.stream.Collectors.toList;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
+import com.google.gerrit.entities.ImmutableConfig;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import java.io.IOException;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
@@ -73,19 +74,17 @@
private final String fileName;
private final ProjectState project;
- private Config cfg;
+ private final ImmutableConfig immutableConfig;
- public ProjectLevelConfig(String fileName, ProjectState project, Config cfg) {
+ public ProjectLevelConfig(
+ String fileName, ProjectState project, @Nullable ImmutableConfig immutableConfig) {
this.fileName = fileName;
this.project = project;
- this.cfg = cfg;
+ this.immutableConfig = immutableConfig == null ? ImmutableConfig.EMPTY : immutableConfig;
}
public Config get() {
- if (cfg == null) {
- cfg = new Config();
- }
- return cfg;
+ return immutableConfig.mutableCopy();
}
public Config getWithInheritance() {
@@ -105,58 +104,61 @@
* @return a combined config.
*/
public Config getWithInheritance(boolean merge) {
- Config cfgWithInheritance = new Config();
- try {
- cfgWithInheritance.fromText(get().toText());
- } catch (ConfigInvalidException e) {
- // cannot happen
- }
- ProjectState parent = Iterables.getFirst(project.parents(), null);
- if (parent != null) {
- Config parentCfg = parent.getConfig(fileName).getWithInheritance();
- for (String section : parentCfg.getSections()) {
- Set<String> allNames = get().getNames(section);
- for (String name : parentCfg.getNames(section)) {
- String[] parentValues = parentCfg.getStringList(section, null, name);
- if (!allNames.contains(name)) {
- cfgWithInheritance.setStringList(section, null, name, Arrays.asList(parentValues));
- } else if (merge) {
- cfgWithInheritance.setStringList(
- section,
- null,
- name,
- Stream.concat(
- Arrays.stream(cfg.getStringList(section, null, name)),
- Arrays.stream(parentValues))
- .sorted()
- .distinct()
- .collect(toList()));
- }
- }
+ Config cfg = new Config();
+ // Traverse from All-Projects down to the current project
+ StreamSupport.stream(project.treeInOrder().spliterator(), false)
+ .forEach(
+ parent -> {
+ ImmutableConfig levelCfg = parent.getConfig(fileName).immutableConfig;
+ for (String section : levelCfg.getSections()) {
+ Set<String> allNames = cfg.getNames(section);
+ for (String name : levelCfg.getNames(section)) {
+ String[] levelValues = levelCfg.getStringList(section, null, name);
+ if (allNames.contains(name) && merge) {
+ cfg.setStringList(
+ section,
+ null,
+ name,
+ Stream.concat(
+ Arrays.stream(cfg.getStringList(section, null, name)),
+ Arrays.stream(levelValues))
+ .sorted()
+ .distinct()
+ .collect(toList()));
+ } else {
+ cfg.setStringList(section, null, name, Arrays.asList(levelValues));
+ }
+ }
- for (String subsection : parentCfg.getSubsections(section)) {
- allNames = get().getNames(section, subsection);
- for (String name : parentCfg.getNames(section, subsection)) {
- String[] parentValues = parentCfg.getStringList(section, subsection, name);
- if (!allNames.contains(name)) {
- cfgWithInheritance.setStringList(
- section, subsection, name, Arrays.asList(parentValues));
- } else if (merge) {
- cfgWithInheritance.setStringList(
- section,
- subsection,
- name,
- Streams.concat(
- Arrays.stream(cfg.getStringList(section, subsection, name)),
- Arrays.stream(parentValues))
- .sorted()
- .distinct()
- .collect(toList()));
- }
- }
- }
- }
- }
- return cfgWithInheritance;
+ for (String subsection : levelCfg.getSubsections(section)) {
+ allNames = cfg.getNames(section, subsection);
+
+ Set<String> allNamesLevelCfg = levelCfg.getNames(section, subsection);
+ if (allNamesLevelCfg.isEmpty()) {
+ // Set empty subsection.
+ cfg.setString(section, subsection, null, null);
+ } else {
+ for (String name : allNamesLevelCfg) {
+ String[] levelValues = levelCfg.getStringList(section, subsection, name);
+ if (allNames.contains(name) && merge) {
+ cfg.setStringList(
+ section,
+ subsection,
+ name,
+ Streams.concat(
+ Arrays.stream(cfg.getStringList(section, subsection, name)),
+ Arrays.stream(levelValues))
+ .sorted()
+ .distinct()
+ .collect(toList()));
+ } else {
+ cfg.setStringList(section, subsection, name, Arrays.asList(levelValues));
+ }
+ }
+ }
+ }
+ }
+ });
+ return cfg;
}
}
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index 8c024ef..249eb35 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.entities.PermissionRule.Action.ALLOW;
import static java.util.Comparator.comparing;
@@ -176,8 +177,9 @@
}
public ProjectLevelConfig getConfig(String fileName) {
- Optional<Config> rawConfig = cachedConfig.getProjectLevelConfig(fileName);
- return new ProjectLevelConfig(fileName, this, rawConfig.orElse(new Config()));
+ checkState(fileName.endsWith(".config"), "file name must end in .config. is: " + fileName);
+ return new ProjectLevelConfig(
+ fileName, this, cachedConfig.getParsedProjectLevelConfigs().get(fileName));
}
public long getMaxObjectSizeLimit() {
diff --git a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index 0e50bb0..c3bcd25 100644
--- a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.server.project.ProjectCache.noSuchProject;
import com.google.common.collect.Streams;
@@ -36,7 +37,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
-import java.util.stream.Collectors;
/**
* Evaluates a submit-like Prolog rule found in the rules.pl file of the current project and filters
@@ -121,10 +121,18 @@
return Collections.singletonList(ruleError("Error looking up change " + cd.getId(), e));
}
- if ((!opts.allowClosed() || OnlineReindexMode.isActive()) && change.isClosed()) {
- SubmitRecord rec = new SubmitRecord();
- rec.status = SubmitRecord.Status.CLOSED;
- return Collections.singletonList(rec);
+ if (change.isClosed() && (!opts.recomputeOnClosedChanges() || OnlineReindexMode.isActive())) {
+ return cd.notes().getSubmitRecords().stream()
+ .map(
+ r -> {
+ SubmitRecord record = r.deepCopy();
+ if (record.status == SubmitRecord.Status.OK) {
+ // Submit records that were OK when they got merged are CLOSED now.
+ record.status = SubmitRecord.Status.CLOSED;
+ }
+ return record;
+ })
+ .collect(toImmutableList());
}
// We evaluate all the plugin-defined evaluators,
@@ -133,7 +141,7 @@
.map(c -> c.call(s -> s.evaluate(cd)))
.filter(Optional::isPresent)
.map(Optional::get)
- .collect(Collectors.toList());
+ .collect(toImmutableList());
}
}
diff --git a/java/com/google/gerrit/server/project/SubmitRuleOptions.java b/java/com/google/gerrit/server/project/SubmitRuleOptions.java
index ad077c0..3b511e1 100644
--- a/java/com/google/gerrit/server/project/SubmitRuleOptions.java
+++ b/java/com/google/gerrit/server/project/SubmitRuleOptions.java
@@ -25,7 +25,7 @@
@AutoValue
public abstract class SubmitRuleOptions {
private static final SubmitRuleOptions defaults =
- new AutoValue_SubmitRuleOptions.Builder().allowClosed(false).build();
+ new AutoValue_SubmitRuleOptions.Builder().recomputeOnClosedChanges(false).build();
public static SubmitRuleOptions defaults() {
return defaults;
@@ -35,13 +35,16 @@
return defaults.toBuilder();
}
- public abstract boolean allowClosed();
+ /**
+ * True if the submit rules should be recomputed even when the change is already closed (merged).
+ */
+ public abstract boolean recomputeOnClosedChanges();
public abstract Builder toBuilder();
@AutoValue.Builder
public abstract static class Builder {
- public abstract SubmitRuleOptions.Builder allowClosed(boolean allowClosed);
+ public abstract SubmitRuleOptions.Builder recomputeOnClosedChanges(boolean allowClosed);
public abstract SubmitRuleOptions build();
}
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index bf56000..8886cca 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -885,7 +885,12 @@
submitRecords.put(options, records);
if (!change().isClosed() && submitRecords.size() == 1) {
// Cache the SubmitRecord with allowClosed = !allowClosed as the SubmitRecord are the same.
- submitRecords.put(options.toBuilder().allowClosed(!options.allowClosed()).build(), records);
+ submitRecords.put(
+ options
+ .toBuilder()
+ .recomputeOnClosedChanges(!options.recomputeOnClosedChanges())
+ .build(),
+ records);
}
}
return records;
diff --git a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index 4922b57..1b6dc62 100644
--- a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -262,7 +262,8 @@
}
if (includeSubmitRecords) {
- SubmitRuleOptions options = SubmitRuleOptions.builder().allowClosed(true).build();
+ SubmitRuleOptions options =
+ SubmitRuleOptions.builder().recomputeOnClosedChanges(true).build();
eventFactory.addSubmitRecords(c, submitRuleEvaluatorFactory.create(options).evaluate(d));
}
diff --git a/java/com/google/gerrit/server/restapi/change/CommentJson.java b/java/com/google/gerrit/server/restapi/change/CommentJson.java
index 77b58c6..edc8fcf 100644
--- a/java/com/google/gerrit/server/restapi/change/CommentJson.java
+++ b/java/com/google/gerrit/server/restapi/change/CommentJson.java
@@ -171,7 +171,10 @@
allComments.stream().map(this::createCommentContextKey).collect(toList());
ImmutableMap<CommentContextKey, CommentContext> allContext = commentContextCache.getAll(keys);
for (T c : allComments) {
- c.contextLines = toContextLineInfoList(allContext.get(createCommentContextKey(c)));
+ CommentContextKey contextKey = createCommentContextKey(c);
+ CommentContext commentContext = allContext.get(contextKey);
+ c.contextLines = toContextLineInfoList(commentContext);
+ c.sourceContentType = commentContext.contentType();
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index b8902b7..af1236e 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -91,6 +91,10 @@
@Option(name = "--intraline")
boolean intraline;
+ // TODO(ghareeb): This is a temporary parameter for debugging. Please remove.
+ @Option(name = "--use-new-diff-cache")
+ boolean useNewDiffCache;
+
@Inject
GetDiff(
ProjectCache projectCache,
@@ -139,13 +143,15 @@
}
psf =
patchScriptFactoryFactory.create(
- notes, fileName, basePatchSet.id(), pId, prefs, currentUser.get());
+ notes, fileName, basePatchSet.id(), pId, prefs, currentUser.get(), useNewDiffCache);
} else if (parentNum > 0) {
psf =
patchScriptFactoryFactory.create(
- notes, fileName, parentNum - 1, pId, prefs, currentUser.get());
+ notes, fileName, parentNum - 1, pId, prefs, currentUser.get(), useNewDiffCache);
} else {
- psf = patchScriptFactoryFactory.create(notes, fileName, null, pId, prefs, currentUser.get());
+ psf =
+ patchScriptFactoryFactory.create(
+ notes, fileName, null, pId, prefs, currentUser.get(), useNewDiffCache);
}
try {
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index f486650..ccea90c 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -120,7 +120,7 @@
private static final SubmitRuleOptions SUBMIT_RULE_OPTIONS = SubmitRuleOptions.builder().build();
private static final SubmitRuleOptions SUBMIT_RULE_OPTIONS_ALLOW_CLOSED =
- SUBMIT_RULE_OPTIONS.toBuilder().allowClosed(true).build();
+ SUBMIT_RULE_OPTIONS.toBuilder().recomputeOnClosedChanges(true).build();
public static class CommitStatus {
private final ImmutableMap<Change.Id, ChangeData> changes;
diff --git a/java/gerrit/PRED_commit_delta_4.java b/java/gerrit/PRED_commit_delta_4.java
index 6e971fc..502b15b 100644
--- a/java/gerrit/PRED_commit_delta_4.java
+++ b/java/gerrit/PRED_commit_delta_4.java
@@ -14,11 +14,10 @@
package gerrit;
-import com.google.gerrit.entities.Patch;
-import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.rules.StoredValues;
import com.googlecode.prolog_cafe.exceptions.IllegalTypeException;
+import com.googlecode.prolog_cafe.exceptions.JavaException;
import com.googlecode.prolog_cafe.exceptions.PInstantiationException;
import com.googlecode.prolog_cafe.exceptions.PrologException;
import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
@@ -28,8 +27,15 @@
import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
import com.googlecode.prolog_cafe.lang.VariableTerm;
+import java.io.IOException;
import java.util.Iterator;
+import java.util.List;
import java.util.regex.Pattern;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.io.DisabledOutputStream;
/**
* Given a regular expression, checks it against the file list in the most recent patchset of a
@@ -76,10 +82,22 @@
engine.r3 = arg3;
engine.r4 = arg4;
- PatchList pl = StoredValues.PATCH_LIST.get(engine);
- Iterator<PatchListEntry> iter = pl.getPatches().iterator();
+ Repository repository = StoredValues.REPOSITORY.get(engine);
- engine.r5 = new JavaObjectTerm(iter);
+ try (DiffFormatter diffFormatter = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
+ diffFormatter.setRepository(repository);
+ // Do not detect renames; that would require reading file contents, which is slow for large
+ // files.
+ RevCommit commit = StoredValues.COMMIT.get(engine);
+ List<DiffEntry> diffEntries =
+ diffFormatter.scan(
+ // In case of a merge commit, i.e. >1 parents, we use parent #0 by convention. So
+ // parent #0 is always the right choice, if it exists.
+ commit.getParentCount() > 0 ? commit.getParent(0) : null, commit);
+ engine.r5 = new JavaObjectTerm(diffEntries.iterator());
+ } catch (IOException e) {
+ throw new JavaException(e);
+ }
return engine.jtry5(commit_delta_check, commit_delta_next);
}
@@ -95,23 +113,22 @@
Pattern regex = (Pattern) ((JavaObjectTerm) a1).object();
@SuppressWarnings("unchecked")
- Iterator<PatchListEntry> iter = (Iterator<PatchListEntry>) ((JavaObjectTerm) a5).object();
+ Iterator<DiffEntry> iter = (Iterator<DiffEntry>) ((JavaObjectTerm) a5).object();
while (iter.hasNext()) {
- PatchListEntry patch = iter.next();
- String newName = patch.getNewName();
- String oldName = patch.getOldName();
- Patch.ChangeType changeType = patch.getChangeType();
+ DiffEntry diffEntry = iter.next();
+ String newName = diffEntry.getNewPath();
+ String oldName = diffEntry.getOldPath();
+ DiffEntry.ChangeType changeType = diffEntry.getChangeType();
- if (Patch.isMagic(newName)) {
- continue;
- }
-
- if (regex.matcher(newName).find() || (oldName != null && regex.matcher(oldName).find())) {
+ if ((!isNull(newName) && regex.matcher(newName).find())
+ || (!isNull(oldName) && regex.matcher(oldName).find())) {
SymbolTerm changeSym = getTypeSymbol(changeType);
- SymbolTerm newSym = SymbolTerm.create(newName);
- SymbolTerm oldSym = Prolog.Nil;
- if (oldName != null) {
- oldSym = SymbolTerm.create(oldName);
+ SymbolTerm newSym = isNull(newName) ? Prolog.Nil : SymbolTerm.create(newName);
+ SymbolTerm oldSym = isNull(oldName) ? Prolog.Nil : SymbolTerm.create(oldName);
+ // For compatibility with legacy semantics:
+ if (changeSym.equals(delete)) {
+ newSym = oldSym;
+ oldSym = Prolog.Nil;
}
if (!a2.unify(changeSym, engine.trail)) {
@@ -130,6 +147,10 @@
}
}
+ private static boolean isNull(String path) {
+ return path.equals("/dev/null");
+ }
+
private static final class PRED_commit_delta_next extends Operation {
@Override
public Operation exec(Prolog engine) {
@@ -152,20 +173,18 @@
}
}
- private static SymbolTerm getTypeSymbol(Patch.ChangeType type) {
+ private static SymbolTerm getTypeSymbol(DiffEntry.ChangeType type) {
switch (type) {
- case ADDED:
+ case ADD:
return add;
- case MODIFIED:
+ case MODIFY:
return modify;
- case DELETED:
+ case DELETE:
return delete;
- case RENAMED:
+ case RENAME:
return rename;
- case COPIED:
+ case COPY:
return copy;
- case REWRITE:
- break;
}
throw new IllegalArgumentException("ChangeType not recognized");
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java
new file mode 100644
index 0000000..bc9f50a5
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java
@@ -0,0 +1,103 @@
+// Copyright (C) 2021 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.acceptance.api.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.entities.LabelFunction;
+import com.google.gerrit.entities.LabelType;
+import com.google.gerrit.entities.LabelValue;
+import com.google.gerrit.entities.SubmitRecord;
+import com.google.gerrit.server.project.SubmitRuleEvaluator;
+import com.google.gerrit.server.project.SubmitRuleOptions;
+import com.google.inject.Inject;
+import java.util.List;
+import org.junit.Test;
+
+public class SubmitRuleIT extends AbstractDaemonTest {
+ @Inject private SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory;
+
+ @Test
+ public void submitRecordsForClosedChanges_parsedBackByDefault() throws Exception {
+ SubmitRuleEvaluator submitRuleEvaluator =
+ submitRuleEvaluatorFactory.create(SubmitRuleOptions.defaults());
+ PushOneCommit.Result r = createChange();
+ approve(r.getChangeId());
+ List<SubmitRecord> recordsBeforeSubmission = submitRuleEvaluator.evaluate(r.getChange());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ // Add a new label that blocks submission if not granted. In case we reevaluate the rules,
+ // this would show up as blocking submission.
+ setupCustomBlockingLabel();
+ List<SubmitRecord> recordsAfterSubmission = submitRuleEvaluator.evaluate(r.getChange());
+ recordsBeforeSubmission.forEach(
+ sr -> sr.status = SubmitRecord.Status.CLOSED); // Set status to closed
+ assertThat(recordsBeforeSubmission).isEqualTo(recordsAfterSubmission);
+ }
+
+ @Test
+ public void submitRecordsForClosedChanges_recomputedIfRequested() throws Exception {
+ SubmitRuleEvaluator submitRuleEvaluator =
+ submitRuleEvaluatorFactory.create(
+ SubmitRuleOptions.builder().recomputeOnClosedChanges(true).build());
+ PushOneCommit.Result r = createChange();
+ approve(r.getChangeId());
+ List<SubmitRecord> recordsBeforeSubmission = submitRuleEvaluator.evaluate(r.getChange());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ // Add a new label that blocks submission if not granted. In case we reevaluate the rules,
+ // this would show up as blocking submission.
+ setupCustomBlockingLabel();
+ List<SubmitRecord> recordsAfterSubmission = submitRuleEvaluator.evaluate(r.getChange());
+ assertThat(recordsBeforeSubmission).isNotEqualTo(recordsAfterSubmission);
+ assertThat(recordsAfterSubmission).hasSize(1);
+ List<SubmitRecord.Label> recordLabels = recordsAfterSubmission.get(0).labels;
+
+ assertThat(recordLabels).hasSize(2);
+ assertCodeReviewApproved(recordLabels);
+ assertMyLabelNeed(recordLabels);
+ }
+
+ private void assertCodeReviewApproved(List<SubmitRecord.Label> recordLabels) {
+ SubmitRecord.Label haveCodeReview = new SubmitRecord.Label();
+ haveCodeReview.label = "Code-Review";
+ haveCodeReview.status = SubmitRecord.Label.Status.OK;
+ haveCodeReview.appliedBy = admin.id();
+ assertThat(recordLabels).contains(haveCodeReview);
+ }
+
+ private void assertMyLabelNeed(List<SubmitRecord.Label> recordLabels) {
+ SubmitRecord.Label needCustomLabel = new SubmitRecord.Label();
+ needCustomLabel.label = "My-Label";
+ needCustomLabel.status = SubmitRecord.Label.Status.NEED;
+ assertThat(recordLabels).contains(needCustomLabel);
+ }
+
+ private void setupCustomBlockingLabel() throws Exception {
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ u.getConfig()
+ .upsertLabelType(
+ LabelType.builder(
+ "My-Label",
+ ImmutableList.of(
+ LabelValue.create((short) 0, "Not approved"),
+ LabelValue.create((short) 1, "Approved")))
+ .setFunction(LabelFunction.MAX_WITH_BLOCK)
+ .build());
+ u.save();
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
index eba2634..5ca7310 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
@@ -21,8 +21,10 @@
import static com.google.gerrit.server.project.testing.TestLabels.value;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.entities.Change;
@@ -30,6 +32,7 @@
import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
import com.google.gerrit.server.patch.filediff.Edit;
import com.google.gerrit.server.project.testing.TestLabels;
import com.google.inject.Inject;
@@ -144,6 +147,56 @@
}
@Test
+ @GerritConfig(name = "change.cumulativeCommentSizeLimit", value = "1k")
+ public void autoGeneratedPostSubmitDiffIsNotPartOfTheCommentSizeLimit() throws Exception {
+ Change.Id changeId =
+ changeOperations.newChange().project(project).file("file").content("content").create();
+ gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());
+ String content = new String(new char[800]).replace("\0", "a");
+ changeOperations.change(changeId).newPatchset().file("file").content(content).create();
+
+ // Post a submit diff that is almost the cumulativeCommentSizeLimit
+ gApi.changes().id(changeId.get()).current().submit();
+ assertThat(Iterables.getLast(gApi.changes().id(changeId.get()).messages()).message)
+ .doesNotContain("many unreviewed changes");
+
+ // unrelated comment and change message posting works fine, since the post submit diff is not
+ // counted towards the cumulativeCommentSizeLimit for unrelated follow-up comments.
+ // 800 + 400 + 400 > 1k, but 400 + 400 < 1k, hence these comments are accepted (the original
+ // 800 is not counted).
+ String message = new String(new char[400]).replace("\0", "a");
+ ReviewInput reviewInput = new ReviewInput().message(message);
+ CommentInput commentInput = new CommentInput();
+ commentInput.line = 1;
+ commentInput.message = message;
+ commentInput.path = "file";
+ reviewInput.comments = ImmutableMap.of("file", ImmutableList.of(commentInput));
+
+ gApi.changes().id(changeId.get()).current().review(reviewInput);
+ }
+
+ @Test
+ @GerritConfig(name = "change.cumulativeCommentSizeLimit", value = "1k")
+ public void postSubmitDiffCannotBeTooBig() throws Exception {
+ Change.Id changeId =
+ changeOperations.newChange().project(project).file("file").content("content").create();
+ gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());
+
+ String content = new String(new char[1100]).replace("\0", "a");
+
+ changeOperations.change(changeId).newPatchset().file("file").content(content).create();
+
+ // Post submit diff is over the cumulativeCommentSizeLimit, so we shorten the message.
+ gApi.changes().id(changeId.get()).current().submit();
+ assertThat(Iterables.getLast(gApi.changes().id(changeId.get()).messages()).message)
+ .isEqualTo(
+ "Change has been successfully merged\n\n1 is the latest approved patch-set.\nThe "
+ + "change was submitted "
+ + "with many unreviewed changes (the diff is too large to show). Please review the "
+ + "diff.");
+ }
+
+ @Test
public void diffChangeMessageOnSubmitWithStickyVote_addedFile() throws Exception {
Change.Id changeId = changeOperations.newChange().project(project).create();
gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index 68bb66c..ec59674 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -77,6 +77,7 @@
private boolean intraline;
private boolean useNewDiffCache;
+ private boolean useNewDiffCacheGetDiff;
private ObjectId commit1;
private String changeId;
@@ -91,6 +92,8 @@
intraline = baseConfig.getBoolean(TEST_PARAMETER_MARKER, "intraline", false);
useNewDiffCache = baseConfig.getBoolean("cache", "diff_cache", "useNewDiffCache", false);
+ useNewDiffCacheGetDiff =
+ baseConfig.getBoolean("cache", "diff_cache", "useNewDiffCache_getDiff", false);
ObjectId headCommit = testRepo.getRepository().resolve("HEAD");
commit1 =
@@ -2749,6 +2752,7 @@
public void symlinkConvertedToRegularFileIsIdentifiedAsAdded() throws Exception {
// TODO(ghareeb): fix this test for the new diff cache implementation
assume().that(useNewDiffCache).isFalse();
+ assume().that(useNewDiffCacheGetDiff).isFalse();
String target = "file.txt";
String symlink = "link.lnk";
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionNewDiffCacheForSingleFileIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionNewDiffCacheForSingleFileIT.java
new file mode 100644
index 0000000..e55f432
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionNewDiffCacheForSingleFileIT.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 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.acceptance.api.revision;
+
+import com.google.gerrit.testing.ConfigSuite;
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Runs the {@link RevisionDiffIT} tests with the new diff cache, enabled for the single file "Get
+ * Diff" endpoint. This is temporary until the new diff cache is fully deployed. The new diff cache
+ * will become the default in the future.
+ */
+public class RevisionNewDiffCacheForSingleFileIT extends RevisionDiffIT {
+ @ConfigSuite.Default
+ public static Config newDiffCacheConfig() {
+ Config config = new Config();
+ config.setBoolean("cache", "diff_cache", "runNewDiffCacheAsync_getDiff", true);
+ return config;
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
index 5fd55ec..ffdbd8e 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
@@ -22,16 +22,23 @@
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import java.util.Arrays;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
public class ProjectLevelConfigIT extends AbstractDaemonTest {
+ private static final String PLUGIN_NAME = "test-plugin";
+
@Inject private ProjectOperations projectOperations;
+ @Inject private PluginConfigFactory pluginConfigFactory;
@Before
public void setUp() throws Exception {
@@ -185,4 +192,27 @@
ProjectState state = projectCache.get(project).get();
assertThat(state.getConfig(configName).get().toText()).isEmpty();
}
+
+ @Test
+ public void emptySubSectionsCanBeRead() throws Exception {
+ updatePluginConfig(project, "[section \"subsection\"]");
+ Config cfg = pluginConfigFactory.getProjectPluginConfigWithInheritance(project, PLUGIN_NAME);
+ assertThat(cfg.getSubsections("section")).containsExactly("subsection");
+ }
+
+ private void updatePluginConfig(Project.NameKey project, String pluginConfig) throws Exception {
+ try (TestRepository<Repository> testRepo =
+ new TestRepository<>(repoManager.openRepository(project))) {
+ Ref ref = testRepo.getRepository().exactRef(RefNames.REFS_CONFIG);
+ RevCommit head = testRepo.getRevWalk().parseCommit(ref.getObjectId());
+ testRepo.update(
+ RefNames.REFS_CONFIG,
+ testRepo
+ .commit()
+ .parent(head)
+ .message("Configure plugin")
+ .add(PLUGIN_NAME + ".config", pluginConfig));
+ }
+ projectCache.evict(project);
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
index 548e3fe..8a0ddd3 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
@@ -31,11 +31,13 @@
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.ContextLineInfo;
+import com.google.gerrit.server.change.FileContentUtil;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.Before;
import org.junit.Test;
@@ -53,15 +55,37 @@
private static final String FILE_CONTENT =
String.join("\n", "Line 1 of file", "", "Line 3 of file", "", "", "Line 6 of file");
+ private static final ObjectId dummyCommit =
+ ObjectId.fromString("93e2901bc0b4719ef6081ee6353b49c9cdd97614");
@Inject private RequestScopeOperations requestScopeOperations;
@Before
- public void setUp() {
+ public void setup() throws Exception {
requestScopeOperations.setApiUser(user.id());
}
@Test
+ public void commentContextForGitSubmoduleFiles() throws Exception {
+ String submodulePath = "submodule_path";
+
+ PushOneCommit push =
+ pushFactory.create(admin.newIdent(), testRepo).addGitSubmodule(submodulePath, dummyCommit);
+ PushOneCommit.Result pushResult = push.to("refs/for/master");
+ String changeId = pushResult.getChangeId();
+ CommentInput comment =
+ CommentsUtil.newComment(submodulePath, Side.REVISION, 1, "comment", false);
+ CommentsUtil.addComments(gApi, changeId, pushResult.getCommit().name(), comment);
+
+ List<CommentInfo> comments =
+ gApi.changes().id(changeId).commentsRequest().withContext(true).getAsList();
+ assertThat(comments).hasSize(1);
+ assertThat(comments.get(0).path).isEqualTo(submodulePath);
+ assertThat(comments.get(0).contextLines)
+ .isEqualTo(createContextLines("1", "Subproject commit " + dummyCommit.getName()));
+ }
+
+ @Test
public void commentContextForCommitMessageForLineComment() throws Exception {
PushOneCommit.Result result =
createChange(testRepo, "master", SUBJECT, FILE_NAME, FILE_CONTENT, "topic");
@@ -319,6 +343,82 @@
IntStream.range(200, 301).boxed().collect(ImmutableList.toImmutableList()));
}
+ @Test
+ public void commentContextReturnsCorrectContentTypeForCommitMessage() throws Exception {
+ PushOneCommit.Result result =
+ createChange(testRepo, "master", SUBJECT, FILE_NAME, FILE_CONTENT, "topic");
+ String changeId = result.getChangeId();
+ String ps1 = result.getCommit().name();
+
+ CommentInput comment = CommentsUtil.newComment(COMMIT_MSG, Side.REVISION, 7, "comment", false);
+ CommentsUtil.addComments(gApi, changeId, ps1, comment);
+
+ List<CommentInfo> comments =
+ gApi.changes().id(changeId).commentsRequest().withContext(true).getAsList();
+
+ assertThat(comments).hasSize(1);
+ assertThat(comments.get(0).path).isEqualTo(COMMIT_MSG);
+ assertThat(comments.get(0).sourceContentType)
+ .isEqualTo(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE);
+ }
+
+ @Test
+ public void commentContextReturnsCorrectContentType_Java() throws Exception {
+ String javaContent =
+ "public class Main {\n"
+ + " public static void main(String[]args){\n"
+ + " if(args==null){\n"
+ + " System.err.println(\"Something\");\n"
+ + " }\n"
+ + " }\n"
+ + " }";
+ String fileName = "src.java";
+ String changeId = createChangeWithContent(fileName, javaContent, /* line= */ 4);
+
+ List<CommentInfo> comments =
+ gApi.changes().id(changeId).commentsRequest().withContext(true).getAsList();
+
+ assertThat(comments).hasSize(1);
+ assertThat(comments.get(0).path).isEqualTo(fileName);
+ assertThat(comments.get(0).contextLines)
+ .isEqualTo(createContextLines("4", " System.err.println(\"Something\");"));
+ assertThat(comments.get(0).sourceContentType).isEqualTo("text/x-java");
+ }
+
+ @Test
+ public void commentContextReturnsCorrectContentType_Cpp() throws Exception {
+ String cppContent =
+ "#include <iostream>\n"
+ + "\n"
+ + "int main() {\n"
+ + " std::cout << \"Hello World!\";\n"
+ + " return 0;\n"
+ + "}";
+ String fileName = "src.cpp";
+ String changeId = createChangeWithContent(fileName, cppContent, /* line= */ 4);
+
+ List<CommentInfo> comments =
+ gApi.changes().id(changeId).commentsRequest().withContext(true).getAsList();
+
+ assertThat(comments).hasSize(1);
+ assertThat(comments.get(0).path).isEqualTo(fileName);
+ assertThat(comments.get(0).contextLines)
+ .isEqualTo(createContextLines("4", " std::cout << \"Hello World!\";"));
+ assertThat(comments.get(0).sourceContentType).isEqualTo("text/x-c++src");
+ }
+
+ private String createChangeWithContent(String fileName, String fileContent, int line)
+ throws Exception {
+ PushOneCommit.Result result =
+ createChange(testRepo, "master", SUBJECT, fileName, fileContent, "topic");
+ String changeId = result.getChangeId();
+ String ps1 = result.getCommit().name();
+
+ CommentInput comment = CommentsUtil.newComment(fileName, Side.REVISION, line, "comment", false);
+ CommentsUtil.addComments(gApi, changeId, ps1, comment);
+ return changeId;
+ }
+
private String createChangeWithComment(int startLine, int endLine) throws Exception {
PushOneCommit.Result result =
createChange(testRepo, "master", SUBJECT, FILE_NAME, FILE_CONTENT, "topic");
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java b/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
index 5cbc767..0585f74 100644
--- a/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
@@ -27,7 +28,9 @@
import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Map;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
@@ -39,7 +42,7 @@
@NoHttpd
public class RulesIT extends AbstractDaemonTest {
private static final String RULE_TEMPLATE =
- "submit_rule(submit(W)) :- \n" + "%s,\n" + "W = label('OK', ok(user(1000000))).";
+ "submit_rule(submit(W)) :- \n%s,\nW = label('OK', ok(user(1000000))).";
@Inject private ProjectOperations projectOperations;
@Inject private SubmitRuleEvaluator.Factory evaluatorFactory;
@@ -89,12 +92,116 @@
assertThat(statusForRuleRemoveFile()).isEqualTo(SubmitRecord.Status.OK);
}
+ @Test
+ public void testCommitDelta_pass() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('file1\\.txt')");
+ assertThat(statusForRuleAddFile("file1.txt")).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testCommitDelta_fail() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('no such file')");
+ assertThat(statusForRuleAddFile("file1.txt")).isEqualTo(SubmitRecord.Status.RULE_ERROR);
+ }
+
+ @Test
+ public void testCommitDelta_addOwners_pass() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('OWNERS', add, _, _)");
+ assertThat(statusForRuleAddFile("foo/OWNERS")).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testCommitDelta_addOwners_fail() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('OWNERS', add, _, _)");
+ assertThat(statusForRuleAddFile("foobar")).isEqualTo(SubmitRecord.Status.RULE_ERROR);
+ }
+
+ @Test
+ public void testCommitDelta_regexp() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('.*')");
+ assertThat(statusForRuleAddFile("foo/bar")).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testCommitDelta_add_provideNewName() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('.*', _, 'foo')");
+ assertThat(statusForRuleAddFile("foo")).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testCommitDelta_modify_provideNewName() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('.*', _, 'a.txt')");
+ assertThat(statusForRuleModifyFile()).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testCommitDelta_delete_provideNewName() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('.*', _, 'a.txt')");
+ assertThat(statusForRuleRemoveFile()).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testCommitDelta_rename_provideOldName() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('.*', _, 'a.txt')");
+ assertThat(statusForRuleRenamedFile()).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testCommitDelta_rename_provideNewName() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('.*', _, 'b.txt')");
+ assertThat(statusForRuleRenamedFile()).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testCommitDelta_rename_matchOldName() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('a\\.txt')");
+ assertThat(statusForRuleRenamedFile()).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testCommitDelta_rename_matchNewName() throws Exception {
+ modifySubmitRules("gerrit:commit_delta('b\\.txt')");
+ assertThat(statusForRuleRenamedFile()).isEqualTo(SubmitRecord.Status.OK);
+ }
+
private SubmitRecord.Status statusForRule() throws Exception {
String oldHead = projectOperations.project(project).getHead("master").name();
- PushOneCommit.Result result1 =
+ PushOneCommit.Result result =
pushFactory.create(user.newIdent(), testRepo).to("refs/for/master");
testRepo.reset(oldHead);
- return getStatus(result1);
+ return getStatus(result);
+ }
+
+ private SubmitRecord.Status statusForRuleAddFile(String... filenames) throws Exception {
+ Map<String, String> fileToContentMap =
+ Arrays.stream(filenames).collect(ImmutableMap.toImmutableMap(f -> f, f -> "file content"));
+ String oldHead = projectOperations.project(project).getHead("master").name();
+ PushOneCommit push =
+ pushFactory.create(admin.newIdent(), testRepo, "subject", fileToContentMap);
+ PushOneCommit.Result result = push.to("refs/for/master");
+ result.assertOkStatus();
+ testRepo.reset(oldHead);
+ return getStatus(result);
+ }
+
+ private SubmitRecord.Status statusForRuleModifyFile() throws Exception {
+ String oldHead = projectOperations.project(project).getHead("master").name();
+
+ // create a.txt
+ commitBuilder().add(PushOneCommit.FILE_NAME, "Hey, it's me!").message("subject").create();
+ pushHead(testRepo, "refs/heads/master", false);
+
+ PushOneCommit.Result result =
+ pushFactory
+ .create(
+ user.newIdent(),
+ testRepo,
+ "subject",
+ ImmutableMap.of(PushOneCommit.FILE_NAME, "I've changed!"))
+ .rmFile(PushOneCommit.FILE_NAME)
+ .to("refs/for/master");
+ testRepo.reset(oldHead);
+ return getStatus(result);
}
private SubmitRecord.Status statusForRuleRemoveFile() throws Exception {
@@ -110,15 +217,30 @@
return getStatus(result);
}
- private SubmitRecord.Status getStatus(PushOneCommit.Result result1) throws Exception {
- ChangeData cd = result1.getChange();
+ private SubmitRecord.Status statusForRuleRenamedFile() throws Exception {
+ String oldHead = projectOperations.project(project).getHead("master").name();
+
+ // create a.txt
+ commitBuilder().add(PushOneCommit.FILE_NAME, "Hey, it's me!").message("subject").create();
+ pushHead(testRepo, "refs/heads/master", false);
+
+ PushOneCommit.Result result =
+ pushFactory
+ .create(user.newIdent(), testRepo, "subject", ImmutableMap.of("b.txt", "Hey, it's me!"))
+ .rmFile(PushOneCommit.FILE_NAME)
+ .to("refs/for/master");
+ testRepo.reset(oldHead);
+ return getStatus(result);
+ }
+
+ private SubmitRecord.Status getStatus(PushOneCommit.Result result) throws Exception {
+ ChangeData cd = result.getChange();
Collection<SubmitRecord> records;
- try (AutoCloseable changeIndex = disableChangeIndex()) {
- try (AutoCloseable accountIndex = disableAccountIndex()) {
- SubmitRuleEvaluator ruleEvaluator = evaluatorFactory.create(SubmitRuleOptions.defaults());
- records = ruleEvaluator.evaluate(cd);
- }
+ try (AutoCloseable ignored1 = disableChangeIndex();
+ AutoCloseable ignored2 = disableAccountIndex()) {
+ SubmitRuleEvaluator ruleEvaluator = evaluatorFactory.create(SubmitRuleOptions.defaults());
+ records = ruleEvaluator.evaluate(cd);
}
assertThat(records).hasSize(1);
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
index 00d01d6..7543ba8 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
@@ -595,6 +595,15 @@
}
@Test
+ public void removeAllAccessSections() {
+ projectOperations.allProjectsForUpdate().removeAllAccessSections().update();
+
+ assertThat(projectOperations.project(allProjects).getConfig())
+ .sectionValues("access")
+ .isEmpty();
+ }
+
+ @Test
public void updatingCapabilitiesNotAllowedForNonAllProjects() throws Exception {
Project.NameKey key = projectOperations.newProject().create();
assertThrows(
diff --git a/javatests/com/google/gerrit/entities/SubmitRecordTest.java b/javatests/com/google/gerrit/entities/SubmitRecordTest.java
index 0e832f4..e2a5787 100644
--- a/javatests/com/google/gerrit/entities/SubmitRecordTest.java
+++ b/javatests/com/google/gerrit/entities/SubmitRecordTest.java
@@ -16,6 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Test;
@@ -67,4 +68,19 @@
assertThat(SubmitRecord.allRecordsOK(submitRecords)).isFalse();
}
+
+ @Test
+ public void deepCopy() {
+ SubmitRecord record = new SubmitRecord();
+ record.status = SubmitRecord.Status.CLOSED;
+ record.errorMessage = "ouch";
+ record.requirements =
+ ImmutableList.of(SubmitRequirement.builder().setFallbackText("foo").setType("baz").build());
+ SubmitRecord.Label label = new SubmitRecord.Label();
+ label.label = "Code-Review";
+ record.labels = ImmutableList.of(label);
+
+ assertThat(record).isNotSameInstanceAs(record.deepCopy());
+ assertThat(record).isEqualTo(record.deepCopy());
+ }
}
diff --git a/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
new file mode 100644
index 0000000..a41d63b
--- /dev/null
+++ b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
@@ -0,0 +1,355 @@
+// Copyright (C) 2021 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.extensions.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ChangeInfoDifferTest {
+
+ private static final String REVISION = "abc123";
+
+ @Test
+ public void getDiff_givenEmptyChangeInfos_returnsEmptyDifference() {
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(new ChangeInfo(), new ChangeInfo());
+
+ // Spot check a few fields, including collections and maps.
+ assertThat(diff.added().branch).isNull();
+ assertThat(diff.added().project).isNull();
+ assertThat(diff.added().currentRevision).isNull();
+ assertThat(diff.added().actions).isNull();
+ assertThat(diff.added().messages).isNull();
+ assertThat(diff.added().reviewers).isNull();
+ assertThat(diff.added().hashtags).isNull();
+ assertThat(diff.removed().branch).isNull();
+ assertThat(diff.removed().project).isNull();
+ assertThat(diff.removed().currentRevision).isNull();
+ assertThat(diff.removed().actions).isNull();
+ assertThat(diff.removed().messages).isNull();
+ assertThat(diff.removed().reviewers).isNull();
+ assertThat(diff.removed().hashtags).isNull();
+ }
+
+ @Test
+ public void getDiff_givenUnchangedTopic_returnsNullTopics() {
+ ChangeInfo oldChangeInfo = createChangeInfoWithTopic("topic");
+ ChangeInfo newChangeInfo = createChangeInfoWithTopic(oldChangeInfo.topic);
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().topic).isNull();
+ assertThat(diff.removed().topic).isNull();
+ }
+
+ @Test
+ public void getDiff_givenChangedTopic_returnsTopics() {
+ ChangeInfo oldChangeInfo = createChangeInfoWithTopic("old-topic");
+ ChangeInfo newChangeInfo = createChangeInfoWithTopic("new-topic");
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().topic).isEqualTo(newChangeInfo.topic);
+ assertThat(diff.removed().topic).isEqualTo(oldChangeInfo.topic);
+ }
+
+ @Test
+ public void getDiff_givenEqualAssignees_returnsNullAssignee() {
+ ChangeInfo oldChangeInfo =
+ createChangeInfoWithAccount(new AccountInfo("name", "mail@mail.com"));
+ ChangeInfo newChangeInfo =
+ createChangeInfoWithAccount(
+ new AccountInfo(oldChangeInfo.assignee.name, oldChangeInfo.assignee.email));
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().assignee).isNull();
+ assertThat(diff.removed().assignee).isNull();
+ }
+
+ @Test
+ public void getDiff_givenNewAssignee_returnsAssignee() {
+ ChangeInfo oldChangeInfo = new ChangeInfo();
+ ChangeInfo newChangeInfo =
+ createChangeInfoWithAccount(new AccountInfo("name", "mail@mail.com"));
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().assignee).isEqualTo(newChangeInfo.assignee);
+ assertThat(diff.removed().assignee).isNull();
+ }
+
+ @Test
+ public void getDiff_withRemovedAssignee_returnsAssignee() {
+ ChangeInfo oldChangeInfo =
+ createChangeInfoWithAccount(new AccountInfo("name", "mail@mail.com"));
+ ChangeInfo newChangeInfo = new ChangeInfo();
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().assignee).isNull();
+ assertThat(diff.removed().assignee).isEqualTo(oldChangeInfo.assignee);
+ }
+
+ @Test
+ public void getDiff_givenAssigneeWithNewName_returnsNameButNotEmail() {
+ ChangeInfo oldChangeInfo =
+ createChangeInfoWithAccount(new AccountInfo("old name", "mail@mail.com"));
+ ChangeInfo newChangeInfo =
+ createChangeInfoWithAccount(new AccountInfo("new name", oldChangeInfo.assignee.email));
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().assignee).isNotNull();
+ assertThat(diff.added().assignee.name).isEqualTo(newChangeInfo.assignee.name);
+ assertThat(diff.added().assignee.email).isNull();
+ assertThat(diff.removed().assignee).isNotNull();
+ assertThat(diff.removed().assignee.name).isEqualTo(oldChangeInfo.assignee.name);
+ assertThat(diff.removed().assignee.email).isNull();
+ }
+
+ @Test
+ public void getDiff_whenHashtagsChanged_returnsHashtags() {
+ String removedHashtag = "removed";
+ String addedHashtag = "added";
+ ChangeInfo oldChangeInfo = createChangeInfoWithHashtags(removedHashtag, "existing");
+ ChangeInfo newChangeInfo = createChangeInfoWithHashtags("existing", addedHashtag);
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().hashtags).isNotNull();
+ assertThat(diff.added().hashtags).containsExactly(addedHashtag);
+ assertThat(diff.removed().hashtags).isNotNull();
+ assertThat(diff.removed().hashtags).containsExactly(removedHashtag);
+ }
+
+ @Test
+ public void getDiff_whenDuplicateHashtagAdded_returnsHashtag() {
+ String hashtag = "hashtag";
+ ChangeInfo oldChangeInfo = createChangeInfoWithHashtags(hashtag, hashtag);
+ ChangeInfo newChangeInfo = createChangeInfoWithHashtags(hashtag, hashtag, hashtag);
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().hashtags).isNotNull();
+ assertThat(diff.added().hashtags).containsExactly(hashtag);
+ assertThat(diff.removed().hashtags).isNull();
+ }
+
+ @Test
+ public void getDiff_whenChangeMessageUnchanged_returnsNullMessage() {
+ String message = "message";
+ ChangeInfo oldChangeInfo = new ChangeInfo(new ChangeMessageInfo(message));
+ ChangeInfo newChangeInfo = new ChangeInfo(new ChangeMessageInfo(message));
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().messages).isNull();
+ assertThat(diff.removed().messages).isNull();
+ }
+
+ @Test
+ public void getDiff_whenChangeMessageAdded_returnsAdded() {
+ ChangeMessageInfo addedMessage = new ChangeMessageInfo("added");
+ ChangeMessageInfo existingMessage = new ChangeMessageInfo("existing");
+ ChangeInfo oldChangeInfo = new ChangeInfo(existingMessage);
+ ChangeInfo newChangeInfo = new ChangeInfo(existingMessage, addedMessage);
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().messages).isNotNull();
+ assertThat(diff.added().messages).containsExactly(addedMessage);
+ assertThat(diff.removed().messages).isNull();
+ }
+
+ @Test
+ public void getDiff_whenChangeMessageRemoved_returnsRemoved() {
+ ChangeMessageInfo removedMessage = new ChangeMessageInfo("removed");
+ ChangeMessageInfo existingMessage = new ChangeMessageInfo("existing");
+ ChangeInfo oldChangeInfo = new ChangeInfo(existingMessage, removedMessage);
+ ChangeInfo newChangeInfo = new ChangeInfo(existingMessage);
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().messages).isNull();
+ assertThat(diff.removed().messages).isNotNull();
+ assertThat(diff.removed().messages).containsExactly(removedMessage);
+ }
+
+ @Test
+ public void getDiff_whenDuplicateMessagesAdded_returnsDuplicates() {
+ ChangeMessageInfo message = new ChangeMessageInfo("message");
+ ChangeInfo oldChangeInfo = new ChangeInfo(message, message);
+ ChangeInfo newChangeInfo = new ChangeInfo(message, message, message, message);
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().messages).isNotNull();
+ assertThat(diff.added().messages).containsExactly(message, message);
+ assertThat(diff.removed().messages).isNull();
+ }
+
+ @Test
+ public void getDiff_whenNoNewRevisions_returnsNullRevisions() {
+ ChangeInfo oldChangeInfo = new ChangeInfo(ImmutableMap.of(REVISION, new RevisionInfo("ref")));
+ ChangeInfo newChangeInfo = new ChangeInfo(ImmutableMap.of(REVISION, new RevisionInfo("ref")));
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().revisions).isNull();
+ assertThat(diff.removed().revisions).isNull();
+ }
+
+ @Test
+ public void getDiff_whenOneAddedRevision_returnsRevision() {
+ RevisionInfo addedRevision = new RevisionInfo("ref");
+ ChangeInfo oldChangeInfo = new ChangeInfo(ImmutableMap.of());
+ ChangeInfo newChangeInfo = new ChangeInfo(ImmutableMap.of(REVISION, addedRevision));
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().revisions).isNotNull();
+ assertThat(diff.added().revisions).hasSize(1);
+ assertThat(diff.added().revisions).containsKey(REVISION);
+ assertThat(diff.added().revisions.get(REVISION).ref).isEqualTo(addedRevision.ref);
+ assertThat(diff.removed().revisions).isNull();
+ }
+
+ @Test
+ public void getDiff_whenOneModifiedRevision_returnsModificationsToRevision() {
+ RevisionInfo oldRevision = new RevisionInfo("ref", 1);
+ RevisionInfo newRevision = new RevisionInfo(oldRevision.ref, 2);
+ ChangeInfo oldChangeInfo = new ChangeInfo(ImmutableMap.of(REVISION, oldRevision));
+ ChangeInfo newChangeInfo = new ChangeInfo(ImmutableMap.of(REVISION, newRevision));
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().revisions).isNotNull();
+ assertThat(diff.added().revisions).hasSize(1);
+ assertThat(diff.added().revisions).containsKey(REVISION);
+ assertThat(diff.added().revisions.get(REVISION).ref).isNull();
+ assertThat(diff.added().revisions.get(REVISION)._number).isEqualTo(newRevision._number);
+ assertThat(diff.removed().revisions).isNotNull();
+ assertThat(diff.removed().revisions).hasSize(1);
+ assertThat(diff.removed().revisions).containsKey(REVISION);
+ assertThat(diff.removed().revisions.get(REVISION).ref).isNull();
+ assertThat(diff.removed().revisions.get(REVISION)._number).isEqualTo(oldRevision._number);
+ }
+
+ @Test
+ public void getDiff_whenOneModifiedRevisionUploader_returnsModificationsToRevisionUploader() {
+ RevisionInfo oldRevision = new RevisionInfo(new AccountInfo("name", "email@mail.com"));
+ RevisionInfo newRevision =
+ new RevisionInfo(
+ new AccountInfo(oldRevision.uploader.name, oldRevision.uploader.email + "2"));
+ ChangeInfo oldChangeInfo = new ChangeInfo(ImmutableMap.of(REVISION, oldRevision));
+ ChangeInfo newChangeInfo = new ChangeInfo(ImmutableMap.of(REVISION, newRevision));
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().revisions).isNotNull();
+ assertThat(diff.added().revisions).hasSize(1);
+ assertThat(diff.added().revisions).containsKey(REVISION);
+ assertThat(diff.added().revisions.get(REVISION).uploader).isNotNull();
+ assertThat(diff.added().revisions.get(REVISION).uploader.name).isNull();
+ assertThat(diff.added().revisions.get(REVISION).uploader.email)
+ .isEqualTo(newRevision.uploader.email);
+ assertThat(diff.removed().revisions).isNotNull();
+ assertThat(diff.removed().revisions).hasSize(1);
+ assertThat(diff.removed().revisions).containsKey(REVISION);
+ assertThat(diff.removed().revisions.get(REVISION).uploader).isNotNull();
+ assertThat(diff.removed().revisions.get(REVISION).uploader.name).isNull();
+ assertThat(diff.removed().revisions.get(REVISION).uploader.email)
+ .isEqualTo(oldRevision.uploader.email);
+ }
+
+ @Test
+ public void getDiff_whenOneUnchangedRevisionUploader_returnsNullRevision() {
+ RevisionInfo oldRevision = new RevisionInfo(new AccountInfo("name", "email@mail.com"));
+ RevisionInfo newRevision = new RevisionInfo(oldRevision.uploader);
+ ChangeInfo oldChangeInfo = new ChangeInfo(ImmutableMap.of(REVISION, oldRevision));
+ ChangeInfo newChangeInfo = new ChangeInfo(ImmutableMap.of(REVISION, newRevision));
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.added().revisions).isNull();
+ assertThat(diff.removed().revisions).isNull();
+ }
+
+ @Test
+ public void getDiff_assertCanConstructAllChangeInfoReferences() throws Exception {
+ buildObjectWithFullFields(ChangeInfo.class);
+ }
+
+ private static Object buildObjectWithFullFields(Class<?> c) throws Exception {
+ if (c == null) {
+ return null;
+ }
+ Object toPopulate = ChangeInfoDiffer.construct(c);
+ for (Field field : toPopulate.getClass().getDeclaredFields()) {
+ Class<?> parameterizedType = getParameterizedType(field);
+ if (!ChangeInfoDiffer.isSimple(field.getType())
+ && !field.getType().isArray()
+ && !Map.class.isAssignableFrom(field.getType())
+ && !Collection.class.isAssignableFrom(field.getType())) {
+ field.set(toPopulate, buildObjectWithFullFields(field.getType()));
+ } else if (Collection.class.isAssignableFrom(field.getType())
+ && parameterizedType != null
+ && !ChangeInfoDiffer.isSimple(parameterizedType)) {
+ field.set(toPopulate, ImmutableList.of(buildObjectWithFullFields(parameterizedType)));
+ }
+ }
+ return toPopulate;
+ }
+
+ private static Class<?> getParameterizedType(Field field) {
+ if (!Collection.class.isAssignableFrom(field.getType())) {
+ return null;
+ }
+ Type genericType = field.getGenericType();
+ if (genericType instanceof ParameterizedType) {
+ return (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
+ }
+ return null;
+ }
+
+ private static ChangeInfo createChangeInfoWithTopic(String topic) {
+ ChangeInfo changeInfo = new ChangeInfo();
+ changeInfo.topic = topic;
+ return changeInfo;
+ }
+
+ private static ChangeInfo createChangeInfoWithAccount(AccountInfo accountInfo) {
+ ChangeInfo changeInfo = new ChangeInfo();
+ changeInfo.assignee = accountInfo;
+ return changeInfo;
+ }
+
+ private static ChangeInfo createChangeInfoWithHashtags(String... hashtags) {
+ ChangeInfo changeInfo = new ChangeInfo();
+ changeInfo.hashtags = ImmutableList.copyOf(hashtags);
+ return changeInfo;
+ }
+}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/CommentContextSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/CommentContextSerializerTest.java
index 643c7b7..8fe7662 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/CommentContextSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/CommentContextSerializerTest.java
@@ -14,7 +14,7 @@
@Test
public void roundTripValue() {
CommentContext commentContext =
- CommentContext.create(ImmutableMap.of(1, "line_1", 2, "line_2"));
+ CommentContext.create(ImmutableMap.of(1, "line_1", 2, "line_2"), "text/x-java");
byte[] serialized = INSTANCE.serialize(commentContext);
CommentContext deserialized = INSTANCE.deserialize(serialized);
diff --git a/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java
index 44ea55a..17fd959 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java
@@ -19,10 +19,12 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.Patch.ChangeType;
import com.google.gerrit.entities.Patch.PatchType;
+import com.google.gerrit.server.patch.ComparisonType;
import com.google.gerrit.server.patch.filediff.Edit;
import com.google.gerrit.server.patch.filediff.FileDiffOutput;
import com.google.gerrit.server.patch.filediff.TaggedEdit;
import java.util.Optional;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
public class FileDiffOutputSerializerTest {
@@ -35,6 +37,9 @@
FileDiffOutput fileDiff =
FileDiffOutput.builder()
+ .oldCommitId(ObjectId.fromString("dd4d2a1498870ca5fe415b33f65d052d69d9eaf5"))
+ .newCommitId(ObjectId.fromString("0cfaab3f2ba76f71798da0a2651f41be8d45f842"))
+ .comparisonType(ComparisonType.againstOtherPatchSet())
.oldPath(Optional.of("old_file_path.txt"))
.newPath(Optional.empty())
.changeType(ChangeType.DELETED)
diff --git a/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java b/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java
new file mode 100644
index 0000000..5bf5154
--- /dev/null
+++ b/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java
@@ -0,0 +1,137 @@
+// Copyright (C) 2021 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.server.patch;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gerrit.testing.InMemoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import java.io.IOException;
+import java.util.Map;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TreeFormatter;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Test class for diff related logic of {@link DiffOperations}. */
+public class DiffOperationsTest {
+ @Inject private GitRepositoryManager repoManager;
+ @Inject private DiffOperations diffOperations;
+
+ private static final Project.NameKey testProjectName = Project.nameKey("test-project");
+ private Repository repo;
+
+ private final String fileName1 = "file_1.txt";
+ private final String fileContent1 = "File content 1";
+ private final String fileName2 = "file_2.txt";
+ private final String fileContent2 = "File content 2";
+
+ @Before
+ public void setUpInjector() throws Exception {
+ Injector injector = Guice.createInjector(new InMemoryModule());
+ injector.injectMembers(this);
+ repo = repoManager.createRepository(testProjectName);
+ }
+
+ @Test
+ public void diffModifiedFileAgainstParent() throws Exception {
+ ImmutableMap<String, String> oldFiles =
+ ImmutableMap.of(fileName1, fileContent1, fileName2, fileContent2);
+ ObjectId oldCommitId = createCommit(repo, null, oldFiles);
+
+ ImmutableMap<String, String> newFiles =
+ ImmutableMap.of(fileName1, fileContent1, fileName2, fileContent2 + "\nnew line here");
+ ObjectId newCommitId = createCommit(repo, oldCommitId, newFiles);
+
+ FileDiffOutput diffOutput =
+ diffOperations.getModifiedFileAgainstParent(
+ testProjectName, newCommitId, /* parentNum=*/ null, fileName2, /* whitespace=*/ null);
+
+ assertThat(diffOutput.oldCommitId()).isEqualTo(oldCommitId);
+ assertThat(diffOutput.newCommitId()).isEqualTo(newCommitId);
+ assertThat(diffOutput.comparisonType().isAgainstParent()).isTrue();
+ assertThat(diffOutput.edits()).hasSize(1);
+ }
+
+ private ObjectId createCommit(
+ Repository repo, ObjectId parentCommit, ImmutableMap<String, String> fileNameToContent)
+ throws IOException {
+ ObjectId treeId = createTree(repo, fileNameToContent);
+ return createCommitInRepo(repo, treeId, parentCommit);
+ }
+
+ private static ObjectId createCommitInRepo(
+ Repository repo, ObjectId treeId, ObjectId parentCommit) throws IOException {
+ try (ObjectInserter oi = repo.newObjectInserter()) {
+ PersonIdent committer =
+ new PersonIdent(new PersonIdent("Foo Bar", "foo.bar@baz.com"), TimeUtil.nowTs());
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(treeId);
+ cb.setCommitter(committer);
+ cb.setAuthor(committer);
+ cb.setMessage("Test commit");
+ if (parentCommit != null) {
+ cb.setParentIds(parentCommit);
+ }
+ ObjectId commitId = oi.insert(cb);
+ oi.flush();
+ oi.close();
+ return commitId;
+ }
+ }
+
+ private static ObjectId createTree(
+ Repository repo, ImmutableMap<String, String> fileNameToContent) throws IOException {
+ try (ObjectInserter oi = repo.newObjectInserter();
+ ObjectReader reader = repo.newObjectReader();
+ RevWalk rw = new RevWalk(reader); ) {
+ TreeFormatter formatter = new TreeFormatter();
+ for (Map.Entry<String, String> entry : fileNameToContent.entrySet()) {
+ String fileName = entry.getKey();
+ String fileContent = entry.getValue();
+ ObjectId fileObjId = createBlob(repo, fileContent);
+ formatter.append(fileName, rw.lookupBlob(fileObjId));
+ }
+ ObjectId treeId = oi.insert(formatter);
+ oi.flush();
+ oi.close();
+ return treeId;
+ }
+ }
+
+ private static ObjectId createBlob(Repository repo, String content) throws IOException {
+ try (ObjectInserter oi = repo.newObjectInserter()) {
+ ObjectId blobId = oi.insert(Constants.OBJ_BLOB, content.getBytes(UTF_8));
+ oi.flush();
+ oi.close();
+ return blobId;
+ }
+ }
+}
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 740c35a..3cd520b 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 740c35ae36f44748b3c91e60ee7dcb2fb6e99549
+Subproject commit 3cd520b1521ff7c558d0cd95274628a3a20de30a
diff --git a/polygerrit-ui/app/.eslintrc.js b/polygerrit-ui/app/.eslintrc.js
index a3da3cf..faf126c 100644
--- a/polygerrit-ui/app/.eslintrc.js
+++ b/polygerrit-ui/app/.eslintrc.js
@@ -82,7 +82,7 @@
],
// https://eslint.org/docs/rules/new-cap
'new-cap': ['error', {
- capIsNewExceptions: ['Polymer', 'GestureEventListeners'],
+ capIsNewExceptions: ['Polymer'],
capIsNewExceptionPattern: '^.*Mixin$',
}],
// https://eslint.org/docs/rules/no-console
@@ -313,7 +313,10 @@
},
},
{
- files: ['*_test.ts'],
+ files: [
+ '*_test.ts',
+ 'test-utils.ts',
+ ],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
diff --git a/polygerrit-ui/app/api/checks.ts b/polygerrit-ui/app/api/checks.ts
index 799d1f7..9e3c8f7 100644
--- a/polygerrit-ui/app/api/checks.ts
+++ b/polygerrit-ui/app/api/checks.ts
@@ -47,6 +47,7 @@
changeNumber: number;
patchsetNumber: number;
repo: string;
+ commmitMessage?: string;
}
export interface ChecksProvider {
@@ -247,7 +248,7 @@
checkName: string | undefined,
/** Identical to 'name' property of Action entity. */
actionName: string
-) => Promise<ActionResult>;
+) => Promise<ActionResult> | undefined;
export interface ActionResult {
/** An empty errorMessage means success. */
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index bd110c8..6797994 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -176,6 +176,7 @@
export declare interface RenderPreferences {
hide_left_side?: boolean;
disable_context_control_buttons?: boolean;
+ show_file_comment_button?: boolean;
}
/**
diff --git a/polygerrit-ui/app/constants/reporting.ts b/polygerrit-ui/app/constants/reporting.ts
new file mode 100644
index 0000000..1456c90
--- /dev/null
+++ b/polygerrit-ui/app/constants/reporting.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright (C) 2021 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.
+ */
+
+export enum LifeCycle {
+ PLUGIN_LIFE_CYCLE = 'Plugin life cycle',
+ STARTED_AS_USER = 'Started as user',
+ STARTED_AS_GUEST = 'Started as guest',
+ VISIBILILITY_HIDDEN = 'Visibility changed to hidden',
+ VISIBILILITY_VISIBLE = 'Visibility changed to visible',
+ EXTENSION_DETECTED = 'Extension detected',
+ PLUGINS_INSTALLED = 'Plugins installed',
+}
+
+export enum Execution {
+ PLUGIN_API = 'plugin-api',
+ REACHABLE_CODE = 'reachable code',
+ METHOD_USED = 'method used',
+}
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts
index cbb3d95..8c756cd 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts
@@ -21,7 +21,6 @@
import '../../shared/gr-icons/gr-icons';
import '../gr-permission/gr-permission';
import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {htmlTemplate} from './gr-access-section_html';
import {
@@ -71,9 +70,7 @@
}
@customElement('gr-access-section')
-export class GrAccessSection extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAccessSection extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
index 4fa84eb..f3a7fd3 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
@@ -21,7 +21,6 @@
import '../../shared/gr-list-view/gr-list-view';
import '../../shared/gr-overlay/gr-overlay';
import '../gr-create-group-dialog/gr-create-group-dialog';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-admin-group-list_html';
@@ -50,7 +49,7 @@
@customElement('gr-admin-group-list')
export class GrAdminGroupList extends ListViewMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -98,8 +97,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._getCreateGroupCapability();
fireTitleChange(this, 'Groups');
this._maybeOpenCreateOverlay(this.params);
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
index f2b4c89..5647b25 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
@@ -31,7 +31,6 @@
import '../gr-repo-dashboards/gr-repo-dashboards';
import '../gr-repo-detail-list/gr-repo-detail-list';
import '../gr-repo-list/gr-repo-list';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-admin-view_html';
@@ -92,9 +91,7 @@
}
@customElement('gr-admin-view')
-export class GrAdminView extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAdminView extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -178,8 +175,8 @@
private readonly jsAPI = appContext.jsApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.reload();
}
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.js b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.js
index e813bec..992ac54 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.js
@@ -20,9 +20,8 @@
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-import {stubBaseUrl} from '../../../test/test-utils.js';
+import {stubBaseUrl, stubRestApi} from '../../../test/test-utils.js';
import {GerritView} from '../../../services/router/router-model.js';
-import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-admin-view');
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts
index 79a3e95..1e38aa9 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts
@@ -16,7 +16,6 @@
*/
import '../../shared/gr-dialog/gr-dialog';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-delete-item-dialog_html';
@@ -36,8 +35,8 @@
}
@customElement('gr-confirm-delete-item-dialog')
-export class GrConfirmDeleteItemDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
+export class GrConfirmDeleteItemDialog extends LegacyElementMixin(
+ PolymerElement
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
index 2124949..102768c 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
@@ -20,7 +20,6 @@
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-select/gr-select';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-create-change-dialog_html';
@@ -50,9 +49,7 @@
};
}
@customElement('gr-create-change-dialog')
-export class GrCreateChangeDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrCreateChangeDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -98,8 +95,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
if (!this.repoName) {
return Promise.resolve();
}
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
index e68f6c9..39dbca8 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
@@ -17,7 +17,6 @@
import '@polymer/iron-input/iron-input';
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-create-group-dialog_html';
@@ -28,9 +27,7 @@
import {appContext} from '../../../services/app-context';
@customElement('gr-create-group-dialog')
-export class GrCreateGroupDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrCreateGroupDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.ts
index 6334670..afcb026 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.ts
@@ -19,7 +19,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-select/gr-select';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-create-pointer-dialog_html';
@@ -35,9 +34,7 @@
}
@customElement('gr-create-pointer-dialog')
-export class GrCreatePointerDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrCreatePointerDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
index f708485..ec768ff 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
@@ -20,14 +20,18 @@
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-select/gr-select';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-create-repo-dialog_html';
import {encodeURL, getBaseUrl} from '../../../utils/url-util';
import {page} from '../../../utils/page-wrapper-utils';
import {customElement, observe, property} from '@polymer/decorators';
-import {ProjectInput, RepoName} from '../../../types/common';
+import {
+ BranchName,
+ GroupId,
+ ProjectInput,
+ RepoName,
+} from '../../../types/common';
import {AutocompleteQuery} from '../../shared/gr-autocomplete/gr-autocomplete';
import {appContext} from '../../../services/app-context';
@@ -38,9 +42,7 @@
}
@customElement('gr-create-repo-dialog')
-export class GrCreateRepoDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrCreateRepoDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -53,8 +55,12 @@
create_empty_commit: true,
permissions_only: false,
name: '' as RepoName,
+ branches: [],
};
+ @property({type: String})
+ _defaultBranch?: BranchName;
+
@property({type: Boolean})
_repoCreated = false;
@@ -62,7 +68,7 @@
_repoOwner?: string;
@property({type: String})
- _repoOwnerId?: string;
+ _repoOwnerId?: GroupId;
@property({type: Object})
_query: AutocompleteQuery;
@@ -91,16 +97,9 @@
this.hasNewRepoName = !!name;
}
- @observe('_repoOwnerId')
- _repoOwnerIdUpdate(id?: string) {
- if (id) {
- this.set('_repoConfig.owners', [id]);
- } else {
- this.set('_repoConfig.owners', undefined);
- }
- }
-
handleCreateRepo() {
+ if (this._defaultBranch) this._repoConfig.branches = [this._defaultBranch];
+ if (this._repoOwnerId) this._repoConfig.owners = [this._repoOwnerId];
return this.restApiService
.createRepo(this._repoConfig)
.then(repoRegistered => {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_html.ts b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_html.ts
index 070ee86..f529ac6 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_html.ts
@@ -46,6 +46,17 @@
</iron-input>
</section>
<section>
+ <span class="title">Default Branch</span>
+ <iron-input autocomplete="off" bind-value="{{_defaultBranch}}">
+ <input
+ is="iron-input"
+ id="defaultBranchNameInput"
+ autocomplete="off"
+ bind-value="{{_defaultBranch}}"
+ />
+ </iron-input>
+ </section>
+ <section>
<span class="title">Rights inherit from</span>
<span class="value">
<gr-autocomplete
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.js b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.js
index f10141a..e6f9bbe 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.js
@@ -39,7 +39,6 @@
create_empty_commit: true,
parent: 'All-Project',
permissions_only: false,
- owners: ['testId'],
};
const saveStub = stubRestApi('createRepo').returns(Promise.resolve({}));
@@ -55,10 +54,10 @@
element._repoOwner = 'test';
element._repoOwnerId = 'testId';
+ element._defaultBranch = 'main';
element.$.repoNameInput.bindValue = configInputObj.name;
element.$.rightsInheritFromInput.bindValue = configInputObj.parent;
- element.$.ownerInput.text = configInputObj.owners[0];
element.$.initialCommit.bindValue =
configInputObj.create_empty_commit;
element.$.parentRepo.bindValue =
@@ -69,14 +68,15 @@
assert.deepEqual(element._repoConfig, configInputObj);
element.handleCreateRepo().then(() => {
- assert.isTrue(saveStub.lastCall.calledWithExactly(configInputObj));
+ assert.isTrue(saveStub.lastCall.calledWithExactly(
+ {
+ ...configInputObj,
+ owners: ['testId'],
+ branches: ['main'],
+ }
+ ));
done();
});
});
-
- test('testing observer of _repoOwner', () => {
- element._repoOwnerId = 'test-5';
- assert.deepEqual(element._repoConfig.owners, ['test-5']);
- });
});
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
index 201b340..959bfa3 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
@@ -19,7 +19,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../shared/gr-account-link/gr-account-link';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-group-audit-log_html';
@@ -40,7 +39,7 @@
@customElement('gr-group-audit-log')
export class GrGroupAuditLog extends ListViewMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -58,8 +57,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
fireTitleChange(this, 'Audit Log');
}
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.js b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.js
index 268112e..fdd5d15 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.js
@@ -17,8 +17,7 @@
import '../../../test/common-test-setup-karma.js';
import './gr-group-audit-log.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-import {addListenerForTest} from '../../../test/test-utils.js';
+import {stubRestApi, addListenerForTest} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-group-audit-log');
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
index 54f58c2..f7a2c8b 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
@@ -24,7 +24,6 @@
import '../../shared/gr-button/gr-button';
import '../../shared/gr-overlay/gr-overlay';
import '../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-group-members_html';
@@ -63,9 +62,7 @@
};
}
@customElement('gr-group-members')
-export class GrGroupMembers extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrGroupMembers extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -126,8 +123,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._loadGroupDetails();
fireTitleChange(this, 'Members');
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
index 84daef8..1ef80a9 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
@@ -22,7 +22,6 @@
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
import '../../shared/gr-select/gr-select';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-group_html';
@@ -71,9 +70,7 @@
}
@customElement('gr-group')
-export class GrGroup extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrGroup extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -134,8 +131,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._loadGroup();
}
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
index 4c09e30..0668330 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
@@ -17,8 +17,7 @@
import '../../../test/common-test-setup-karma.js';
import './gr-group.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-import {addListenerForTest} from '../../../test/test-utils.js';
+import {stubRestApi, addListenerForTest} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-group');
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
index 85ba052..6528d35 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
@@ -23,7 +23,6 @@
import '../../shared/gr-button/gr-button';
import '../gr-rule-editor/gr-rule-editor';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-permission_html';
@@ -94,9 +93,7 @@
* @event added-permission-removed
*/
@customElement('gr-permission')
-export class GrPermission extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrPermission extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts
index 1ab32ea..7c7c948 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts
@@ -20,7 +20,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-plugin-config-array-editor_html';
@@ -38,9 +37,7 @@
}
@customElement('gr-plugin-config-array-editor')
-class GrPluginConfigArrayEditor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrPluginConfigArrayEditor extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
index f5e9a92..c1c2bbd 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
@@ -17,7 +17,6 @@
import '../../../styles/gr-table-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-list-view/gr-list-view';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-plugin-list_html';
@@ -27,8 +26,7 @@
} from '../../../mixins/gr-list-view-mixin/gr-list-view-mixin';
import {customElement, property} from '@polymer/decorators';
import {PluginInfo} from '../../../types/common';
-import {firePageError} from '../../../utils/event-util';
-import {fireTitleChange} from '../../../utils/event-util';
+import {firePageError, fireTitleChange} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
import {ErrorCallback} from '../../../api/rest';
@@ -37,7 +35,7 @@
}
@customElement('gr-plugin-list')
export class GrPluginList extends ListViewMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -80,8 +78,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
fireTitleChange(this, 'Plugins');
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
index 56c5733..17a9ee6 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
@@ -19,7 +19,6 @@
import '../../../styles/shared-styles';
import '../gr-access-section/gr-access-section';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-access_html';
@@ -63,9 +62,7 @@
* @event show-alert
*/
@customElement('gr-repo-access')
-export class GrRepoAccess extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrRepoAccess extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
index f209729..e988a33 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
@@ -23,7 +23,6 @@
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-overlay/gr-overlay';
import '../gr-create-change-dialog/gr-create-change-dialog';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-commands_html';
@@ -61,9 +60,7 @@
}
@customElement('gr-repo-commands')
-export class GrRepoCommands extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrRepoCommands extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -94,8 +91,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._loadRepo();
fireTitleChange(this, 'Repo Commands');
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
index 7b3c7fb..a132f64 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
@@ -17,7 +17,6 @@
import '../../../styles/shared-styles';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-dashboards_html';
@@ -34,9 +33,7 @@
}
@customElement('gr-repo-dashboards')
-export class GrRepoDashboards extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrRepoDashboards extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
index a486e27..4cb325c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
@@ -28,7 +28,6 @@
import '../gr-create-pointer-dialog/gr-create-pointer-dialog';
import '../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-detail-list_html';
@@ -63,7 +62,7 @@
}
@customElement('gr-repo-detail-list')
export class GrRepoDetailList extends ListViewMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
index d6aa0e6..bcbc756 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
@@ -20,7 +20,6 @@
import '../../shared/gr-list-view/gr-list-view';
import '../../shared/gr-overlay/gr-overlay';
import '../gr-create-repo-dialog/gr-create-repo-dialog';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-list_html';
@@ -50,7 +49,7 @@
@customElement('gr-repo-list')
export class GrRepoList extends ListViewMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -91,8 +90,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._getCreateRepoCapability();
fireTitleChange(this, 'Repos');
this._maybeOpenCreateOverlay(this.params);
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
index e9a6158..347a56b 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
@@ -24,7 +24,6 @@
import '../../shared/gr-tooltip-content/gr-tooltip-content';
import '../gr-plugin-config-array-editor/gr-plugin-config-array-editor';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-plugin-config_html';
@@ -62,9 +61,7 @@
}
@customElement('gr-repo-plugin-config')
-class GrRepoPluginConfig extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrRepoPluginConfig extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
index b6881ff..6ba9ad8 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
@@ -24,7 +24,6 @@
import '../../../styles/gr-subpage-styles';
import '../../../styles/shared-styles';
import '../gr-repo-plugin-config/gr-repo-plugin-config';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo_html';
@@ -83,9 +82,7 @@
};
@customElement('gr-repo')
-export class GrRepo extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrRepo extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -144,8 +141,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._loadRepo();
fireTitleChange(this, `${this.repo}`);
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
index 13d0e50..f8c03d2 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
@@ -19,7 +19,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-select/gr-select';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-rule-editor_html';
@@ -102,9 +101,7 @@
}
@customElement('gr-rule-editor')
-export class GrRuleEditor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrRuleEditor extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -158,8 +155,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
// Check needed for test purposes.
if (!this._originalRuleValues && this.rule) {
// Observer _handleValueChange is called after the ready()
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js
index 9cc6357..9c3646a 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js
@@ -197,7 +197,7 @@
element._setupValues(element.rule);
flush();
flush(() => {
- element.attached();
+ element.connectedCallback();
done();
});
});
@@ -306,7 +306,7 @@
flush();
element.rule.value.added = true;
flush(() => {
- element.attached();
+ element.connectedCallback();
done();
});
});
@@ -371,7 +371,7 @@
element._setupValues(element.rule);
flush();
flush(() => {
- element.attached();
+ element.connectedCallback();
done();
});
});
@@ -425,7 +425,7 @@
flush();
element.rule.value.added = true;
flush(() => {
- element.attached();
+ element.connectedCallback();
done();
});
});
@@ -482,7 +482,7 @@
element._setupValues(element.rule);
flush();
flush(() => {
- element.attached();
+ element.connectedCallback();
done();
});
});
@@ -524,7 +524,7 @@
flush();
element.rule.value.added = true;
flush(() => {
- element.attached();
+ element.connectedCallback();
done();
});
});
@@ -571,7 +571,7 @@
element._setupValues(element.rule);
flush();
flush(() => {
- element.attached();
+ element.connectedCallback();
done();
});
});
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index 558037d..6fbee32 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -26,7 +26,6 @@
import '../../../styles/shared-styles';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-list-item_html';
@@ -79,7 +78,7 @@
@customElement('gr-change-list-item')
export class GrChangeListItem extends ChangeTableMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -126,8 +125,8 @@
reporting: ReportingService = appContext.reportingService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
getPluginLoader()
.awaitPluginsLoaded()
.then(() => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.js
index acf71c3..5570d26 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.js
@@ -27,7 +27,6 @@
let element;
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
element = basicFixture.instantiate();
});
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index 42741fa..34572c5 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -20,7 +20,6 @@
import '../gr-repo-header/gr-repo-header';
import '../gr-user-header/gr-user-header';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-list-view_html';
@@ -62,9 +61,7 @@
}
@customElement('gr-change-list-view')
-export class GrChangeListView extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrChangeListView extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -121,8 +118,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._loadPreferences();
}
@@ -144,7 +141,7 @@
// NOTE: This method may be called before attachment. Fire title-change
// in an async so that attachment to the DOM can take place first.
- this.async(() => fireTitleChange(this, this._query));
+ setTimeout(() => fireTitleChange(this, this._query));
this.restApiService
.getPreferences()
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index f26cd46..6eb3bc1 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -21,7 +21,6 @@
import '../../../styles/shared-styles';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
import {afterNextRender} from '@polymer/polymer/lib/utils/render-status';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-list_html';
@@ -74,9 +73,7 @@
}
@customElement('gr-change-list')
export class GrChangeList extends ChangeTableMixin(
- KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
- )
+ KeyboardShortcutMixin(LegacyElementMixin(PolymerElement))
) {
static get template() {
return htmlTemplate;
@@ -176,8 +173,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
getPluginLoader()
.awaitPluginsLoaded()
.then(() => {
@@ -187,6 +184,12 @@
});
}
+ /** @override */
+ disconnectedCallback() {
+ this.$.cursor.unsetCursor();
+ super.disconnectedCallback();
+ }
+
/**
* Iron-a11y-keys-behavior catches keyboard events globally. Some keyboard
* events must be scoped to a component level (e.g. `enter`) in order to not
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
index f320296..a6f3d85 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
@@ -18,7 +18,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-icons/gr-icons';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {customElement} from '@polymer/decorators';
@@ -32,9 +31,7 @@
}
@customElement('gr-create-change-help')
-class GrCreateChangeHelp extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrCreateChangeHelp extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts
index 1ee8cb5..ed4c968 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts
@@ -19,7 +19,6 @@
import '../../shared/gr-overlay/gr-overlay';
import '../../shared/gr-shell-command/gr-shell-command';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {customElement, property} from '@polymer/decorators';
@@ -44,9 +43,7 @@
}
@customElement('gr-create-commands-dialog')
-export class GrCreateCommandsDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrCreateCommandsDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts
index e53f68b..5c263bb 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts
@@ -18,7 +18,6 @@
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-overlay/gr-overlay';
import '../../shared/gr-repo-branch-picker/gr-repo-branch-picker';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-create-destination-dialog_html';
@@ -44,8 +43,8 @@
}
@customElement('gr-create-destination-dialog')
-export class GrCreateDestinationDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
+export class GrCreateDestinationDialog extends LegacyElementMixin(
+ PolymerElement
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index a34bd63..3b1c61e 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -23,7 +23,6 @@
import '../gr-create-change-help/gr-create-change-help';
import '../gr-create-destination-dialog/gr-create-destination-dialog';
import '../gr-user-header/gr-user-header';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-dashboard-view_html';
@@ -79,9 +78,7 @@
}
@customElement('gr-dashboard-view')
-export class GrDashboardView extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDashboardView extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -130,8 +127,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._loadPreferences();
this.addEventListener('reload', e => {
e.stopPropagation();
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
index a5de72b..165306e 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
@@ -17,13 +17,12 @@
import '../../../test/common-test-setup-karma.js';
import './gr-dashboard-view.js';
-import {isHidden} from '../../../test/test-utils.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {GerritView} from '../../../services/router/router-model.js';
import {changeIsOpen} from '../../../utils/change-util.js';
import {ChangeStatus} from '../../../constants/constants.js';
import {createAccountWithId} from '../../../test/test-data-generators.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {addListenerForTest, stubRestApi, isHidden} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-dashboard-view');
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
index 35a2a7f..ec24800 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
@@ -18,7 +18,6 @@
import '../../../styles/dashboard-header-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-date-formatter/gr-date-formatter';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-header_html';
@@ -28,9 +27,7 @@
/** @extends PolymerElement */
@customElement('gr-repo-header')
-class GrRepoHeader extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrRepoHeader extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts
index cfee0cd..c4406cd 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts
@@ -21,7 +21,6 @@
import '../../shared/gr-avatar/gr-avatar';
import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../../styles/dashboard-header-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-user-header_html';
@@ -32,9 +31,7 @@
import {appContext} from '../../../services/app-context';
@customElement('gr-user-header')
-export class GrUserHeader extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrUserHeader extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 87b09c7..dcd1d93 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -30,7 +30,6 @@
import '../gr-confirm-submit-dialog/gr-confirm-submit-dialog';
import '../../../styles/shared-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-actions_html';
@@ -338,8 +337,7 @@
}
@customElement('gr-change-actions')
-export class GrChangeActions
- extends GestureEventListeners(LegacyElementMixin(PolymerElement))
+export class GrChangeActions extends LegacyElementMixin(PolymerElement)
implements GrChangeActionsElement {
static get template() {
return htmlTemplate;
@@ -2083,7 +2081,7 @@
}
if (attemptsRemaining) {
- this.async(check, AWAIT_CHANGE_TIMEOUT_MS);
+ setTimeout(check, AWAIT_CHANGE_TIMEOUT_MS);
} else {
resolve(false);
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js
index e71c086..3d91a06 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js
@@ -18,10 +18,9 @@
import '../../../test/common-test-setup-karma.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
import './gr-change-metadata.js';
-import {resetPlugins} from '../../../test/test-utils.js';
+import {resetPlugins, stubRestApi} from '../../../test/test-utils.js';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
-import {stubRestApi} from '../../../test/test-utils.js';
const testHtmlPlugin = document.createElement('dom-module');
testHtmlPlugin.innerHTML = `
@@ -87,7 +86,6 @@
}
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('deleteVote').returns(Promise.resolve({ok: true}));
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index ef4d323..44974ce 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -33,7 +33,6 @@
import '../gr-reviewer-list/gr-reviewer-list';
import '../../shared/gr-account-list/gr-account-list';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-metadata_html';
@@ -67,7 +66,7 @@
ServerInfo,
TopicName,
} from '../../../types/common';
-import {assertNever} from '../../../utils/common-util';
+import {assertNever, unique} from '../../../utils/common-util';
import {GrEditableLabel} from '../../shared/gr-editable-label/gr-editable-label';
import {GrLinkedChip} from '../../shared/gr-linked-chip/gr-linked-chip';
import {appContext} from '../../../services/app-context';
@@ -78,7 +77,15 @@
DisplayRules,
} from '../../../utils/change-metadata-util';
import {fireEvent} from '../../../utils/event-util';
-import {EditRevisionInfo, ParsedChangeInfo} from '../../../types/types';
+import {
+ EditRevisionInfo,
+ notUndefined,
+ ParsedChangeInfo,
+} from '../../../types/types';
+import {
+ AutocompleteQuery,
+ AutocompleteSuggestion,
+} from '../../shared/gr-autocomplete/gr-autocomplete';
const HASHTAG_ADD_MESSAGE = 'Add Hashtag';
@@ -118,9 +125,7 @@
}
@customElement('gr-change-metadata')
-export class GrChangeMetadata extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrChangeMetadata extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -206,16 +211,22 @@
@property({type: Boolean})
_isNewChangeSummaryUiEnabled = false;
+ @property({type: Object})
+ queryTopic?: AutocompleteQuery;
+
flagsService = appContext.flagsService;
restApiService = appContext.restApiService;
+ private readonly reporting = appContext.reportingService;
+
/** @override */
ready() {
super.ready();
this._isNewChangeSummaryUiEnabled = this.flagsService.isEnabled(
KnownExperimentId.NEW_CHANGE_SUMMARY_UI
);
+ this.queryTopic = (input: string) => this._getTopicSuggestions(input);
}
@observe('change.labels')
@@ -567,6 +578,10 @@
_onShowAllClick() {
this._showAllSections = !this._showAllSections;
+ this.reporting.reportInteraction('toggle show all button', {
+ sectionName: 'metadata',
+ toState: this._showAllSections ? 'Show all' : 'Show less',
+ });
}
/**
@@ -672,6 +687,18 @@
provider.init();
return provider;
}
+
+ _getTopicSuggestions(input: string): Promise<AutocompleteSuggestion[]> {
+ return this.restApiService
+ .getChangesWithSimilarTopic(input)
+ .then(response =>
+ (response ?? [])
+ .map(change => change.topic)
+ .filter(notUndefined)
+ .filter(unique)
+ .map(topic => ({name: topic, value: topic}))
+ );
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
index 59bf8ad..4f5e6a6 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
@@ -363,6 +363,8 @@
read-only="[[_topicReadOnly]]"
on-changed="_handleTopicChanged"
show-as-edit-pencil="[[_isNewChangeSummaryUiEnabled]]"
+ autocomplete="true"
+ query="[[queryTopic]]"
></gr-editable-label>
</template>
</span>
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts
index adc7fd3..0b65f53 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts
@@ -20,7 +20,6 @@
import '../../shared/gr-label/gr-label';
import '../../shared/gr-label-info/gr-label-info';
import '../../shared/gr-limited-text/gr-limited-text';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-requirements_html';
@@ -58,9 +57,7 @@
}
@customElement('gr-change-requirements')
-class GrChangeRequirements extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrChangeRequirements extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -94,6 +91,8 @@
KnownExperimentId.NEW_CHANGE_SUMMARY_UI
);
+ private readonly reporting = appContext.reportingService;
+
_computeShowWip(change: ChangeInfo) {
return change.work_in_progress;
}
@@ -193,6 +192,10 @@
_handleShowHide() {
this._showOptionalLabels = !this._showOptionalLabels;
+ this.reporting.reportInteraction('toggle show all button', {
+ sectionName: 'optional labels',
+ toState: this._showOptionalLabels ? 'Show all' : 'Show less',
+ });
}
_computeSubmitRequirementEndpoint(item: ChangeRequirement | ChangeWIP) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
index 8322f3c..6e20f03 100644
--- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
@@ -77,6 +77,8 @@
@property()
category?: CommentTabState;
+ private readonly reporting = appContext.reportingService;
+
static get styles() {
return [
sharedStyles,
@@ -131,6 +133,9 @@
private handleClick(e: MouseEvent) {
e.stopPropagation();
e.preventDefault();
+ this.reporting.reportInteraction('comment chip click', {
+ category: this.category,
+ });
fireShowPrimaryTab(this, PrimaryTab.COMMENT_THREADS, true, {
commentTab: this.category,
});
@@ -380,7 +385,7 @@
}
private onChipClick(state: ChecksTabState) {
- fireShowPrimaryTab(this, PrimaryTab.CHECKS, true, {
+ fireShowPrimaryTab(this, PrimaryTab.CHECKS, false, {
checksTab: state,
});
}
@@ -427,7 +432,7 @@
!!draftCount ||
!!countUnresolvedComments}
>
- No Comments</span
+ No comments</span
><gr-summary-chip
styleType=${SummaryChipStyles.WARNING}
category=${CommentTabState.DRAFTS}
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index a7f5ea7..aded3b9 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -45,7 +45,6 @@
import '../gr-upload-help-dialog/gr-upload-help-dialog';
import '../../checks/gr-checks-tab';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-view_html';
@@ -111,6 +110,7 @@
ChangeId,
RelatedChangeAndCommitInfo,
RelatedChangesInfo,
+ BasePatchSetNum,
} from '../../../types/common';
import {DiffPreferencesInfo} from '../../../types/diff';
import {GrReplyDialog, FocusTarget} from '../gr-reply-dialog/gr-reply-dialog';
@@ -166,9 +166,9 @@
fireEvent,
firePageError,
fireDialogChange,
+ fireTitleChange,
} from '../../../utils/event-util';
import {KnownExperimentId} from '../../../services/flags/flags';
-import {fireTitleChange} from '../../../utils/event-util';
import {GerritView} from '../../../services/router/router-model';
import {takeUntil} from 'rxjs/operators';
import {aPluginHasRegistered$} from '../../../services/checks/checks-model';
@@ -250,7 +250,7 @@
@customElement('gr-change-view')
export class GrChangeView extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -605,20 +605,6 @@
}
/** @override */
- connectedCallback() {
- super.connectedCallback();
- this._throttledToggleChangeStar = this._throttleWrap(e =>
- this._handleToggleChangeStar(e as CustomKeyboardEvent)
- );
- }
-
- /** @override */
- disconnectedCallback() {
- this.disconnected$.next();
- super.disconnectedCallback();
- }
-
- /** @override */
created() {
super.created();
@@ -651,8 +637,11 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
+ this._throttledToggleChangeStar = this._throttleWrap(e =>
+ this._handleToggleChangeStar(e as CustomKeyboardEvent)
+ );
this._getServerConfig().then(config => {
this._serverConfig = config;
this._replyDisabled = false;
@@ -719,8 +708,8 @@
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
+ this.disconnected$.next();
this.unlisten(window, 'scroll', '_handleScroll');
this.unlisten(document, 'visibilitychange', '_handleVisibilityChange');
this.cancelDebouncer(DEBOUNCER_REPLY_OVERLAY_REFIT);
@@ -729,6 +718,7 @@
if (this._updateCheckTimerHandle) {
this._cancelUpdateCheckTimer();
}
+ super.disconnectedCallback();
}
get messagesList(): GrMessagesList | null {
@@ -822,11 +812,14 @@
}
const tabName = tabs[activeIndex].dataset['name'];
if (scrollIntoView) {
- paperTabs.scrollIntoView();
+ paperTabs.scrollIntoView({block: 'center'});
}
if (paperTabs.selected !== activeIndex) {
+ // paperTabs.selected is undefined during rendering
+ if (paperTabs.selected !== undefined) {
+ this.reporting.reportInteraction('show-tab', {tabName});
+ }
paperTabs.selected = activeIndex;
- this.reporting.reportInteraction('show-tab', {tabName});
}
return tabName;
}
@@ -1383,7 +1376,7 @@
this._sendShowChangeEvent();
- this.async(() => {
+ setTimeout(() => {
if (this.viewState.scrollTop) {
document.documentElement.scrollTop = document.body.scrollTop = this.viewState.scrollTop;
} else {
@@ -1495,10 +1488,10 @@
if (this.viewState.showReplyDialog) {
this._openReplyDialog(this.$.replyDialog.FocusTarget.ANY);
// TODO(kaspern@): Find a better signal for when to call center.
- this.async(() => {
+ setTimeout(() => {
this.$.replyOverlay.center();
}, 100);
- this.async(() => {
+ setTimeout(() => {
this.$.replyOverlay.center();
}, 1000);
this.set('viewState.showReplyDialog', false);
@@ -1769,7 +1762,7 @@
GerritNav.navigateToChange(
this._change,
latestPatchNum,
- this._patchRange.patchNum
+ this._patchRange.patchNum as BasePatchSetNum
);
}
@@ -1899,6 +1892,7 @@
}
_openReplyDialog(section?: FocusTarget) {
+ if (!this._change) return;
this.$.replyOverlay.open().finally(() => {
// the following code should be executed no matter open succeed or not
this._resetReplyOverlayFocusStops();
@@ -2050,7 +2044,7 @@
}
);
}
- return false;
+ return true;
}
);
}
@@ -2227,10 +2221,11 @@
}
});
- // Resolves when the project config has loaded.
- const projectConfigLoaded = detailCompletes.then(() =>
- this._getProjectConfig()
- );
+ // Resolves when the project config has successfully loaded.
+ const projectConfigLoaded = detailCompletes.then(success => {
+ if (!success) return Promise.resolve();
+ return this._getProjectConfig();
+ });
allDataPromises.push(projectConfigLoaded);
// Resolves when change comments have loaded (comments, drafts and robot
@@ -2594,7 +2589,7 @@
return;
}
- this._updateCheckTimerHandle = this.async(() => {
+ this._updateCheckTimerHandle = window.setTimeout(() => {
assertIsDefined(this._change, '_change');
const change = this._change;
fetchChangeUpdates(change, this.restApiService).then(result => {
@@ -2650,7 +2645,7 @@
_cancelUpdateCheckTimer() {
if (this._updateCheckTimerHandle) {
- this.cancelAsync(this._updateCheckTimerHandle);
+ window.clearTimeout(this._updateCheckTimerHandle);
}
this._updateCheckTimerHandle = null;
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index 08c04e8..feb61cf 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -149,7 +149,7 @@
overflow-x: hidden;
}
.relatedChanges {
- flex: 1 1 auto;
+ flex: 0 1 auto;
overflow: hidden;
padding: var(--spacing-l) 0;
}
@@ -171,6 +171,9 @@
overflow: hidden;
position: relative; /* for arrowToCurrentChange to have position:absolute and be hidden */
}
+ .emptySpace {
+ flex-grow: 1;
+ }
.commitContainer {
display: flex;
flex-direction: column;
@@ -575,6 +578,7 @@
</div>
</template>
</div>
+ <div class="emptySpace"></div>
</div>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index ae446cd..b4e39f5 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -38,6 +38,7 @@
import 'lodash/lodash';
import {
stubRestApi,
+ SinonSpyMember,
TestKeyboardShortcutBinder,
} from '../../../test/test-utils';
import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
@@ -64,6 +65,7 @@
import {
AccountId,
ApprovalInfo,
+ BasePatchSetNum,
ChangeId,
ChangeInfo,
CommitId,
@@ -87,11 +89,7 @@
} from '@polymer/iron-test-helpers/mock-interactions';
import {GrEditControls} from '../../edit/gr-edit-controls/gr-edit-controls';
import {AppElementChangeViewParams} from '../../gr-app-types';
-import {
- SinonFakeTimers,
- SinonSpy,
- SinonStubbedMember,
-} from 'sinon/pkg/sinon-esm';
+import {SinonFakeTimers, SinonStubbedMember} from 'sinon/pkg/sinon-esm';
import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
import {CustomKeyboardEvent} from '../../../types/events';
import {
@@ -100,7 +98,6 @@
UIDraft,
UIRobot,
} from '../../../utils/comment-util';
-import 'lodash/lodash';
import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
import {GerritView} from '../../../services/router/router-model';
import {ParsedChangeInfo} from '../../../types/types';
@@ -110,11 +107,6 @@
const pluginApi = _testOnly_initGerritPluginApi();
const fixture = fixtureFromElement('gr-change-view');
-type SinonSpyMember<F extends (...args: any) => any> = SinonSpy<
- Parameters<F>,
- ReturnType<F>
->;
-
suite('gr-change-view tests', () => {
let element: GrChangeView;
@@ -435,7 +427,7 @@
};
element._patchRange = {
patchNum: 3 as PatchSetNum,
- basePatchNum: 1 as PatchSetNum,
+ basePatchNum: 1 as BasePatchSetNum,
};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
element._handleDiffAgainstBase(new CustomEvent('') as CustomKeyboardEvent);
@@ -451,7 +443,7 @@
revisions: createRevisions(10),
};
element._patchRange = {
- basePatchNum: 1 as PatchSetNum,
+ basePatchNum: 1 as BasePatchSetNum,
patchNum: 3 as PatchSetNum,
};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
@@ -472,7 +464,7 @@
};
element._patchRange = {
patchNum: 3 as PatchSetNum,
- basePatchNum: 1 as PatchSetNum,
+ basePatchNum: 1 as BasePatchSetNum,
};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
element._handleDiffBaseAgainstLeft(
@@ -490,7 +482,7 @@
revisions: createRevisions(10),
};
element._patchRange = {
- basePatchNum: 1 as PatchSetNum,
+ basePatchNum: 1 as BasePatchSetNum,
patchNum: 3 as PatchSetNum,
};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
@@ -509,7 +501,7 @@
revisions: createRevisions(10),
};
element._patchRange = {
- basePatchNum: 1 as PatchSetNum,
+ basePatchNum: 1 as BasePatchSetNum,
patchNum: 3 as PatchSetNum,
};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
@@ -1534,7 +1526,7 @@
element._initialLoadComplete = true;
- value.basePatchNum = 1 as PatchSetNum;
+ value.basePatchNum = 1 as BasePatchSetNum;
value.patchNum = 2 as PatchSetNum;
element._paramsChanged(value);
assert.isFalse(reloadStub.calledTwice);
@@ -1567,7 +1559,7 @@
element._initialLoadComplete = true;
- value.basePatchNum = 1 as PatchSetNum;
+ value.basePatchNum = 1 as BasePatchSetNum;
value.patchNum = 2 as PatchSetNum;
element._paramsChanged(value);
assert.isTrue(reloadPortedCommentsStub.calledOnce);
@@ -1848,7 +1840,7 @@
};
sinon.stub(element, '_getEdit').callsFake(() =>
Promise.resolve({
- base_patch_set_number: 1 as PatchSetNum,
+ base_patch_set_number: 1 as BasePatchSetNum,
commit: {...editCommit},
base_revision: 'abc',
ref: 'some/ref' as GitRef,
@@ -1991,7 +1983,6 @@
});
test('revert dialog opened with revert param', done => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(true));
const awaitPluginsLoadedStub = sinon
.stub(getPluginLoader(), 'awaitPluginsLoaded')
.callsFake(() => Promise.resolve());
@@ -2334,18 +2325,11 @@
});
suite('update checks', () => {
+ let clock: SinonFakeTimers;
let startUpdateCheckTimerSpy: SinonSpyMember<typeof element._startUpdateCheckTimer>;
- let asyncStub: SinonStubbedMember<typeof element.async>;
setup(() => {
+ clock = sinon.useFakeTimers();
startUpdateCheckTimerSpy = sinon.spy(element, '_startUpdateCheckTimer');
- asyncStub = sinon.stub(element, 'async').callsFake(f => {
- // Only fire the async callback one time.
- if (asyncStub.callCount > 1) {
- return 1;
- }
- f.call(element);
- return 1;
- });
element._change = {
...createChange(),
revisions: createRevisions(1),
@@ -2389,14 +2373,14 @@
...createServerInfo(),
change: {...createChangeConfig(), update_delay: 12345},
};
+ clock.tick(12345 * 1000);
await flush();
assert.equal(startUpdateCheckTimerSpy.callCount, 2);
assert.isTrue(getChangeDetailStub.called);
- assert.equal(asyncStub.lastCall.args[1], 12345 * 1000);
});
- test('_startUpdateCheckTimer out-of-date shows an alert', done => {
+ test('_startUpdateCheckTimer out-of-date shows an alert', async () => {
stubRestApi('getChangeDetail').callsFake(() =>
Promise.resolve({
...createChange(),
@@ -2407,15 +2391,18 @@
})
);
+ let alertMessage = 'alert not fired';
element.addEventListener('show-alert', e => {
- assert.equal(e.detail.message, 'A newer patch set has been uploaded');
- done();
+ alertMessage = e.detail.message;
});
element._serverConfig = {
...createServerInfo(),
change: {...createChangeConfig(), update_delay: 12345},
};
+ clock.tick(12345 * 1000);
+ await flush();
+ assert.equal(alertMessage, 'A newer patch set has been uploaded');
assert.equal(startUpdateCheckTimerSpy.callCount, 1);
});
@@ -2435,13 +2422,14 @@
...createServerInfo(),
change: {...createChangeConfig(), update_delay: 12345},
};
+ clock.tick(12345 * 1000 * 2);
await flush();
// No toast, instead a second call to _startUpdateCheckTimer().
assert.equal(startUpdateCheckTimerSpy.callCount, 2);
});
- test('_startUpdateCheckTimer new status shows an alert', done => {
+ test('_startUpdateCheckTimer new status shows an alert', async () => {
stubRestApi('getChangeDetail').callsFake(() =>
Promise.resolve({
...createChange(),
@@ -2453,17 +2441,21 @@
})
);
+ let alertMessage = 'alert not fired';
element.addEventListener('show-alert', e => {
- assert.equal(e.detail.message, 'This change has been merged');
- done();
+ alertMessage = e.detail.message;
});
element._serverConfig = {
...createServerInfo(),
change: {...createChangeConfig(), update_delay: 12345},
};
+ clock.tick(12345 * 1000);
+ await flush();
+
+ assert.equal(alertMessage, 'This change has been merged');
});
- test('_startUpdateCheckTimer new messages shows an alert', done => {
+ test('_startUpdateCheckTimer new messages shows an alert', async () => {
stubRestApi('getChangeDetail').callsFake(() =>
Promise.resolve({
...createChange(),
@@ -2473,17 +2465,19 @@
current_revision: 'rev1' as CommitId,
})
);
+
+ let alertMessage = 'alert not fired';
element.addEventListener('show-alert', e => {
- assert.equal(
- e.detail.message,
- 'There are new messages on this change'
- );
- done();
+ alertMessage = e.detail.message;
});
element._serverConfig = {
...createServerInfo(),
change: {...createChangeConfig(), update_delay: 12345},
};
+ clock.tick(12345 * 1000);
+ await flush();
+
+ assert.equal(alertMessage, 'There are new messages on this change');
});
});
@@ -2562,15 +2556,9 @@
createAppElementChangeViewParams()
)
);
- assert.isFalse(
- callCompute(
- {basePatchNum: EditPatchSetNum, patchNum: 1 as PatchSetNum},
- createAppElementChangeViewParams()
- )
- );
assert.isTrue(
callCompute(
- {basePatchNum: 1 as PatchSetNum, patchNum: EditPatchSetNum},
+ {basePatchNum: 1 as BasePatchSetNum, patchNum: EditPatchSetNum},
createAppElementChangeViewParams()
)
);
@@ -2600,7 +2588,7 @@
const edit: EditInfo = {
ref: 'ref/test/abc' as GitRef,
base_revision: 'abc',
- base_patch_set_number: 1 as PatchSetNum,
+ base_patch_set_number: 1 as BasePatchSetNum,
commit: {...editCommit},
fetch: {},
};
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
index 18bd3a0..0b148b7 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
@@ -16,7 +16,6 @@
*/
import '../../../styles/shared-styles';
import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-commit-info_html';
@@ -31,9 +30,7 @@
}
@customElement('gr-commit-info')
-export class GrCommitInfo extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrCommitInfo extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
index 10563ee..df99471 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
@@ -17,7 +17,6 @@
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
import '../../shared/gr-dialog/gr-dialog';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-abandon-dialog_html';
@@ -42,7 +41,7 @@
*/
@customElement('gr-confirm-abandon-dialog')
export class GrConfirmAbandonDialog extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts
index 2f33858..5aa5e8b 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts
@@ -16,7 +16,6 @@
*/
import '../../../styles/shared-styles';
import '../../shared/gr-dialog/gr-dialog';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-cherrypick-conflict-dialog_html';
@@ -29,8 +28,8 @@
}
@customElement('gr-confirm-cherrypick-conflict-dialog')
-export class GrConfirmCherrypickConflictDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
+export class GrConfirmCherrypickConflictDialog extends LegacyElementMixin(
+ PolymerElement
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index 225e3e9..3475e45 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -19,7 +19,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-dialog/gr-dialog';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-cherrypick-dialog_html';
@@ -38,6 +37,7 @@
import {AutocompleteSuggestion} from '../../shared/gr-autocomplete/gr-autocomplete';
import {HttpMethod, ChangeStatus} from '../../../constants/constants';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
+import {fireEvent} from '../../../utils/event-util';
const SUGGESTIONS_LIMIT = 15;
const CHANGE_SUBJECT_LIMIT = 50;
@@ -74,8 +74,8 @@
}
@customElement('gr-confirm-cherrypick-dialog')
-export class GrConfirmCherrypickDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
+export class GrConfirmCherrypickDialog extends LegacyElementMixin(
+ PolymerElement
) {
static get template() {
return htmlTemplate;
@@ -264,10 +264,12 @@
_handlecherryPickSingleChangeClicked() {
this._cherryPickType = CherryPickType.SINGLE_CHANGE;
+ fireEvent(this, 'iron-resize');
}
_handlecherryPickTopicClicked() {
this._cherryPickType = CherryPickType.TOPIC;
+ fireEvent(this, 'iron-resize');
}
@observe('changeStatus', 'commitNum', 'commitMessage')
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts
index 4de395c..6c0099e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts
@@ -46,11 +46,12 @@
}
.cherryPickTopicLayout {
display: flex;
+ align-items: center;
+ margin-bottom: var(--spacing-m);
}
.cherryPickSingleChange,
.cherryPickTopic {
margin-left: var(--spacing-m);
- margin-bottom: var(--spacing-m);
}
.cherry-pick-topic-message {
margin-bottom: var(--spacing-m);
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
index 5e95e66..9da7ea2 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
@@ -17,7 +17,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-dialog/gr-dialog';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-move-dialog_html';
@@ -31,7 +30,7 @@
@customElement('gr-confirm-move-dialog')
export class GrConfirmMoveDialog extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
index b2b6e61..aa8745a 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
@@ -17,7 +17,6 @@
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-dialog/gr-dialog';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-rebase-dialog_html';
@@ -49,9 +48,7 @@
}
@customElement('gr-confirm-rebase-dialog')
-export class GrConfirmRebaseDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrConfirmRebaseDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
index e10b12b..a785afd 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
@@ -17,7 +17,6 @@
import '../../shared/gr-dialog/gr-dialog';
import '../../../styles/shared-styles';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-revert-dialog_html';
@@ -41,9 +40,7 @@
}
@customElement('gr-confirm-revert-dialog')
-export class GrConfirmRevertDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrConfirmRevertDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.ts
index 9e1256f..609947d 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.ts
@@ -16,7 +16,6 @@
*/
import '../../shared/gr-dialog/gr-dialog';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-revert-submission-dialog_html';
@@ -29,8 +28,8 @@
const CHANGE_SUBJECT_LIMIT = 50;
@customElement('gr-confirm-revert-submission-dialog')
-export class GrConfirmRevertSubmissionDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
+export class GrConfirmRevertSubmissionDialog extends LegacyElementMixin(
+ PolymerElement
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
index df0678ee..7130907 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
@@ -21,7 +21,6 @@
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
import '../../../styles/shared-styles';
import '../gr-thread-list/gr-thread-list';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-submit-dialog_html';
@@ -37,9 +36,7 @@
};
}
@customElement('gr-confirm-submit-dialog')
-export class GrConfirmSubmitDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrConfirmSubmitDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.js
deleted file mode 100644
index e175fda..0000000
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * @license
- * Copyright (C) 2018 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.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-confirm-submit-dialog.js';
-
-const basicFixture = fixtureFromElement('gr-confirm-submit-dialog');
-
-suite('gr-file-list-header tests', () => {
- let element;
-
- setup(() => {
- element = basicFixture.instantiate();
- element._initialised = true;
- });
-
- test('display', () => {
- element.action = {label: 'my-label'};
- element.change = {
- subject: 'my-subject',
- revisions: {},
- };
- flush();
- const header = element.shadowRoot
- .querySelector('.header');
- assert.equal(header.textContent.trim(), 'my-label');
-
- const message = element.shadowRoot
- .querySelector('.main p');
- assert.notEqual(message.textContent.length, 0);
- assert.notEqual(message.textContent.indexOf('my-subject'), -1);
- });
-
- test('_computeUnresolvedCommentsWarning', () => {
- const change = {unresolved_comment_count: 1};
- assert.equal(element._computeUnresolvedCommentsWarning(change),
- 'Heads Up! 1 unresolved comment.');
-
- const change2 = {unresolved_comment_count: 2};
- assert.equal(element._computeUnresolvedCommentsWarning(change2),
- 'Heads Up! 2 unresolved comments.');
- });
-
- test('_computeHasChangeEdit', () => {
- const change = {
- revisions: {
- d442ff05d6c4f2a3af0eeca1f67374b39f9dc3d8: {
- _number: 'edit',
- },
- },
- unresolved_comment_count: 0,
- };
-
- assert.equal(element._computeHasChangeEdit(change), true);
-
- const change2 = {
- revisions: {
- d442ff05d6c4f2a3af0eeca1f67374b39f9dc3d8: {
- _number: 2,
- },
- },
- };
- assert.equal(element._computeHasChangeEdit(change2), false);
- });
-});
-
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.ts
new file mode 100644
index 0000000..e9f3019
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.ts
@@ -0,0 +1,89 @@
+/**
+ * @license
+ * Copyright (C) 2018 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.
+ */
+
+import '../../../test/common-test-setup-karma';
+import {createChange, createRevision} from '../../../test/test-data-generators';
+import {queryAndAssert} from '../../../test/test-utils';
+import {PatchSetNum} from '../../../types/common';
+import {GrConfirmSubmitDialog} from './gr-confirm-submit-dialog';
+
+const basicFixture = fixtureFromElement('gr-confirm-submit-dialog');
+
+suite('gr-confirm-submit-dialog tests', () => {
+ let element: GrConfirmSubmitDialog;
+
+ setup(() => {
+ element = basicFixture.instantiate();
+ element._initialised = true;
+ });
+
+ test('display', () => {
+ element.action = {label: 'my-label'};
+ element.change = {
+ ...createChange(),
+ subject: 'my-subject',
+ revisions: {},
+ };
+ flush();
+ const header = queryAndAssert(element, '.header');
+ assert.equal(header.textContent!.trim(), 'my-label');
+
+ const message = queryAndAssert(element, '.main p');
+ assert.isNotEmpty(message.textContent);
+ assert.notEqual(message.textContent!.indexOf('my-subject'), -1);
+ });
+
+ test('_computeUnresolvedCommentsWarning', () => {
+ const change = {...createChange(), unresolved_comment_count: 1};
+ assert.equal(
+ element._computeUnresolvedCommentsWarning(change),
+ 'Heads Up! 1 unresolved comment.'
+ );
+
+ const change2 = {...createChange(), unresolved_comment_count: 2};
+ assert.equal(
+ element._computeUnresolvedCommentsWarning(change2),
+ 'Heads Up! 2 unresolved comments.'
+ );
+ });
+
+ test('_computeHasChangeEdit', () => {
+ const change = {
+ ...createChange(),
+ revisions: {
+ d442ff05d6c4f2a3af0eeca1f67374b39f9dc3d8: {
+ ...createRevision(),
+ _number: 'edit' as PatchSetNum,
+ },
+ },
+ unresolved_comment_count: 0,
+ };
+
+ assert.isTrue(element._computeHasChangeEdit(change));
+
+ const change2 = {
+ ...createChange(),
+ revisions: {
+ d442ff05d6c4f2a3af0eeca1f67374b39f9dc3d8: {
+ ...createRevision(),
+ _number: 2 as PatchSetNum,
+ },
+ },
+ };
+ assert.isFalse(element._computeHasChangeEdit(change2));
+ });
+});
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
index ab1e4e6..0aae46c 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
@@ -16,7 +16,6 @@
*/
import '../../../styles/shared-styles';
import '../../shared/gr-download-commands/gr-download-commands';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-download-dialog_html';
@@ -38,9 +37,7 @@
}
@customElement('gr-download-dialog')
-export class GrDownloadDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDownloadDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index 146b3e2..6726055 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -25,7 +25,6 @@
import '../../shared/gr-icons/gr-icons';
import '../gr-commit-info/gr-commit-info';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-file-list-header_html';
@@ -46,6 +45,7 @@
ServerInfo,
RevisionInfo,
NumericChangeId,
+ BasePatchSetNum,
} from '../../../types/common';
import {DiffPreferencesInfo} from '../../../types/diff';
import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
@@ -75,7 +75,7 @@
@customElement('gr-file-list-header')
export class GrFileListHeader extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -151,7 +151,7 @@
patchNum?: PatchSetNum;
@property({type: String})
- basePatchNum?: PatchSetNum;
+ basePatchNum?: BasePatchSetNum;
@property({type: String})
filesExpanded?: FilesExpandedState;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index c2947a6..91e0c7e 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -28,7 +28,6 @@
import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
import '../../shared/gr-file-status-chip/gr-file-status-chip';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-file-list_html';
@@ -171,7 +170,7 @@
@customElement('gr-file-list')
export class GrFileList extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -356,8 +355,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
getPluginLoader()
.awaitPluginsLoaded()
.then(() => {
@@ -405,10 +404,11 @@
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
+ this.$.fileCursor.unsetCursor();
this._cancelDiffs();
this.cancelDebouncer(DEBOUNCER_LOADING_CHANGE);
+ super.disconnectedCallback();
}
/**
@@ -1778,7 +1778,7 @@
*/
_reportRenderedRow(index: number) {
if (index === this._shownFiles.length - 1) {
- this.async(() => {
+ setTimeout(() => {
this.reporting.timeEndWithAverage(
RENDER_TIMING_LABEL,
RENDER_AVG_TIMING_LABEL,
@@ -1816,6 +1816,16 @@
_computeTruncatedPath(path: string) {
return computeTruncatedPath(path);
}
+
+ _getOldPath(file: NormalizedFileInfo) {
+ // The gr-endpoint-decorator is waiting until all gr-endpoint-param
+ // values are updated.
+ // The old_path property is undefined for added files, and the
+ // gr-endpoint-param value bound to file.old_path is never updates.
+ // As a results, the gr-endpoint-decorator doesn't work for added files.
+ // As a workaround, this method returns null instead of undefined.
+ return file.old_path ?? null;
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
index c57a2d5..2078d1a 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
@@ -375,6 +375,8 @@
</gr-endpoint-param>
<gr-endpoint-param name="path" value="[[file.__path]]">
</gr-endpoint-param>
+ <gr-endpoint-param name="oldPath" value="[[_getOldPath(file)]]">
+ </gr-endpoint-param>
</gr-endpoint-decorator>
</template>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
index 80d8729..2eee60e 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
@@ -16,7 +16,6 @@
*/
import '../../../test/common-test-setup-karma.js';
-import {listenOnce} from '../../../test/test-utils.js';
import '../../diff/gr-comment-api/gr-comment-api.js';
import {getMockDiffResponse} from '../../../test/mocks/diff-response.js';
import './gr-file-list.js';
@@ -26,7 +25,7 @@
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {runA11yAudit} from '../../../test/a11y-test-utils.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-import {TestKeyboardShortcutBinder, stubRestApi, spyRestApi} from '../../../test/test-utils.js';
+import {TestKeyboardShortcutBinder, stubRestApi, spyRestApi, listenOnce} from '../../../test/test-utils.js';
import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
import {createCommentThreads} from '../../../utils/comment-util.js';
import {createChangeComments} from '../../../test/test-data-generators.js';
@@ -80,7 +79,6 @@
suite('basic tests', () => {
setup(done => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(true));
stubRestApi('getPreferences').returns(Promise.resolve({}));
stubRestApi('getDiffComments').returns(Promise.resolve({}));
stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
index b50bff4..55c505a 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
@@ -17,7 +17,6 @@
import '@polymer/iron-input/iron-input';
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-included-in-dialog_html';
@@ -31,9 +30,7 @@
}
@customElement('gr-included-in-dialog')
-export class GrIncludedInDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrIncludedInDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts
index 60a6058..7ea2075 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts
@@ -18,7 +18,6 @@
import '../../shared/gr-button/gr-button';
import '../../../styles/gr-voting-styles';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-label-score-row_html';
@@ -56,9 +55,7 @@
}
@customElement('gr-label-score-row')
-export class GrLabelScoreRow extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrLabelScoreRow extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
index 661cd1a..0258fc9 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
@@ -16,7 +16,6 @@
*/
import '../gr-label-score-row/gr-label-score-row';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-label-scores_html';
@@ -38,11 +37,10 @@
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
import {appContext} from '../../../services/app-context';
import {labelCompare} from '../../../utils/label-util';
+import {Execution} from '../../../constants/reporting';
@customElement('gr-label-scores')
-export class GrLabelScores extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrLabelScores extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -104,7 +102,10 @@
}
}
const stringVal = `${numberValue}`;
- this.reporting.reportExecution('label-value-not-found', {value: stringVal});
+ this.reporting.reportExecution(Execution.REACHABLE_CODE, {
+ value: stringVal,
+ id: 'label-value-not-found',
+ });
return stringVal;
}
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index f913459..28ce47e 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -23,7 +23,6 @@
import '../../shared/gr-formatted-text/gr-formatted-text';
import '../../../styles/shared-styles';
import '../../../styles/gr-voting-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-message_html';
@@ -41,6 +40,7 @@
ChangeMessageId,
PatchSetNum,
AccountInfo,
+ BasePatchSetNum,
} from '../../../types/common';
import {CommentThread} from '../../../utils/comment-util';
import {hasOwnProperty} from '../../../utils/common-util';
@@ -69,7 +69,7 @@
id: ChangeMessageId;
}
-interface ChangeMessage extends ChangeMessageInfo {
+export interface ChangeMessage extends ChangeMessageInfo {
// TODO(TS): maybe should be an enum instead
type: string;
expanded: boolean;
@@ -84,9 +84,7 @@
}
@customElement('gr-message')
-export class GrMessage extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrMessage extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -204,8 +202,8 @@
this.addEventListener('click', e => this._handleClick(e));
}
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.restApiService.getConfig().then(config => {
this.config = config;
});
@@ -306,7 +304,7 @@
const match = this.message.message.match(MERGED_PATCHSET_PATTERN)!;
if (isNaN(Number(match[1])))
throw new Error('invalid patchnum in message');
- basePatchNum = Number(match[1]) as PatchSetNum;
+ basePatchNum = Number(match[1]) as BasePatchSetNum;
patchNum = computeLatestPatchNum(computeAllPatchSets(this.change))!;
} else {
// Message is of the form "Commit Message was updated" or "Patchset X
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
index 8d03e32..45f4406 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
@@ -61,7 +61,7 @@
font-weight: var(--font-weight-bold);
}
.message {
- --gr-formatted-text-prose-max-width: 80ch;
+ --gr-formatted-text-prose-max-width: 120ch;
}
.collapsed .message {
max-width: none;
@@ -288,7 +288,8 @@
items="[[update.reviewers]]"
as="reviewer"
>
- <gr-account-chip account="[[reviewer]]"> </gr-account-chip>
+ <gr-account-chip account="[[reviewer]]" change="[[change]]">
+ </gr-account-chip>
</template>
</div>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js
deleted file mode 100644
index 3f4e13a..0000000
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js
+++ /dev/null
@@ -1,577 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 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.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-message.js';
-import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {createChange, createRevisions} from '../../../test/test-data-generators.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-
-const basicFixture = fixtureFromElement('gr-message');
-
-suite('gr-message tests', () => {
- let element;
-
- suite('when admin and logged in', () => {
- setup(done => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(true));
- stubRestApi('getPreferences').returns(Promise.resolve({}));
- stubRestApi('getConfig').returns(Promise.resolve({}));
- stubRestApi('getIsAdmin').returns(Promise.resolve(true));
- stubRestApi('deleteChangeCommitMessage').returns(Promise.resolve({}));
- element = basicFixture.instantiate();
- flush(done);
- });
-
- test('reply event', done => {
- element.message = {
- id: '47c43261_55aa2c41',
- author: {
- _account_id: 1115495,
- name: 'Andrew Bonventre',
- email: 'andybons@chromium.org',
- },
- date: '2016-01-12 20:24:49.448000000',
- message: 'Uploaded patch set 1.',
- _revision_number: 1,
- expanded: true,
- };
-
- element.addEventListener('reply', e => {
- assert.deepEqual(e.detail.message, element.message);
- done();
- });
- flush();
- assert.isFalse(
- element.shadowRoot.querySelector('.replyActionContainer').hidden
- );
- MockInteractions.tap(element.shadowRoot.querySelector('.replyBtn'));
- });
-
- test('can see delete button', () => {
- element.message = {
- id: '47c43261_55aa2c41',
- author: {
- _account_id: 1115495,
- name: 'Andrew Bonventre',
- email: 'andybons@chromium.org',
- },
- date: '2016-01-12 20:24:49.448000000',
- message: 'Uploaded patch set 1.',
- _revision_number: 1,
- expanded: true,
- };
-
- flush();
- assert.isFalse(element.shadowRoot.querySelector('.deleteBtn').hidden);
- });
-
- test('delete change message', done => {
- element.changeNum = 314159;
- element.message = {
- id: '47c43261_55aa2c41',
- author: {
- _account_id: 1115495,
- name: 'Andrew Bonventre',
- email: 'andybons@chromium.org',
- },
- date: '2016-01-12 20:24:49.448000000',
- message: 'Uploaded patch set 1.',
- _revision_number: 1,
- expanded: true,
- };
-
- element.addEventListener('change-message-deleted', e => {
- assert.deepEqual(e.detail.message, element.message);
- assert.isFalse(element.shadowRoot.querySelector('.deleteBtn').disabled);
- done();
- });
- flush();
- MockInteractions.tap(element.shadowRoot.querySelector('.deleteBtn'));
- assert.isTrue(element.shadowRoot.querySelector('.deleteBtn').disabled);
- });
-
- test('autogenerated prefix hiding', () => {
- element.message = {
- tag: 'autogenerated:gerrit:test',
- updated: '2016-01-12 20:24:49.448000000',
- expanded: false,
- };
-
- assert.isTrue(element.isAutomated);
- assert.isFalse(element.hidden);
-
- element.hideAutomated = true;
-
- assert.isTrue(element.hidden);
- });
-
- test('reviewer message treated as autogenerated', () => {
- element.message = {
- tag: 'autogenerated:gerrit:test',
- updated: '2016-01-12 20:24:49.448000000',
- reviewer: {},
- expanded: false,
- };
-
- assert.isTrue(element.isAutomated);
- assert.isFalse(element.hidden);
-
- element.hideAutomated = true;
-
- assert.isTrue(element.hidden);
- });
-
- test('batch reviewer message treated as autogenerated', () => {
- element.message = {
- type: 'REVIEWER_UPDATE',
- updated: '2016-01-12 20:24:49.448000000',
- reviewer: {},
- expanded: false,
- };
-
- assert.isTrue(element.isAutomated);
- assert.isFalse(element.hidden);
-
- element.hideAutomated = true;
-
- assert.isTrue(element.hidden);
- });
-
- test('tag that is not autogenerated prefix does not hide', () => {
- element.message = {
- tag: 'something',
- updated: '2016-01-12 20:24:49.448000000',
- expanded: false,
- };
-
- assert.isFalse(element.isAutomated);
- assert.isFalse(element.hidden);
-
- element.hideAutomated = true;
-
- assert.isFalse(element.hidden);
- });
-
- test('reply button hidden unless logged in', () => {
- const message = {
- message: 'Uploaded patch set 1.',
- expanded: false,
- };
- assert.isFalse(element._computeShowReplyButton(message, false));
- assert.isTrue(element._computeShowReplyButton(message, true));
- });
-
- test('_computeShowOnBehalfOf', () => {
- const message = {
- message: '...',
- expanded: false,
- };
- assert.isNotOk(element._computeShowOnBehalfOf(message));
- message.author = {_account_id: 1115495};
- assert.isNotOk(element._computeShowOnBehalfOf(message));
- message.real_author = {_account_id: 1115495};
- assert.isNotOk(element._computeShowOnBehalfOf(message));
- message.real_author._account_id = 123456;
- assert.isOk(element._computeShowOnBehalfOf(message));
- message.updated_by = message.author;
- delete message.author;
- assert.isOk(element._computeShowOnBehalfOf(message));
- delete message.updated_by;
- assert.isNotOk(element._computeShowOnBehalfOf(message));
- });
-
- ['Trybot-Ready', 'Tryjob-Request', 'Commit-Queue'].forEach(label => {
- test(`${label} ignored for color voting`, () => {
- element.message = {
- author: {},
- expanded: false,
- message: `Patch Set 1: ${label}+1`,
- };
- assert.isNotOk(
- element.root.querySelector('.negativeVote'));
- assert.isNotOk(
- element.root.querySelector('.positiveVote'));
- });
- });
-
- test('clicking on date link fires event', () => {
- element.message = {
- type: 'REVIEWER_UPDATE',
- updated: '2016-01-12 20:24:49.448000000',
- reviewer: {},
- id: '47c43261_55aa2c41',
- expanded: false,
- };
- flush();
- const stub = sinon.stub();
- element.addEventListener('message-anchor-tap', stub);
- const dateEl = element.shadowRoot
- .querySelector('.date');
- assert.ok(dateEl);
- MockInteractions.tap(dateEl);
-
- assert.isTrue(stub.called);
- assert.deepEqual(stub.lastCall.args[0].detail, {id: element.message.id});
- });
-
- suite('uploaded patchset X message navigates to X - 1 vs X', () => {
- let navStub;
- setup(() => {
- element.change = {...createChange(), revisions: createRevisions(4)};
- navStub = sinon.stub(GerritNav, 'navigateToChange');
- });
-
- test('Patchset 1 navigates to Base', () => {
- element.message = {
- message: 'Uploaded patch set 1.',
- };
- element._handleViewPatchsetDiff(new MouseEvent('click'));
- assert.isTrue(navStub.calledWithExactly(element.change, 1,
- 'PARENT'));
- });
-
- test('Patchset X navigates to X vs X - 1', () => {
- element.message = {
- message: 'Uploaded patch set 2.',
- };
- element._handleViewPatchsetDiff(new MouseEvent('click'));
- assert.isTrue(navStub.calledWithExactly(element.change, 2, 1));
-
- element.message = {
- message: 'Uploaded patch set 200.',
- };
- element._handleViewPatchsetDiff(new MouseEvent('click'));
- assert.isTrue(navStub.calledWithExactly(element.change, 200, 199));
- });
-
- test('Commit message updated', () => {
- element.message = {
- message: 'Commit message updated.',
- };
- element._handleViewPatchsetDiff(new MouseEvent('click'));
- assert.isTrue(navStub.calledWithExactly(element.change, 4, 3));
- });
-
- test('Merged patchset change message', () => {
- element.message = {
- message: 'abcd↵3 is the latest approved patch-set.↵abc',
- };
- element._handleViewPatchsetDiff(new MouseEvent('click'));
- assert.isTrue(navStub.calledWithExactly(element.change, 4, 3));
- });
- });
-
- suite('compute messages', () => {
- test('empty', () => {
- assert.equal(element._computeMessageContent(true, '', ''), '');
- assert.equal(element._computeMessageContent(false, '', ''), '');
- });
-
- test('new patchset', () => {
- const original = 'Uploaded patch set 1.';
- const tag = 'autogenerated:gerrit:newPatchSet';
- let actual = element._computeMessageContent(true, original, tag);
- assert.equal(actual, element._computeMessageContentCollapsed(
- original, tag, []));
- assert.equal(actual, original);
- actual = element._computeMessageContent(false, original, tag);
- assert.equal(actual, original);
- });
-
- test('new patchset rebased', () => {
- const original = 'Patch Set 27: Patch Set 26 was rebased';
- const tag = 'autogenerated:gerrit:newPatchSet';
- const expected = 'Patch Set 26 was rebased';
- let actual = element._computeMessageContent(true, original, tag);
- assert.equal(actual, expected);
- assert.equal(actual, element._computeMessageContentCollapsed(
- original, tag, []));
- actual = element._computeMessageContent(false, original, tag);
- assert.equal(actual, expected);
- });
-
- test('ready for review', () => {
- const original = 'Patch Set 1:\n\nThis change is ready for review.';
- const tag = undefined;
- const expected = 'This change is ready for review.';
- let actual = element._computeMessageContent(true, original, tag);
- assert.equal(actual, expected);
- assert.equal(actual, element._computeMessageContentCollapsed(
- original, tag, []));
- actual = element._computeMessageContent(false, original, tag);
- assert.equal(actual, expected);
- });
-
- test('vote', () => {
- const original = 'Patch Set 1: Code-Style+1';
- const tag = undefined;
- const expected = '';
- let actual = element._computeMessageContent(true, original, tag);
- assert.equal(actual, expected);
- actual = element._computeMessageContent(false, original, tag);
- assert.equal(actual, expected);
- });
-
- test('comments', () => {
- const original = 'Patch Set 1:\n\n(3 comments)';
- const tag = undefined;
- const expected = '';
- let actual = element._computeMessageContent(true, original, tag);
- assert.equal(actual, expected);
- actual = element._computeMessageContent(false, original, tag);
- assert.equal(actual, expected);
- });
- });
-
- test('votes', () => {
- element.message = {
- author: {},
- expanded: false,
- message: 'Patch Set 1: Verified+1 Code-Review-2 Trybot-Label3+1 Blub+1',
- };
- element.labelExtremes = {
- 'Verified': {max: 1, min: -1},
- 'Code-Review': {max: 2, min: -2},
- 'Trybot-Label3': {max: 3, min: 0},
- };
- flush();
- const scoreChips = element.root.querySelectorAll('.score');
- assert.equal(scoreChips.length, 3);
-
- assert.isTrue(scoreChips[0].classList.contains('positive'));
- assert.isTrue(scoreChips[0].classList.contains('max'));
-
- assert.isTrue(scoreChips[1].classList.contains('negative'));
- assert.isTrue(scoreChips[1].classList.contains('min'));
-
- assert.isTrue(scoreChips[2].classList.contains('positive'));
- assert.isFalse(scoreChips[2].classList.contains('min'));
- });
-
- test('Uploaded patch set X', () => {
- element.message = {
- author: {},
- expanded: false,
- message: 'Uploaded patch set 1:' +
- 'Verified+1 Code-Review-2 Trybot-Label3+1 Blub+1',
- };
- element.labelExtremes = {
- 'Verified': {max: 1, min: -1},
- 'Code-Review': {max: 2, min: -2},
- 'Trybot-Label3': {max: 3, min: 0},
- };
- flush();
- const scoreChips = element.root.querySelectorAll('.score');
- assert.equal(scoreChips.length, 3);
-
- assert.isTrue(scoreChips[0].classList.contains('positive'));
- assert.isTrue(scoreChips[0].classList.contains('max'));
-
- assert.isTrue(scoreChips[1].classList.contains('negative'));
- assert.isTrue(scoreChips[1].classList.contains('min'));
-
- assert.isTrue(scoreChips[2].classList.contains('positive'));
- assert.isFalse(scoreChips[2].classList.contains('min'));
- });
-
- test('removed votes', () => {
- element.message = {
- author: {},
- expanded: false,
- message: 'Patch Set 1: Verified+1 -Code-Review -Commit-Queue',
- };
- element.labelExtremes = {
- 'Verified': {max: 1, min: -1},
- 'Code-Review': {max: 2, min: -2},
- 'Commit-Queue': {max: 3, min: 0},
- };
- flush();
- const scoreChips = element.root.querySelectorAll('.score');
- assert.equal(scoreChips.length, 3);
-
- assert.isTrue(scoreChips[1].classList.contains('removed'));
- assert.isTrue(scoreChips[2].classList.contains('removed'));
- });
-
- test('false negative vote', () => {
- element.message = {
- author: {},
- expanded: false,
- message: 'Patch Set 1: Cherry Picked from branch stable-2.14.',
- };
- element.labelExtremes = {};
- const scoreChips = element.root.querySelectorAll('.score');
- assert.equal(scoreChips.length, 0);
- });
- });
-
- suite('when not logged in', () => {
- setup(done => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(false));
- stubRestApi('getPreferences').returns(Promise.resolve({}));
- stubRestApi('getConfig').returns(Promise.resolve({}));
- stubRestApi('getIsAdmin').returns(Promise.resolve(false));
- stubRestApi('deleteChangeCommitMessage').returns(Promise.resolve({}));
- element = basicFixture.instantiate();
- flush(done);
- });
-
- test('reply and delete button should be hidden', () => {
- element.message = {
- id: '47c43261_55aa2c41',
- author: {
- _account_id: 1115495,
- name: 'Andrew Bonventre',
- email: 'andybons@chromium.org',
- },
- date: '2016-01-12 20:24:49.448000000',
- message: 'Uploaded patch set 1.',
- _revision_number: 1,
- expanded: true,
- };
-
- flush();
- assert.isTrue(
- element.shadowRoot.querySelector('.replyActionContainer').hidden
- );
- assert.isTrue(
- element.shadowRoot.querySelector('.deleteBtn').hidden
- );
- });
- });
-
- suite('patchset comment summary', () => {
- setup(() => {
- element = basicFixture.instantiate();
- element.message = {id: '6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3'};
- });
-
- test('single patchset comment posted', () => {
- const threads = [{
- comments: [{
- change_message_id: '6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3',
- patch_set: 1,
- id: 'e365b138_bed65caa',
- updated: '2020-05-15 13:35:56.000000000',
- message: 'testing the load',
- unresolved: false,
- path: '/PATCHSET_LEVEL',
- collapsed: false,
- }],
- patchNum: 1,
- path: '/PATCHSET_LEVEL',
- rootId: 'e365b138_bed65caa',
- }];
- assert.equal(element._computeMessageContentCollapsed(
- '', undefined, threads), 'testing the load');
- assert.equal(element._computeMessageContent(false, '', undefined), '');
- });
-
- test('single patchset comment with reply', () => {
- const threads = [{
- comments: [{
- patch_set: 1,
- id: 'e365b138_bed65caa',
- updated: '2020-05-15 13:35:56.000000000',
- message: 'testing the load',
- unresolved: false,
- path: '/PATCHSET_LEVEL',
- collapsed: false,
- }, {
- change_message_id: '6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3',
- patch_set: 1,
- id: 'd6efcc85_4cbbb6f4',
- in_reply_to: 'e365b138_bed65caa',
- updated: '2020-05-15 16:55:28.000000000',
- message: 'n',
- unresolved: false,
- path: '/PATCHSET_LEVEL',
- __draft: true,
- collapsed: true,
- }],
- patchNum: 1,
- path: '/PATCHSET_LEVEL',
- rootId: 'e365b138_bed65caa',
- }];
- assert.equal(element._computeMessageContentCollapsed(
- '', undefined, threads), 'n');
- assert.equal(element._computeMessageContent(false, '', undefined), '');
- });
- });
-
- suite('when logged in but not admin', () => {
- setup(async () => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(true));
- stubRestApi('getConfig').returns(Promise.resolve({}));
- stubRestApi('getIsAdmin').returns(Promise.resolve(false));
- stubRestApi('deleteChangeCommitMessage').returns(Promise.resolve({}));
- element = basicFixture.instantiate();
- await flush();
- });
-
- test('can see reply but not delete button', () => {
- element.message = {
- id: '47c43261_55aa2c41',
- author: {
- _account_id: 1115495,
- name: 'Andrew Bonventre',
- email: 'andybons@chromium.org',
- },
- date: '2016-01-12 20:24:49.448000000',
- message: 'Uploaded patch set 1.',
- _revision_number: 1,
- expanded: true,
- };
-
- flush();
- assert.isFalse(
- element.shadowRoot.querySelector('.replyActionContainer').hidden
- );
- assert.isTrue(
- element.shadowRoot.querySelector('.deleteBtn').hidden
- );
- });
-
- test('reply button shown when message is updated', () => {
- element.message = undefined;
- flush();
- let replyEl = element.shadowRoot.querySelector('.replyActionContainer');
- // We don't even expect the button to show up in the DOM when the message
- // is undefined.
- assert.isNotOk(replyEl);
-
- element.message = {
- id: '47c43261_55aa2c41',
- author: {
- _account_id: 1115495,
- name: 'Andrew Bonventre',
- email: 'andybons@chromium.org',
- },
- date: '2016-01-12 20:24:49.448000000',
- message: 'not empty',
- _revision_number: 1,
- expanded: true,
- };
- flush();
- replyEl = element.shadowRoot.querySelector('.replyActionContainer');
- assert.isOk(replyEl);
- assert.isFalse(replyEl.hidden);
- });
- });
-});
-
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
new file mode 100644
index 0000000..3652be9
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
@@ -0,0 +1,676 @@
+/**
+ * @license
+ * Copyright (C) 2016 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.
+ */
+
+import '../../../test/common-test-setup-karma.js';
+import './gr-message';
+import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import {
+ createChange,
+ createChangeMessage,
+ createComment,
+ createRevisions,
+} from '../../../test/test-data-generators';
+import {
+ query,
+ queryAll,
+ queryAndAssert,
+ stubRestApi,
+} from '../../../test/test-utils';
+import {GrMessage} from './gr-message';
+import {
+ AccountId,
+ BasePatchSetNum,
+ ChangeMessageId,
+ EmailAddress,
+ NumericChangeId,
+ PatchSetNum,
+ ReviewInputTag,
+ Timestamp,
+ UrlEncodedCommentId,
+} from '../../../types/common.js';
+import {tap} from '@polymer/iron-test-helpers/mock-interactions';
+import {
+ ChangeMessageDeletedEventDetail,
+ ReplyEventDetail,
+} from '../../../types/events.js';
+import {GrButton} from '../../shared/gr-button/gr-button.js';
+import {CommentSide} from '../../../constants/constants.js';
+import {SinonStubbedMember} from 'sinon/pkg/sinon-esm';
+
+const basicFixture = fixtureFromElement('gr-message');
+
+suite('gr-message tests', () => {
+ let element: GrMessage;
+
+ suite('when admin and logged in', () => {
+ setup(done => {
+ stubRestApi('getIsAdmin').returns(Promise.resolve(true));
+ element = basicFixture.instantiate();
+ flush(done);
+ });
+
+ test('reply event', done => {
+ element.message = {
+ ...createChangeMessage(),
+ id: '47c43261_55aa2c41' as ChangeMessageId,
+ author: {
+ _account_id: 1115495 as AccountId,
+ name: 'Andrew Bonventre',
+ email: 'andybons@chromium.org' as EmailAddress,
+ },
+ date: '2016-01-12 20:24:49.448000000' as Timestamp,
+ message: 'Uploaded patch set 1.',
+ _revision_number: 1 as PatchSetNum,
+ expanded: true,
+ };
+
+ element.addEventListener('reply', (e: CustomEvent<ReplyEventDetail>) => {
+ assert.deepEqual(e.detail.message, element.message);
+ done();
+ });
+ flush();
+ assert.isFalse(
+ queryAndAssert<HTMLElement>(element, '.replyActionContainer').hidden
+ );
+ tap(queryAndAssert(element, '.replyBtn'));
+ });
+
+ test('can see delete button', () => {
+ element.message = {
+ ...createChangeMessage(),
+ id: '47c43261_55aa2c41' as ChangeMessageId,
+ author: {
+ _account_id: 1115495 as AccountId,
+ name: 'Andrew Bonventre',
+ email: 'andybons@chromium.org' as EmailAddress,
+ },
+ date: '2016-01-12 20:24:49.448000000' as Timestamp,
+ message: 'Uploaded patch set 1.',
+ _revision_number: 1 as PatchSetNum,
+ expanded: true,
+ };
+
+ flush();
+ assert.isFalse(queryAndAssert<HTMLElement>(element, '.deleteBtn').hidden);
+ });
+
+ test('delete change message', done => {
+ element.changeNum = 314159 as NumericChangeId;
+ element.message = {
+ ...createChangeMessage(),
+ id: '47c43261_55aa2c41' as ChangeMessageId,
+ author: {
+ _account_id: 1115495 as AccountId,
+ name: 'Andrew Bonventre',
+ email: 'andybons@chromium.org' as EmailAddress,
+ },
+ date: '2016-01-12 20:24:49.448000000' as Timestamp,
+ message: 'Uploaded patch set 1.',
+ _revision_number: 1 as PatchSetNum,
+ expanded: true,
+ };
+
+ element.addEventListener(
+ 'change-message-deleted',
+ (e: CustomEvent<ChangeMessageDeletedEventDetail>) => {
+ assert.deepEqual(e.detail.message, element.message);
+ assert.isFalse(
+ (queryAndAssert(element, '.deleteBtn') as GrButton).disabled
+ );
+ done();
+ }
+ );
+ flush();
+ tap(queryAndAssert(element, '.deleteBtn'));
+ assert.isTrue(
+ (queryAndAssert(element, '.deleteBtn') as GrButton).disabled
+ );
+ });
+
+ test('autogenerated prefix hiding', () => {
+ element.message = {
+ ...createChangeMessage(),
+ tag: 'autogenerated:gerrit:test' as ReviewInputTag,
+ expanded: false,
+ };
+
+ assert.isTrue(element.isAutomated);
+ assert.isFalse(element.hidden);
+
+ element.hideAutomated = true;
+
+ assert.isTrue(element.hidden);
+ });
+
+ test('reviewer message treated as autogenerated', () => {
+ element.message = {
+ ...createChangeMessage(),
+ tag: 'autogenerated:gerrit:test' as ReviewInputTag,
+ reviewer: {},
+ expanded: false,
+ };
+
+ assert.isTrue(element.isAutomated);
+ assert.isFalse(element.hidden);
+
+ element.hideAutomated = true;
+
+ assert.isTrue(element.hidden);
+ });
+
+ test('batch reviewer message treated as autogenerated', () => {
+ element.message = {
+ ...createChangeMessage(),
+ type: 'REVIEWER_UPDATE',
+ reviewer: {},
+ expanded: false,
+ };
+
+ assert.isTrue(element.isAutomated);
+ assert.isFalse(element.hidden);
+
+ element.hideAutomated = true;
+
+ assert.isTrue(element.hidden);
+ });
+
+ test('tag that is not autogenerated prefix does not hide', () => {
+ element.message = {
+ ...createChangeMessage(),
+ tag: 'something' as ReviewInputTag,
+ expanded: false,
+ };
+
+ assert.isFalse(element.isAutomated);
+ assert.isFalse(element.hidden);
+
+ element.hideAutomated = true;
+
+ assert.isFalse(element.hidden);
+ });
+
+ test('reply button hidden unless logged in', () => {
+ const message = {
+ ...createChangeMessage(),
+ message: 'Uploaded patch set 1.',
+ expanded: false,
+ };
+ assert.isFalse(element._computeShowReplyButton(message, false));
+ assert.isTrue(element._computeShowReplyButton(message, true));
+ });
+
+ test('_computeShowOnBehalfOf', () => {
+ const message = {
+ ...createChangeMessage(),
+ message: '...',
+ expanded: false,
+ };
+ assert.isNotOk(element._computeShowOnBehalfOf(message));
+ message.author = {_account_id: 1115495 as AccountId};
+ assert.isNotOk(element._computeShowOnBehalfOf(message));
+ message.real_author = {_account_id: 1115495 as AccountId};
+ assert.isNotOk(element._computeShowOnBehalfOf(message));
+ message.real_author._account_id = 123456 as AccountId;
+ assert.isOk(element._computeShowOnBehalfOf(message));
+ message.updated_by = message.author;
+ delete message.author;
+ assert.isOk(element._computeShowOnBehalfOf(message));
+ delete message.updated_by;
+ assert.isNotOk(element._computeShowOnBehalfOf(message));
+ });
+
+ ['Trybot-Ready', 'Tryjob-Request', 'Commit-Queue'].forEach(label => {
+ test(`${label} ignored for color voting`, () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message: `Patch Set 1: ${label}+1`,
+ };
+ assert.isNotOk(query(element, '.negativeVote'));
+ assert.isNotOk(query(element, '.positiveVote'));
+ });
+ });
+
+ test('clicking on date link fires event', () => {
+ element.message = {
+ ...createChangeMessage(),
+ type: 'REVIEWER_UPDATE',
+ reviewer: {},
+ id: '47c43261_55aa2c41' as ChangeMessageId,
+ expanded: false,
+ };
+ flush();
+ const stub = sinon.stub();
+ element.addEventListener('message-anchor-tap', stub);
+ const dateEl = queryAndAssert(element, '.date');
+ assert.ok(dateEl);
+ tap(dateEl);
+
+ assert.isTrue(stub.called);
+ assert.deepEqual(stub.lastCall.args[0].detail, {id: element.message.id});
+ });
+
+ suite('uploaded patchset X message navigates to X - 1 vs X', () => {
+ let navStub: SinonStubbedMember<typeof GerritNav.navigateToChange>;
+ setup(() => {
+ element.change = {...createChange(), revisions: createRevisions(4)};
+ navStub = sinon.stub(GerritNav, 'navigateToChange');
+ });
+
+ test('Patchset 1 navigates to Base', () => {
+ element.message = {
+ ...createChangeMessage(),
+ message: 'Uploaded patch set 1.',
+ };
+ element._handleViewPatchsetDiff(new MouseEvent('click'));
+ assert.isTrue(
+ navStub.calledWithExactly(
+ element.change!,
+ 1 as PatchSetNum,
+ 'PARENT' as BasePatchSetNum
+ )
+ );
+ });
+
+ test('Patchset X navigates to X vs X - 1', () => {
+ element.message = {
+ ...createChangeMessage(),
+ message: 'Uploaded patch set 2.',
+ };
+ element._handleViewPatchsetDiff(new MouseEvent('click'));
+ assert.isTrue(
+ navStub.calledWithExactly(
+ element.change!,
+ 2 as PatchSetNum,
+ 1 as BasePatchSetNum
+ )
+ );
+
+ element.message = {
+ ...createChangeMessage(),
+ message: 'Uploaded patch set 200.',
+ };
+ element._handleViewPatchsetDiff(new MouseEvent('click'));
+ assert.isTrue(
+ navStub.calledWithExactly(
+ element.change!,
+ 200 as PatchSetNum,
+ 199 as BasePatchSetNum
+ )
+ );
+ });
+
+ test('Commit message updated', () => {
+ element.message = {
+ ...createChangeMessage(),
+ message: 'Commit message updated.',
+ };
+ element._handleViewPatchsetDiff(new MouseEvent('click'));
+ assert.isTrue(
+ navStub.calledWithExactly(
+ element.change!,
+ 4 as PatchSetNum,
+ 3 as BasePatchSetNum
+ )
+ );
+ });
+
+ test('Merged patchset change message', () => {
+ element.message = {
+ ...createChangeMessage(),
+ message: 'abcd↵3 is the latest approved patch-set.↵abc',
+ };
+ element._handleViewPatchsetDiff(new MouseEvent('click'));
+ assert.isTrue(
+ navStub.calledWithExactly(
+ element.change!,
+ 4 as PatchSetNum,
+ 3 as BasePatchSetNum
+ )
+ );
+ });
+ });
+
+ suite('compute messages', () => {
+ test('empty', () => {
+ assert.equal(
+ element._computeMessageContent(true, '', '' as ReviewInputTag),
+ ''
+ );
+ assert.equal(
+ element._computeMessageContent(false, '', '' as ReviewInputTag),
+ ''
+ );
+ });
+
+ test('new patchset', () => {
+ const original = 'Uploaded patch set 1.';
+ const tag = 'autogenerated:gerrit:newPatchSet' as ReviewInputTag;
+ let actual = element._computeMessageContent(true, original, tag);
+ assert.equal(
+ actual,
+ element._computeMessageContentCollapsed(original, tag, [])
+ );
+ assert.equal(actual, original);
+ actual = element._computeMessageContent(false, original, tag);
+ assert.equal(actual, original);
+ });
+
+ test('new patchset rebased', () => {
+ const original = 'Patch Set 27: Patch Set 26 was rebased';
+ const tag = 'autogenerated:gerrit:newPatchSet' as ReviewInputTag;
+ const expected = 'Patch Set 26 was rebased';
+ let actual = element._computeMessageContent(true, original, tag);
+ assert.equal(actual, expected);
+ assert.equal(
+ actual,
+ element._computeMessageContentCollapsed(original, tag, [])
+ );
+ actual = element._computeMessageContent(false, original, tag);
+ assert.equal(actual, expected);
+ });
+
+ test('ready for review', () => {
+ const original = 'Patch Set 1:\n\nThis change is ready for review.';
+ const tag = undefined;
+ const expected = 'This change is ready for review.';
+ let actual = element._computeMessageContent(true, original, tag);
+ assert.equal(actual, expected);
+ assert.equal(
+ actual,
+ element._computeMessageContentCollapsed(original, tag, [])
+ );
+ actual = element._computeMessageContent(false, original, tag);
+ assert.equal(actual, expected);
+ });
+
+ test('vote', () => {
+ const original = 'Patch Set 1: Code-Style+1';
+ const tag = undefined;
+ const expected = '';
+ let actual = element._computeMessageContent(true, original, tag);
+ assert.equal(actual, expected);
+ actual = element._computeMessageContent(false, original, tag);
+ assert.equal(actual, expected);
+ });
+
+ test('comments', () => {
+ const original = 'Patch Set 1:\n\n(3 comments)';
+ const tag = undefined;
+ const expected = '';
+ let actual = element._computeMessageContent(true, original, tag);
+ assert.equal(actual, expected);
+ actual = element._computeMessageContent(false, original, tag);
+ assert.equal(actual, expected);
+ });
+ });
+
+ test('votes', () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message: 'Patch Set 1: Verified+1 Code-Review-2 Trybot-Label3+1 Blub+1',
+ };
+ element.labelExtremes = {
+ Verified: {max: 1, min: -1},
+ 'Code-Review': {max: 2, min: -2},
+ 'Trybot-Label3': {max: 3, min: 0},
+ };
+ flush();
+ const scoreChips = queryAll(element, '.score');
+ assert.equal(scoreChips.length, 3);
+
+ assert.isTrue(scoreChips[0].classList.contains('positive'));
+ assert.isTrue(scoreChips[0].classList.contains('max'));
+
+ assert.isTrue(scoreChips[1].classList.contains('negative'));
+ assert.isTrue(scoreChips[1].classList.contains('min'));
+
+ assert.isTrue(scoreChips[2].classList.contains('positive'));
+ assert.isFalse(scoreChips[2].classList.contains('min'));
+ });
+
+ test('Uploaded patch set X', () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message:
+ 'Uploaded patch set 1:' +
+ 'Verified+1 Code-Review-2 Trybot-Label3+1 Blub+1',
+ };
+ element.labelExtremes = {
+ Verified: {max: 1, min: -1},
+ 'Code-Review': {max: 2, min: -2},
+ 'Trybot-Label3': {max: 3, min: 0},
+ };
+ flush();
+ const scoreChips = queryAll(element, '.score');
+ assert.equal(scoreChips.length, 3);
+
+ assert.isTrue(scoreChips[0].classList.contains('positive'));
+ assert.isTrue(scoreChips[0].classList.contains('max'));
+
+ assert.isTrue(scoreChips[1].classList.contains('negative'));
+ assert.isTrue(scoreChips[1].classList.contains('min'));
+
+ assert.isTrue(scoreChips[2].classList.contains('positive'));
+ assert.isFalse(scoreChips[2].classList.contains('min'));
+ });
+
+ test('removed votes', () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message: 'Patch Set 1: Verified+1 -Code-Review -Commit-Queue',
+ };
+ element.labelExtremes = {
+ Verified: {max: 1, min: -1},
+ 'Code-Review': {max: 2, min: -2},
+ 'Commit-Queue': {max: 3, min: 0},
+ };
+ flush();
+ const scoreChips = queryAll(element, '.score');
+ assert.equal(scoreChips.length, 3);
+
+ assert.isTrue(scoreChips[1].classList.contains('removed'));
+ assert.isTrue(scoreChips[2].classList.contains('removed'));
+ });
+
+ test('false negative vote', () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message: 'Patch Set 1: Cherry Picked from branch stable-2.14.',
+ };
+ element.labelExtremes = {};
+ const scoreChips = element.root!.querySelectorAll('.score');
+ assert.equal(scoreChips.length, 0);
+ });
+ });
+
+ suite('when not logged in', () => {
+ setup(done => {
+ stubRestApi('getLoggedIn').returns(Promise.resolve(false));
+ stubRestApi('getIsAdmin').returns(Promise.resolve(false));
+ element = basicFixture.instantiate();
+ flush(done);
+ });
+
+ test('reply and delete button should be hidden', () => {
+ element.message = {
+ ...createChangeMessage(),
+ id: '47c43261_55aa2c41' as ChangeMessageId,
+ author: {
+ _account_id: 1115495 as AccountId,
+ name: 'Andrew Bonventre',
+ email: 'andybons@chromium.org' as EmailAddress,
+ },
+ date: '2016-01-12 20:24:49.448000000' as Timestamp,
+ message: 'Uploaded patch set 1.',
+ _revision_number: 1 as PatchSetNum,
+ expanded: true,
+ };
+
+ flush();
+ assert.isTrue(
+ queryAndAssert<HTMLElement>(element, '.replyActionContainer').hidden
+ );
+ assert.isTrue(queryAndAssert<HTMLElement>(element, '.deleteBtn').hidden);
+ });
+ });
+
+ suite('patchset comment summary', () => {
+ setup(() => {
+ element = basicFixture.instantiate();
+ element.message = {
+ ...createChangeMessage(),
+ id: '6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3' as ChangeMessageId,
+ };
+ });
+
+ test('single patchset comment posted', () => {
+ const threads = [
+ {
+ comments: [
+ {
+ ...createComment(),
+ change_message_id: '6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3' as ChangeMessageId,
+ patch_set: 1 as PatchSetNum,
+ id: 'e365b138_bed65caa' as UrlEncodedCommentId,
+ updated: '2020-05-15 13:35:56.000000000' as Timestamp,
+ message: 'testing the load',
+ unresolved: false,
+ path: '/PATCHSET_LEVEL',
+ collapsed: false,
+ },
+ ],
+ patchNum: 1 as PatchSetNum,
+ path: '/PATCHSET_LEVEL',
+ rootId: 'e365b138_bed65caa' as UrlEncodedCommentId,
+ commentSide: CommentSide.REVISION,
+ },
+ ];
+ assert.equal(
+ element._computeMessageContentCollapsed('', undefined, threads),
+ 'testing the load'
+ );
+ assert.equal(element._computeMessageContent(false, '', undefined), '');
+ });
+
+ test('single patchset comment with reply', () => {
+ const threads = [
+ {
+ comments: [
+ {
+ ...createComment(),
+ patch_set: 1 as PatchSetNum,
+ id: 'e365b138_bed65caa' as UrlEncodedCommentId,
+ updated: '2020-05-15 13:35:56.000000000' as Timestamp,
+ message: 'testing the load',
+ unresolved: false,
+ path: '/PATCHSET_LEVEL',
+ collapsed: false,
+ },
+ {
+ change_message_id: '6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3',
+ patch_set: 1 as PatchSetNum,
+ id: 'd6efcc85_4cbbb6f4' as UrlEncodedCommentId,
+ in_reply_to: 'e365b138_bed65caa' as UrlEncodedCommentId,
+ updated: '2020-05-15 16:55:28.000000000' as Timestamp,
+ message: 'n',
+ unresolved: false,
+ path: '/PATCHSET_LEVEL',
+ __draft: true,
+ collapsed: true,
+ },
+ ],
+ patchNum: 1 as PatchSetNum,
+ path: '/PATCHSET_LEVEL',
+ rootId: 'e365b138_bed65caa' as UrlEncodedCommentId,
+ commentSide: CommentSide.REVISION,
+ },
+ ];
+ assert.equal(
+ element._computeMessageContentCollapsed('', undefined, threads),
+ 'n'
+ );
+ assert.equal(element._computeMessageContent(false, '', undefined), '');
+ });
+ });
+
+ suite('when logged in but not admin', () => {
+ setup(async () => {
+ stubRestApi('getIsAdmin').returns(Promise.resolve(false));
+ element = basicFixture.instantiate();
+ await flush();
+ });
+
+ test('can see reply but not delete button', () => {
+ element.message = {
+ ...createChangeMessage(),
+ id: '47c43261_55aa2c41' as ChangeMessageId,
+ author: {
+ _account_id: 1115495 as AccountId,
+ name: 'Andrew Bonventre',
+ email: 'andybons@chromium.org' as EmailAddress,
+ },
+ date: '2016-01-12 20:24:49.448000000' as Timestamp,
+ message: 'Uploaded patch set 1.',
+ _revision_number: 1 as PatchSetNum,
+ expanded: true,
+ };
+
+ flush();
+ assert.isFalse(
+ queryAndAssert<HTMLElement>(element, '.replyActionContainer').hidden
+ );
+ assert.isTrue(queryAndAssert<HTMLElement>(element, '.deleteBtn').hidden);
+ });
+
+ test('reply button shown when message is updated', () => {
+ element.message = undefined;
+ flush();
+ let replyEl = query(element, '.replyActionContainer');
+ // We don't even expect the button to show up in the DOM when the message
+ // is undefined.
+ assert.isNotOk(replyEl);
+
+ element.message = {
+ ...createChangeMessage(),
+ id: '47c43261_55aa2c41' as ChangeMessageId,
+ author: {
+ _account_id: 1115495 as AccountId,
+ name: 'Andrew Bonventre',
+ email: 'andybons@chromium.org' as EmailAddress,
+ },
+ date: '2016-01-12 20:24:49.448000000' as Timestamp,
+ message: 'not empty',
+ _revision_number: 1 as PatchSetNum,
+ expanded: true,
+ };
+ flush();
+ replyEl = queryAndAssert(element, '.replyActionContainer');
+ assert.isOk(replyEl);
+ assert.isFalse((replyEl as HTMLElement).hidden);
+ });
+ });
+});
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
index bc3f167..16d9d39 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
@@ -19,7 +19,6 @@
import '../../shared/gr-icons/gr-icons';
import '../gr-message/gr-message';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-messages-list_html';
@@ -214,7 +213,7 @@
@customElement('gr-messages-list')
export class GrMessagesList extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.js
index d22c7d0..fd45eec 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.js
@@ -133,7 +133,6 @@
suite('basic tests', () => {
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getDiffComments').returns(Promise.resolve(comments));
stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
@@ -440,7 +439,6 @@
let commentApiWrapper;
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getDiffComments').returns(Promise.resolve({}));
stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-change.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-change.ts
index 7b698f1..49c2486 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-change.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-change.ts
@@ -35,9 +35,6 @@
href?: string;
@property()
- isCurrentChange = false;
-
- @property()
showSubmittableCheck = false;
@property()
@@ -102,9 +99,6 @@
.submittableCheck.submittable {
display: inline;
}
- .arrowToCurrentChange {
- position: absolute;
- }
`,
];
}
@@ -113,13 +107,7 @@
const change = this.change;
if (!change) throw new Error('Missing change');
const linkClass = this._computeLinkClass(change);
- return html`<span
- role="img"
- class="arrowToCurrentChange"
- aria-label="Arrow marking current change"
- ?hidden=${!this.isCurrentChange}
- >âž”</span
- >
+ return html`
<div class="changeContainer">
<a href="${this.href}" class="${linkClass}"><slot></slot></a>
${this.showSubmittableCheck
@@ -137,7 +125,8 @@
(${this._computeChangeStatus(change)})
</span>`
: ''}
- </div> `;
+ </div>
+ `;
}
_computeLinkClass(change: ChangeInfo | RelatedChangeAndCommitInfo) {
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts
index e921979..abac54c 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts
@@ -21,7 +21,13 @@
import '../../plugins/gr-endpoint-slot/gr-endpoint-slot';
import {classMap} from 'lit-html/directives/class-map';
import {GrLitElement} from '../../lit/gr-lit-element';
-import {customElement, property, css, internalProperty} from 'lit-element';
+import {
+ customElement,
+ property,
+ css,
+ internalProperty,
+ TemplateResult,
+} from 'lit-element';
import {sharedStyles} from '../../../styles/shared-styles';
import {
SubmittedTogetherInfo,
@@ -42,7 +48,22 @@
} from '../../../utils/change-util';
/** What is the maximum number of shown changes in collapsed list? */
-const MAX_CHANGES_WHEN_COLLAPSED = 3;
+const DEFALT_NUM_CHANGES_WHEN_COLLAPSED = 3;
+
+export interface ChangeMarkersInList {
+ showCurrentChangeArrow: boolean;
+ showWhenCollapsed: boolean;
+ showTopArrow: boolean;
+ showBottomArrow: boolean;
+}
+
+export enum Section {
+ RELATED_CHANGES = 'related changes',
+ SUBMITTED_TOGETHER = 'submitted together',
+ SAME_TOPIC = 'same topic',
+ MERGE_CONFLICTS = 'merge conflicts',
+ CHERRY_PICKS = 'cherry picks',
+}
@customElement('gr-related-changes-list-experimental')
export class GrRelatedChangesListExperimental extends GrLitElement {
@@ -86,16 +107,34 @@
section {
margin-bottom: var(--spacing-m);
}
+ gr-related-change {
+ display: flex;
+ }
+ .marker {
+ position: absolute;
+ margin-left: calc(-1 * var(--spacing-s));
+ }
+ .arrowToCurrentChange {
+ position: absolute;
+ }
`,
];
}
render() {
- let showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
+ const sectionSize = this.sectionSizeFactory(
+ this.relatedChanges.length,
+ this.submittedTogether?.changes.length || 0,
+ this.sameTopicChanges.length,
+ this.conflictingChanges.length,
+ this.cherryPickChanges.length
+ );
+ const relatedChangesMarkersPredicate = this.markersPredicateFactory(
this.relatedChanges.length,
this.relatedChanges.findIndex(relatedChange =>
this._changesEqual(relatedChange, this.change)
- )
+ ),
+ sectionSize(Section.RELATED_CHANGES)
);
const connectedRevisions = this._computeConnectedRevisions(
this.change,
@@ -109,26 +148,29 @@
<gr-related-collapse
title="Relation chain"
.length=${this.relatedChanges.length}
+ .numChangesWhenCollapsed=${sectionSize(Section.RELATED_CHANGES)}
>
${this.relatedChanges.map(
(change, index) =>
- html`<gr-related-change
- class="${classMap({
- ['show-when-collapsed']: showWhenCollapsedPredicate(index),
- })}"
- .isCurrentChange="${this._changesEqual(change, this.change)}"
- .change="${change}"
- .connectedRevisions="${connectedRevisions}"
- .href="${change?._change_number
- ? GerritNav.getUrlForChangeById(
- change._change_number,
- change.project,
- change._revision_number as PatchSetNum
- )
- : ''}"
- .showChangeStatus=${true}
- >${change.commit.subject}</gr-related-change
- >`
+ html`${this.renderMarkers(
+ relatedChangesMarkersPredicate(index)
+ )}<gr-related-change
+ class="${classMap({
+ ['show-when-collapsed']: relatedChangesMarkersPredicate(index)
+ .showWhenCollapsed,
+ })}"
+ .change="${change}"
+ .connectedRevisions="${connectedRevisions}"
+ .href="${change?._change_number
+ ? GerritNav.getUrlForChangeById(
+ change._change_number,
+ change.project,
+ change._revision_number as PatchSetNum
+ )
+ : ''}"
+ .showChangeStatus=${true}
+ >${change.commit.subject}</gr-related-change
+ >`
)}
</gr-related-collapse>
</section>`;
@@ -136,11 +178,12 @@
const submittedTogetherChanges = this.submittedTogether?.changes ?? [];
const countNonVisibleChanges =
this.submittedTogether?.non_visible_changes ?? 0;
- showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
+ const submittedTogetherMarkersPredicate = this.markersPredicateFactory(
submittedTogetherChanges.length,
submittedTogetherChanges.findIndex(relatedChange =>
this._changesEqual(relatedChange, this.change)
- )
+ ),
+ sectionSize(Section.SUBMITTED_TOGETHER)
);
const submittedTogetherSection = html`<section
id="submittedTogether"
@@ -150,23 +193,27 @@
<gr-related-collapse
title="Submitted together"
.length=${submittedTogetherChanges.length}
+ .numChangesWhenCollapsed=${sectionSize(Section.SUBMITTED_TOGETHER)}
>
${submittedTogetherChanges.map(
(change, index) =>
- html`<gr-related-change
- class="${classMap({
- ['show-when-collapsed']: showWhenCollapsedPredicate(index),
- })}"
- .isCurrentChange="${this._changesEqual(change, this.change)}"
- .change="${change}"
- .href="${GerritNav.getUrlForChangeById(
- change._number,
- change.project
- )}"
- .showSubmittableCheck=${true}
- >${change.project}: ${change.branch}:
- ${change.subject}</gr-related-change
- >`
+ html`${this.renderMarkers(
+ submittedTogetherMarkersPredicate(index)
+ )}<gr-related-change
+ class="${classMap({
+ ['show-when-collapsed']: submittedTogetherMarkersPredicate(
+ index
+ ).showWhenCollapsed,
+ })}"
+ .change="${change}"
+ .href="${GerritNav.getUrlForChangeById(
+ change._number,
+ change.project
+ )}"
+ .showSubmittableCheck=${true}
+ >${change.project}: ${change.branch}:
+ ${change.subject}</gr-related-change
+ >`
)}
</gr-related-collapse>
<div class="note" ?hidden=${!countNonVisibleChanges}>
@@ -174,9 +221,10 @@
</div>
</section>`;
- showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
+ const sameTopicMarkersPredicate = this.markersPredicateFactory(
this.sameTopicChanges.length,
- -1
+ -1,
+ sectionSize(Section.SAME_TOPIC)
);
const sameTopicSection = html`<section
id="sameTopic"
@@ -185,28 +233,33 @@
<gr-related-collapse
title="Same topic"
.length=${this.sameTopicChanges.length}
+ .numChangesWhenCollapsed=${sectionSize(Section.SAME_TOPIC)}
>
${this.sameTopicChanges.map(
(change, index) =>
- html`<gr-related-change
- class="${classMap({
- ['show-when-collapsed']: showWhenCollapsedPredicate(index),
- })}"
- .change="${change}"
- .href="${GerritNav.getUrlForChangeById(
- change._number,
- change.project
- )}"
- >${change.project}: ${change.branch}:
- ${change.subject}</gr-related-change
- >`
+ html`${this.renderMarkers(
+ sameTopicMarkersPredicate(index)
+ )}<gr-related-change
+ class="${classMap({
+ ['show-when-collapsed']: sameTopicMarkersPredicate(index)
+ .showWhenCollapsed,
+ })}"
+ .change="${change}"
+ .href="${GerritNav.getUrlForChangeById(
+ change._number,
+ change.project
+ )}"
+ >${change.project}: ${change.branch}:
+ ${change.subject}</gr-related-change
+ >`
)}
</gr-related-collapse>
</section>`;
- showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
+ const mergeConflictsMarkersPredicate = this.markersPredicateFactory(
this.conflictingChanges.length,
- -1
+ -1,
+ sectionSize(Section.MERGE_CONFLICTS)
);
const mergeConflictsSection = html`<section
id="mergeConflicts"
@@ -215,27 +268,32 @@
<gr-related-collapse
title="Merge conflicts"
.length=${this.conflictingChanges.length}
+ .numChangesWhenCollapsed=${sectionSize(Section.MERGE_CONFLICTS)}
>
${this.conflictingChanges.map(
(change, index) =>
- html`<gr-related-change
- class="${classMap({
- ['show-when-collapsed']: showWhenCollapsedPredicate(index),
- })}"
- .change="${change}"
- .href="${GerritNav.getUrlForChangeById(
- change._number,
- change.project
- )}"
- >${change.subject}</gr-related-change
- >`
+ html`${this.renderMarkers(
+ mergeConflictsMarkersPredicate(index)
+ )}<gr-related-change
+ class="${classMap({
+ ['show-when-collapsed']: mergeConflictsMarkersPredicate(index)
+ .showWhenCollapsed,
+ })}"
+ .change="${change}"
+ .href="${GerritNav.getUrlForChangeById(
+ change._number,
+ change.project
+ )}"
+ >${change.subject}</gr-related-change
+ >`
)}
</gr-related-collapse>
</section>`;
- showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
+ const cherryPicksMarkersPredicate = this.markersPredicateFactory(
this.cherryPickChanges.length,
- -1
+ -1,
+ sectionSize(Section.CHERRY_PICKS)
);
const cherryPicksSection = html`<section
id="cherryPicks"
@@ -244,20 +302,24 @@
<gr-related-collapse
title="Cherry picks"
.length=${this.cherryPickChanges.length}
+ .numChangesWhenCollapsed=${sectionSize(Section.CHERRY_PICKS)}
>
${this.cherryPickChanges.map(
(change, index) =>
- html`<gr-related-change
- class="${classMap({
- ['show-when-collapsed']: showWhenCollapsedPredicate(index),
- })}"
- .change="${change}"
- .href="${GerritNav.getUrlForChangeById(
- change._number,
- change.project
- )}"
- >${change.branch}: ${change.subject}</gr-related-change
- >`
+ html`${this.renderMarkers(
+ cherryPicksMarkersPredicate(index)
+ )}<gr-related-change
+ class="${classMap({
+ ['show-when-collapsed']: cherryPicksMarkersPredicate(index)
+ .showWhenCollapsed,
+ })}"
+ .change="${change}"
+ .href="${GerritNav.getUrlForChangeById(
+ change._number,
+ change.project
+ )}"
+ >${change.branch}: ${change.subject}</gr-related-change
+ >`
)}
</gr-related-collapse>
</section>`;
@@ -271,17 +333,140 @@
</gr-endpoint-decorator>`;
}
- showWhenCollapsedPredicateFactory(length: number, highlightIndex: number) {
- return (index: number) => {
- if (highlightIndex === -1) return index < MAX_CHANGES_WHEN_COLLAPSED;
- if (highlightIndex === 0) return index <= MAX_CHANGES_WHEN_COLLAPSED - 1;
+ sectionSizeFactory(
+ relatedChangesLen: number,
+ submittedTogetherLen: number,
+ sameTopicLen: number,
+ mergeConflictsLen: number,
+ cherryPicksLen: number
+ ) {
+ const calcDefaultSize = (length: number) =>
+ Math.min(length, DEFALT_NUM_CHANGES_WHEN_COLLAPSED);
+
+ const sectionSizes = [
+ {
+ section: Section.RELATED_CHANGES,
+ size: calcDefaultSize(relatedChangesLen),
+ len: relatedChangesLen,
+ },
+ {
+ section: Section.SUBMITTED_TOGETHER,
+ size: calcDefaultSize(submittedTogetherLen),
+ len: submittedTogetherLen,
+ },
+ {
+ section: Section.SAME_TOPIC,
+ size: calcDefaultSize(sameTopicLen),
+ len: sameTopicLen,
+ },
+ {
+ section: Section.MERGE_CONFLICTS,
+ size: calcDefaultSize(mergeConflictsLen),
+ len: mergeConflictsLen,
+ },
+ {
+ section: Section.CHERRY_PICKS,
+ size: calcDefaultSize(cherryPicksLen),
+ len: cherryPicksLen,
+ },
+ ];
+
+ const FILLER = 1; // space for header
+ let totalSize = sectionSizes.reduce(
+ (acc, val) => acc + val.size + (val.size !== 0 ? FILLER : 0),
+ 0
+ );
+
+ const MAX_SIZE = 16;
+ for (let i = 0; i < sectionSizes.length; i++) {
+ if (totalSize >= MAX_SIZE) break;
+ const sizeObj = sectionSizes[i];
+ if (sizeObj.size === sizeObj.len) continue;
+ const newSize = Math.min(
+ MAX_SIZE - totalSize + sizeObj.size,
+ sizeObj.len
+ );
+ totalSize += newSize - sizeObj.size;
+ sizeObj.size = newSize;
+ }
+
+ return (section: Section) => {
+ const sizeObj = sectionSizes.find(sizeObj => sizeObj.section === section);
+ if (sizeObj) return sizeObj.size;
+ return DEFALT_NUM_CHANGES_WHEN_COLLAPSED;
+ };
+ }
+
+ markersPredicateFactory(
+ length: number,
+ highlightIndex: number,
+ numChangesShownWhenCollapsed = DEFALT_NUM_CHANGES_WHEN_COLLAPSED
+ ): (index: number) => ChangeMarkersInList {
+ const showWhenCollapsedPredicate = (index: number) => {
+ if (highlightIndex === -1) return index < numChangesShownWhenCollapsed;
+ if (highlightIndex === 0)
+ return index <= numChangesShownWhenCollapsed - 1;
if (highlightIndex === length - 1)
- return index >= length - MAX_CHANGES_WHEN_COLLAPSED;
+ return index >= length - numChangesShownWhenCollapsed;
+ let numBeforeHighlight = Math.floor(numChangesShownWhenCollapsed / 2);
+ let numAfterHighlight =
+ Math.floor(numChangesShownWhenCollapsed / 2) -
+ (numChangesShownWhenCollapsed % 2 ? 0 : 1);
+ numBeforeHighlight += Math.max(
+ highlightIndex + numAfterHighlight - length + 1,
+ 0
+ );
+ numAfterHighlight -= Math.min(0, highlightIndex - numBeforeHighlight);
return (
- highlightIndex - MAX_CHANGES_WHEN_COLLAPSED + 2 <= index &&
- index <= highlightIndex + MAX_CHANGES_WHEN_COLLAPSED - 2
+ highlightIndex - numBeforeHighlight <= index &&
+ index <= highlightIndex + numAfterHighlight
);
};
+ return (index: number) => {
+ return {
+ showCurrentChangeArrow:
+ highlightIndex !== -1 && index === highlightIndex,
+ showWhenCollapsed: showWhenCollapsedPredicate(index),
+ showTopArrow:
+ index >= 1 &&
+ index !== highlightIndex &&
+ showWhenCollapsedPredicate(index) &&
+ !showWhenCollapsedPredicate(index - 1),
+ showBottomArrow:
+ index <= length - 2 &&
+ index !== highlightIndex &&
+ showWhenCollapsedPredicate(index) &&
+ !showWhenCollapsedPredicate(index + 1),
+ };
+ };
+ }
+
+ renderMarkers(changeMarkers: ChangeMarkersInList) {
+ if (changeMarkers.showCurrentChangeArrow) {
+ return html`<span
+ role="img"
+ class="arrowToCurrentChange"
+ aria-label="Arrow marking current change"
+ >âž”</span
+ >`;
+ }
+ if (changeMarkers.showTopArrow) {
+ return html`<span
+ role="img"
+ class="marker"
+ aria-label="Arrow marking change has collapsed ancestors"
+ ><iron-icon icon="gr-icons:arrowDropUp"></iron-icon
+ ></span> `;
+ }
+ if (changeMarkers.showBottomArrow) {
+ return html`<span
+ role="img"
+ class="marker"
+ aria-label="Arrow marking change has collapsed descendants"
+ ><iron-icon icon="gr-icons:arrowDropDown"></iron-icon
+ ></span> `;
+ }
+ return nothing;
}
reload(getRelatedChanges?: Promise<RelatedChangesInfo | undefined>) {
@@ -429,6 +614,11 @@
@property()
length = 0;
+ @property()
+ numChangesWhenCollapsed = DEFALT_NUM_CHANGES_WHEN_COLLAPSED;
+
+ private readonly reporting = appContext.reportingService;
+
static get styles() {
return [
sharedStyles,
@@ -456,13 +646,25 @@
width: 1.2em;
}
.collapsed ::slotted(gr-related-change.show-when-collapsed) {
- display: flex;
+ visibility: visible;
+ height: auto;
}
- .collapsed ::slotted(gr-related-change) {
+ .collapsed ::slotted(.marker) {
+ display: block;
+ }
+ .show-all ::slotted(.marker) {
display: none;
}
+ /* keep width, so width of section and position of show all button
+ * are set according to width of all (even hidden) elements
+ */
+ .collapsed ::slotted(gr-related-change) {
+ visibility: hidden;
+ height: 0px;
+ }
::slotted(gr-related-change) {
- display: flex;
+ visibility: visible;
+ height: auto;
}
gr-button iron-icon {
color: inherit;
@@ -481,14 +683,14 @@
render() {
const title = html`<h4 class="title">${this.title}</h4>`;
- const collapsible = this.length > MAX_CHANGES_WHEN_COLLAPSED;
+ const collapsible = this.length > this.numChangesWhenCollapsed;
const items = html` <div
- class="${!this.showAll && collapsible ? 'collapsed' : ''}"
+ class="${!this.showAll && collapsible ? 'collapsed' : 'show-all'}"
>
<slot></slot>
</div>`;
- let button = nothing;
+ let button: TemplateResult | typeof nothing = nothing;
if (collapsible) {
if (this.showAll) {
button = html`<gr-button link="" @click="${this.toggle}"
@@ -509,6 +711,10 @@
private toggle(e: MouseEvent) {
e.stopPropagation();
this.showAll = !this.showAll;
+ this.reporting.reportInteraction('toggle show all button', {
+ sectionName: this.title,
+ toState: this.showAll ? 'Show all' : 'Show less',
+ });
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental_test.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental_test.ts
new file mode 100644
index 0000000..6d8fe13
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental_test.ts
@@ -0,0 +1,158 @@
+/**
+ * @license
+ * Copyright (C) 2021 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.
+ */
+
+import '../../../test/common-test-setup-karma';
+import './gr-related-changes-list-experimental';
+import {
+ ChangeMarkersInList,
+ GrRelatedChangesListExperimental,
+ Section,
+} from './gr-related-changes-list-experimental';
+
+const basicFixture = fixtureFromElement('gr-related-changes-list-experimental');
+
+suite('gr-related-changes-list-experimental', () => {
+ let element: GrRelatedChangesListExperimental;
+
+ setup(() => {
+ element = basicFixture.instantiate();
+ });
+
+ suite('show when collapsed', () => {
+ function genBoolArray(
+ instructions: Array<{
+ len: number;
+ v: boolean;
+ }>
+ ) {
+ return instructions
+ .map(inst => Array.from({length: inst.len}, () => inst.v))
+ .reduce((acc, val) => acc.concat(val), []);
+ }
+
+ function checkShowWhenCollapsed(
+ expected: boolean[],
+ markersPredicate: (index: number) => ChangeMarkersInList,
+ msg: string
+ ) {
+ for (let i = 0; i < expected.length; i++) {
+ assert.equal(
+ markersPredicate(i).showWhenCollapsed,
+ expected[i],
+ `change on pos (${i}) ${msg}`
+ );
+ }
+ }
+
+ test('size 5', () => {
+ const markersPredicate = element.markersPredicateFactory(10, 4, 5);
+ const expectedCollapsing = genBoolArray([
+ {len: 2, v: false},
+ {len: 5, v: true},
+ {len: 3, v: false},
+ ]);
+ checkShowWhenCollapsed(
+ expectedCollapsing,
+ markersPredicate,
+ 'highlight 4, size 10, size 5'
+ );
+
+ const markersPredicate2 = element.markersPredicateFactory(10, 8, 5);
+ const expectedCollapsing2 = genBoolArray([
+ {len: 5, v: false},
+ {len: 5, v: true},
+ ]);
+ checkShowWhenCollapsed(
+ expectedCollapsing2,
+ markersPredicate2,
+ 'highlight 8, size 10, size 5'
+ );
+
+ const markersPredicate3 = element.markersPredicateFactory(10, 1, 5);
+ const expectedCollapsing3 = genBoolArray([
+ {len: 5, v: true},
+ {len: 5, v: false},
+ ]);
+ checkShowWhenCollapsed(
+ expectedCollapsing3,
+ markersPredicate3,
+ 'highlight 1, size 10, size 5'
+ );
+ });
+
+ test('size 4', () => {
+ const markersPredicate = element.markersPredicateFactory(10, 4, 4);
+ const expectedCollapsing = genBoolArray([
+ {len: 2, v: false},
+ {len: 4, v: true},
+ {len: 4, v: false},
+ ]);
+ checkShowWhenCollapsed(
+ expectedCollapsing,
+ markersPredicate,
+ 'highlight 4, len 10, size 4'
+ );
+
+ const markersPredicate2 = element.markersPredicateFactory(10, 8, 4);
+ const expectedCollapsing2 = genBoolArray([
+ {len: 6, v: false},
+ {len: 4, v: true},
+ ]);
+ checkShowWhenCollapsed(
+ expectedCollapsing2,
+ markersPredicate2,
+ 'highlight 8, len 10, size 4'
+ );
+
+ const markersPredicate3 = element.markersPredicateFactory(10, 1, 4);
+ const expectedCollapsing3 = genBoolArray([
+ {len: 4, v: true},
+ {len: 6, v: false},
+ ]);
+ checkShowWhenCollapsed(
+ expectedCollapsing3,
+ markersPredicate3,
+ 'highlight 1, len 10, size 4'
+ );
+ });
+ });
+
+ suite('section size', () => {
+ test('1 section', () => {
+ const sectionSize = element.sectionSizeFactory(20, 0, 0, 0, 0);
+ assert.equal(sectionSize(Section.RELATED_CHANGES), 15);
+ const sectionSize2 = element.sectionSizeFactory(0, 0, 10, 0, 0);
+ assert.equal(sectionSize2(Section.SAME_TOPIC), 10);
+ });
+ test('2 sections', () => {
+ const sectionSize = element.sectionSizeFactory(20, 20, 0, 0, 0);
+ assert.equal(sectionSize(Section.RELATED_CHANGES), 11);
+ assert.equal(sectionSize(Section.SUBMITTED_TOGETHER), 3);
+ const sectionSize2 = element.sectionSizeFactory(4, 0, 10, 0, 0);
+ assert.equal(sectionSize2(Section.RELATED_CHANGES), 4);
+ assert.equal(sectionSize2(Section.SAME_TOPIC), 10);
+ });
+ test('many sections', () => {
+ const sectionSize = element.sectionSizeFactory(20, 20, 3, 3, 3);
+ assert.equal(sectionSize(Section.RELATED_CHANGES), 3);
+ assert.equal(sectionSize(Section.SUBMITTED_TOGETHER), 3);
+ const sectionSize2 = element.sectionSizeFactory(4, 1, 10, 1, 1);
+ assert.equal(sectionSize2(Section.RELATED_CHANGES), 4);
+ assert.equal(sectionSize2(Section.SAME_TOPIC), 4);
+ });
+ });
+});
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
index 5705c4f..1f51138 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
@@ -19,7 +19,6 @@
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
import '../../plugins/gr-endpoint-slot/gr-endpoint-slot';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-related-changes-list_html';
@@ -53,9 +52,7 @@
}
@customElement('gr-related-changes-list')
-export class GrRelatedChangesList extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrRelatedChangesList extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -303,8 +300,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
// We listen to `new-section-loaded` events to allow plugins to trigger
// visibility computations, if their content or visibility changed.
this.addEventListener('new-section-loaded', () =>
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js
deleted file mode 100644
index f54b819..0000000
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js
+++ /dev/null
@@ -1,631 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 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.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-related-changes-list.js';
-import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
-import {resetPlugins} from '../../../test/test-utils.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-
-const pluginApi = _testOnly_initGerritPluginApi();
-
-const basicFixture = fixtureFromElement('gr-related-changes-list');
-
-suite('gr-related-changes-list tests', () => {
- let element;
-
- setup(() => {
- element = basicFixture.instantiate();
- });
-
- test('connected revisions', () => {
- const change = {
- revisions: {
- 'e3c6d60783bfdec9ebae7dcfec4662360433449e': {
- _number: 1,
- },
- '26e5e4c9c7ae31cbd876271cca281ce22b413997': {
- _number: 2,
- },
- 'bf7884d695296ca0c91702ba3e2bc8df0f69a907': {
- _number: 7,
- },
- 'b5fc49f2e67d1889d5275cac04ad3648f2ec7fe3': {
- _number: 5,
- },
- 'd6bcee67570859ccb684873a85cf50b1f0e96fda': {
- _number: 6,
- },
- 'cc960918a7f90388f4a9e05753d0f7b90ad44546': {
- _number: 3,
- },
- '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6': {
- _number: 4,
- },
- },
- };
- let patchNum = 7;
- let relatedChanges = [
- {
- commit: {
- commit: '2cebeedfb1e80f4b872d0a13ade529e70652c0c8',
- parents: [
- {
- commit: '87ed20b241576b620bbaa3dfd47715ce6782b7dd',
- },
- ],
- },
- },
- {
- commit: {
- commit: '87ed20b241576b620bbaa3dfd47715ce6782b7dd',
- parents: [
- {
- commit: '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb',
- },
- ],
- },
- },
- {
- commit: {
- commit: '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb',
- parents: [
- {
- commit: 'b0ccb183494a8e340b8725a2dc553967d61e6dae',
- },
- ],
- },
- },
- {
- commit: {
- commit: 'b0ccb183494a8e340b8725a2dc553967d61e6dae',
- parents: [
- {
- commit: 'bf7884d695296ca0c91702ba3e2bc8df0f69a907',
- },
- ],
- },
- },
- {
- commit: {
- commit: 'bf7884d695296ca0c91702ba3e2bc8df0f69a907',
- parents: [
- {
- commit: '613bc4f81741a559c6667ac08d71dcc3348f73ce',
- },
- ],
- },
- },
- {
- commit: {
- commit: '613bc4f81741a559c6667ac08d71dcc3348f73ce',
- parents: [
- {
- commit: '455ed9cd27a16bf6991f04dcc57ef575dc4d5e75',
- },
- ],
- },
- },
- ];
-
- let connectedChanges =
- element._computeConnectedRevisions(change, patchNum, relatedChanges);
- assert.deepEqual(connectedChanges, [
- '613bc4f81741a559c6667ac08d71dcc3348f73ce',
- 'bf7884d695296ca0c91702ba3e2bc8df0f69a907',
- 'bf7884d695296ca0c91702ba3e2bc8df0f69a907',
- 'b0ccb183494a8e340b8725a2dc553967d61e6dae',
- '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb',
- '87ed20b241576b620bbaa3dfd47715ce6782b7dd',
- '2cebeedfb1e80f4b872d0a13ade529e70652c0c8',
- ]);
-
- patchNum = 4;
- relatedChanges = [
- {
- commit: {
- commit: '2cebeedfb1e80f4b872d0a13ade529e70652c0c8',
- parents: [
- {
- commit: '87ed20b241576b620bbaa3dfd47715ce6782b7dd',
- },
- ],
- },
- },
- {
- commit: {
- commit: '87ed20b241576b620bbaa3dfd47715ce6782b7dd',
- parents: [
- {
- commit: '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb',
- },
- ],
- },
- },
- {
- commit: {
- commit: '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb',
- parents: [
- {
- commit: 'b0ccb183494a8e340b8725a2dc553967d61e6dae',
- },
- ],
- },
- },
- {
- commit: {
- commit: 'a3e5d9d4902b915a39e2efba5577211b9b3ebe7b',
- parents: [
- {
- commit: '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6',
- },
- ],
- },
- },
- {
- commit: {
- commit: '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6',
- parents: [
- {
- commit: 'af815dac54318826b7f1fa468acc76349ffc588e',
- },
- ],
- },
- },
- {
- commit: {
- commit: 'af815dac54318826b7f1fa468acc76349ffc588e',
- parents: [
- {
- commit: '58f76e406e24cb8b0f5d64c7f5ac1e8616d0a22c',
- },
- ],
- },
- },
- ];
-
- connectedChanges =
- element._computeConnectedRevisions(change, patchNum, relatedChanges);
- assert.deepEqual(connectedChanges, [
- 'af815dac54318826b7f1fa468acc76349ffc588e',
- '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6',
- '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6',
- 'a3e5d9d4902b915a39e2efba5577211b9b3ebe7b',
- ]);
- });
-
- test('_changesEqual', () => {
- const change1 = {change_id: 123, _number: 0};
- const change2 = {change_id: 456, _number: 1};
- const change3 = {change_id: 123, _number: 2};
- const change4 = {change_id: 123, _change_number: 1};
-
- assert.isTrue(element._changesEqual(change1, change1));
- assert.isFalse(element._changesEqual(change1, change2));
- assert.isFalse(element._changesEqual(change1, change3));
- assert.isTrue(element._changesEqual(change2, change4));
- });
-
- test('_getChangeNumber', () => {
- const change1 = {change_id: 123, _number: 0};
- const change2 = {change_id: 456, _change_number: 1};
- assert.equal(element._getChangeNumber(change1), 0);
- assert.equal(element._getChangeNumber(change2), 1);
- });
-
- test('event for section loaded fires for each section ', () => {
- const loadedStub = sinon.stub();
- element.patchNum = 7;
- element.change = {
- change_id: 123,
- status: 'NEW',
- };
- element.mergeable = true;
- element.addEventListener('new-section-loaded', loadedStub);
- stubRestApi('getRelatedChanges').returns(Promise.resolve({changes: []}));
- stubRestApi('getChangesSubmittedTogether').returns(Promise.resolve());
- stubRestApi('getChangeCherryPicks').returns(Promise.resolve());
- stubRestApi('getChangeConflicts').returns(Promise.resolve());
-
- return element.reload().then(() => {
- assert.equal(loadedStub.callCount, 4);
- });
- });
-
- suite('getChangeConflicts resolves undefined', () => {
- let element;
-
- setup(() => {
- element = basicFixture.instantiate();
-
- stubRestApi('getRelatedChanges').returns(Promise.resolve({changes: []}));
- stubRestApi('getChangesSubmittedTogether').returns(Promise.resolve());
- stubRestApi('getChangeCherryPicks').returns(Promise.resolve());
- stubRestApi('getChangeConflicts').returns(Promise.resolve());
- });
-
- test('_conflicts are an empty array', () => {
- element.patchNum = 7;
- element.change = {
- change_id: 123,
- status: 'NEW',
- };
- element.mergeable = true;
- element.reload();
- assert.equal(element._conflicts.length, 0);
- });
- });
-
- suite('get conflicts tests', () => {
- let element;
- let conflictsStub;
-
- setup(() => {
- element = basicFixture.instantiate();
-
- stubRestApi('getRelatedChanges').returns(Promise.resolve({changes: []}));
- stubRestApi('getChangesSubmittedTogether').returns(Promise.resolve());
- stubRestApi('getChangeCherryPicks').returns(Promise.resolve());
- conflictsStub = stubRestApi('getChangeConflicts').returns(
- Promise.resolve());
- });
-
- test('request conflicts if open and mergeable', () => {
- element.patchNum = 7;
- element.change = {
- change_id: 123,
- status: 'NEW',
- };
- element.mergeable = true;
- element.reload();
- assert.isTrue(conflictsStub.called);
- });
-
- test('does not request conflicts if closed and mergeable', () => {
- element.patchNum = 7;
- element.change = {
- change_id: 123,
- status: 'MERGED',
- };
- element.reload();
- assert.isFalse(conflictsStub.called);
- });
-
- test('does not request conflicts if open and not mergeable', () => {
- element.patchNum = 7;
- element.change = {
- change_id: 123,
- status: 'NEW',
- };
- element.mergeable = false;
- element.reload();
- assert.isFalse(conflictsStub.called);
- });
-
- test('doesnt request conflicts if closed and not mergeable', () => {
- element.patchNum = 7;
- element.change = {
- change_id: 123,
- status: 'MERGED',
- };
- element.mergeable = false;
- element.reload();
- assert.isFalse(conflictsStub.called);
- });
- });
-
- test('_calculateHasParent', () => {
- const changeId = 123;
- const relatedChanges = [];
-
- assert.equal(element._calculateHasParent(changeId, relatedChanges),
- false);
-
- relatedChanges.push({change_id: 123});
- assert.equal(element._calculateHasParent(changeId, relatedChanges),
- false);
-
- relatedChanges.push({change_id: 234});
- assert.equal(element._calculateHasParent(changeId, relatedChanges),
- true);
- });
-
- suite('hidden attribute and update event', () => {
- const changes = [{
- project: 'foo/bar',
- change_id: 'Ideadbeef',
- commit: {
- commit: 'deadbeef',
- parents: [{commit: 'abc123'}],
- author: {},
- subject: 'do that thing',
- },
- _change_number: 12345,
- _revision_number: 1,
- _current_revision_number: 1,
- status: 'NEW',
- }];
-
- test('clear and empties', () => {
- element._relatedResponse = {changes};
- element._submittedTogether = {changes};
- element._conflicts = changes;
- element._cherryPicks = changes;
- element._sameTopic = changes;
-
- element.hidden = false;
- element.clear();
- assert.isTrue(element.hidden);
- assert.equal(element._relatedResponse.changes.length, 0);
- assert.equal(element._submittedTogether.changes.length, 0);
- assert.equal(element._conflicts.length, 0);
- assert.equal(element._cherryPicks.length, 0);
- assert.equal(element._sameTopic.length, 0);
- });
-
- test('update fires', () => {
- const updateHandler = sinon.stub();
- element.addEventListener('update', updateHandler);
-
- element._resultsChanged({}, {}, [], [], []);
- assert.isTrue(element.hidden);
- assert.isFalse(updateHandler.called);
-
- element._resultsChanged({}, {}, [], [], ['test']);
- assert.isFalse(element.hidden);
- assert.isTrue(updateHandler.called);
- updateHandler.reset();
-
- element._resultsChanged(
- {}, {changes: [], non_visible_changes: 0}, [], [], []);
- assert.isTrue(element.hidden);
- assert.isFalse(updateHandler.called);
-
- element._resultsChanged(
- {}, {changes: ['test'], non_visible_changes: 0}, [], [], []);
- assert.isFalse(element.hidden);
- assert.isTrue(updateHandler.called);
- updateHandler.reset();
-
- element._resultsChanged(
- {}, {changes: [], non_visible_changes: 1}, [], [], []);
- assert.isFalse(element.hidden);
- assert.isTrue(updateHandler.called);
- });
-
- suite('hiding and unhiding', () => {
- test('related response', () => {
- assert.isTrue(element.hidden);
- element._resultsChanged({changes}, {}, [], [], []);
- assert.isFalse(element.hidden);
- });
-
- test('submitted together', () => {
- assert.isTrue(element.hidden);
- element._resultsChanged({}, {changes}, [], [], []);
- assert.isFalse(element.hidden);
- });
-
- test('conflicts', () => {
- assert.isTrue(element.hidden);
- element._resultsChanged({}, {}, changes, [], []);
- assert.isFalse(element.hidden);
- });
-
- test('cherrypicks', () => {
- assert.isTrue(element.hidden);
- element._resultsChanged({}, {}, [], changes, []);
- assert.isFalse(element.hidden);
- });
-
- test('same topic', () => {
- assert.isTrue(element.hidden);
- element._resultsChanged({}, {}, [], [], changes);
- assert.isFalse(element.hidden);
- });
- });
- });
-
- test('_computeChangeURL uses GerritNav', () => {
- const getUrlStub = sinon.stub(GerritNav, 'getUrlForChangeById');
- element._computeChangeURL(123, 'abc/def', 12);
- assert.isTrue(getUrlStub.called);
- });
-
- suite('submitted together changes', () => {
- const change = {
- project: 'foo/bar',
- change_id: 'Ideadbeef',
- commit: {
- commit: 'deadbeef',
- parents: [{commit: 'abc123'}],
- author: {},
- subject: 'do that thing',
- },
- _change_number: 12345,
- _revision_number: 1,
- _current_revision_number: 1,
- status: 'NEW',
- };
-
- test('_computeSubmittedTogetherClass', () => {
- assert.strictEqual(
- element._computeSubmittedTogetherClass(undefined),
- 'hidden');
- assert.strictEqual(
- element._computeSubmittedTogetherClass({changes: []}),
- 'hidden');
- assert.strictEqual(
- element._computeSubmittedTogetherClass({changes: [{}]}),
- '');
- assert.strictEqual(
- element._computeSubmittedTogetherClass({
- changes: [],
- non_visible_changes: 0,
- }),
- 'hidden');
- assert.strictEqual(
- element._computeSubmittedTogetherClass({
- changes: [],
- non_visible_changes: 1,
- }),
- '');
- assert.strictEqual(
- element._computeSubmittedTogetherClass({
- changes: [{}],
- non_visible_changes: 1,
- }),
- '');
- });
-
- test('no submitted together changes', () => {
- flush();
- assert.include(element.$.submittedTogether.className, 'hidden');
- });
-
- test('no non-visible submitted together changes', () => {
- element._submittedTogether = {changes: [change]};
- flush();
- assert.notInclude(element.$.submittedTogether.className, 'hidden');
- assert.isNull(element.shadowRoot
- .querySelector('.note'));
- });
-
- test('no visible submitted together changes', () => {
- // Technically this should never happen, but worth asserting the logic.
- element._submittedTogether = {changes: [], non_visible_changes: 1};
- flush();
- assert.notInclude(element.$.submittedTogether.className, 'hidden');
- assert.isNotNull(element.shadowRoot
- .querySelector('.note'));
- assert.strictEqual(
- element.shadowRoot
- .querySelector('.note').innerText.trim(),
- '(+ 1 non-visible change)');
- });
-
- test('visible and non-visible submitted together changes', () => {
- element._submittedTogether = {changes: [change], non_visible_changes: 2};
- flush();
- assert.notInclude(element.$.submittedTogether.className, 'hidden');
- assert.isNotNull(element.shadowRoot
- .querySelector('.note'));
- assert.strictEqual(
- element.shadowRoot
- .querySelector('.note').innerText.trim(),
- '(+ 2 non-visible changes)');
- });
- });
-});
-
-suite('gr-related-changes-list plugin tests', () => {
- let element;
-
- setup(() => {
- resetPlugins();
- element = basicFixture.instantiate();
- });
-
- teardown(() => {
- resetPlugins();
- });
-
- test('endpoint params', done => {
- element.change = {labels: {}};
- let hookEl;
- let plugin;
- pluginApi.install(
- p => {
- plugin = p;
- plugin.hook('related-changes-section').getLastAttached()
- .then(el => hookEl = el);
- },
- '0.1',
- 'http://some/plugins/url1.html');
- getPluginLoader().loadPlugins([]);
- flush(() => {
- assert.strictEqual(hookEl.plugin, plugin);
- assert.strictEqual(hookEl.change, element.change);
- done();
- });
- });
-
- test('hiding and unhiding', done => {
- element.change = {labels: {}};
- let hookEl;
- let plugin;
-
- // No changes, and no plugin. The element is still hidden.
- element._resultsChanged({}, {}, [], [], []);
- assert.isTrue(element.hidden);
- pluginApi.install(
- p => {
- plugin = p;
- plugin.hook('related-changes-section').getLastAttached()
- .then(el => hookEl = el);
- },
- '0.1',
- 'http://some/plugins/url2.html');
- getPluginLoader().loadPlugins([]);
- flush(() => {
- // No changes, and plugin without hidden attribute. So it's visible.
- element._resultsChanged({}, {}, [], [], []);
- assert.isFalse(element.hidden);
-
- // No changes, but plugin with true hidden attribute. So it's invisible.
- hookEl.hidden = true;
-
- element._resultsChanged({}, {}, [], [], []);
- assert.isTrue(element.hidden);
-
- // No changes, and plugin with false hidden attribute. So it's visible.
- hookEl.hidden = false;
- element._resultsChanged({}, {}, [], [], []);
- assert.isFalse(element.hidden);
-
- // Hiding triggered by plugin itself
- hookEl.hidden = true;
- hookEl.dispatchEvent(new CustomEvent('new-section-loaded', {
- composed: true, bubbles: true,
- }));
- assert.isTrue(element.hidden);
-
- // Unhiding triggered by plugin itself
- hookEl.hidden = false;
- hookEl.dispatchEvent(new CustomEvent('new-section-loaded', {
- composed: true, bubbles: true,
- }));
- assert.isFalse(element.hidden);
-
- // Hiding plugin keeps list visible, if there are changes
- hookEl.hidden = false;
- element._sameTopic = ['test'];
- element._resultsChanged({}, {}, [], [], ['test']);
- assert.isFalse(element.hidden);
- hookEl.hidden = true;
- hookEl.dispatchEvent(new CustomEvent('new-section-loaded', {
- composed: true, bubbles: true,
- }));
- assert.isFalse(element.hidden);
-
- done();
- });
- });
-});
-
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts
new file mode 100644
index 0000000..c5b3a58
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts
@@ -0,0 +1,853 @@
+/**
+ * @license
+ * Copyright (C) 2016 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.
+ */
+
+import {ChangeStatus} from '../../../constants/constants';
+import '../../../test/common-test-setup-karma';
+import {
+ createChange,
+ createCommit,
+ createCommitInfoWithRequiredCommit,
+ createParsedChange,
+ createRelatedChangeAndCommitInfo,
+ createRevision,
+} from '../../../test/test-data-generators';
+import {
+ ChangeId,
+ ChangeInfo,
+ CommitId,
+ NumericChangeId,
+ PatchSetNum,
+ RelatedChangeAndCommitInfo,
+ RepoName,
+} from '../../../types/common';
+import {ParsedChangeInfo} from '../../../types/types';
+import './gr-related-changes-list';
+import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
+import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit';
+import {
+ query,
+ queryAndAssert,
+ resetPlugins,
+ stubRestApi,
+} from '../../../test/test-utils';
+import {GrRelatedChangesList} from './gr-related-changes-list';
+import {SinonStubbedMember} from 'sinon/pkg/sinon-esm';
+import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
+import {PluginApi} from '../../../api/plugin';
+import {GrEndpointDecorator} from '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+import {_testOnly_resetEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
+
+const pluginApi = _testOnly_initGerritPluginApi();
+
+const basicFixture = fixtureFromElement('gr-related-changes-list');
+
+suite('gr-related-changes-list tests', () => {
+ let element: GrRelatedChangesList;
+
+ setup(() => {
+ // Since pluginEndpoints are global, must reset state.
+ _testOnly_resetEndpoints();
+ element = basicFixture.instantiate();
+ });
+
+ test('connected revisions', () => {
+ const change: ParsedChangeInfo = {
+ ...createParsedChange(),
+ revisions: {
+ e3c6d60783bfdec9ebae7dcfec4662360433449e: createRevision(1),
+ '26e5e4c9c7ae31cbd876271cca281ce22b413997': createRevision(2),
+ bf7884d695296ca0c91702ba3e2bc8df0f69a907: createRevision(7),
+ b5fc49f2e67d1889d5275cac04ad3648f2ec7fe3: createRevision(5),
+ d6bcee67570859ccb684873a85cf50b1f0e96fda: createRevision(6),
+ cc960918a7f90388f4a9e05753d0f7b90ad44546: createRevision(3),
+ '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6': createRevision(4),
+ },
+ };
+ let patchNum = 7 as PatchSetNum;
+ let relatedChanges: RelatedChangeAndCommitInfo[] = [
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ '2cebeedfb1e80f4b872d0a13ade529e70652c0c8'
+ ),
+ parents: [
+ {
+ commit: '87ed20b241576b620bbaa3dfd47715ce6782b7dd' as CommitId,
+ subject: 'subject1',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ '87ed20b241576b620bbaa3dfd47715ce6782b7dd'
+ ),
+ parents: [
+ {
+ commit: '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb' as CommitId,
+ subject: 'subject2',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb'
+ ),
+ parents: [
+ {
+ commit: 'b0ccb183494a8e340b8725a2dc553967d61e6dae' as CommitId,
+ subject: 'subject3',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ 'b0ccb183494a8e340b8725a2dc553967d61e6dae'
+ ),
+ parents: [
+ {
+ commit: 'bf7884d695296ca0c91702ba3e2bc8df0f69a907' as CommitId,
+ subject: 'subject4',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ 'bf7884d695296ca0c91702ba3e2bc8df0f69a907'
+ ),
+ parents: [
+ {
+ commit: '613bc4f81741a559c6667ac08d71dcc3348f73ce' as CommitId,
+ subject: 'subject5',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ '613bc4f81741a559c6667ac08d71dcc3348f73ce'
+ ),
+ parents: [
+ {
+ commit: '455ed9cd27a16bf6991f04dcc57ef575dc4d5e75' as CommitId,
+ subject: 'subject6',
+ },
+ ],
+ },
+ },
+ ];
+
+ let connectedChanges = element._computeConnectedRevisions(
+ change,
+ patchNum,
+ relatedChanges
+ );
+ assert.deepEqual(connectedChanges, [
+ '613bc4f81741a559c6667ac08d71dcc3348f73ce',
+ 'bf7884d695296ca0c91702ba3e2bc8df0f69a907',
+ 'bf7884d695296ca0c91702ba3e2bc8df0f69a907',
+ 'b0ccb183494a8e340b8725a2dc553967d61e6dae',
+ '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb',
+ '87ed20b241576b620bbaa3dfd47715ce6782b7dd',
+ '2cebeedfb1e80f4b872d0a13ade529e70652c0c8',
+ ]);
+
+ patchNum = 4 as PatchSetNum;
+ relatedChanges = [
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ '2cebeedfb1e80f4b872d0a13ade529e70652c0c8'
+ ),
+ parents: [
+ {
+ commit: '87ed20b241576b620bbaa3dfd47715ce6782b7dd' as CommitId,
+ subject: 'My parent commit',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ '87ed20b241576b620bbaa3dfd47715ce6782b7dd'
+ ),
+ parents: [
+ {
+ commit: '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb' as CommitId,
+ subject: 'My parent commit',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb'
+ ),
+ parents: [
+ {
+ commit: 'b0ccb183494a8e340b8725a2dc553967d61e6dae' as CommitId,
+ subject: 'My parent commit',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ 'a3e5d9d4902b915a39e2efba5577211b9b3ebe7b'
+ ),
+ parents: [
+ {
+ commit: '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6' as CommitId,
+ subject: 'My parent commit',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6'
+ ),
+ parents: [
+ {
+ commit: 'af815dac54318826b7f1fa468acc76349ffc588e' as CommitId,
+ subject: 'My parent commit',
+ },
+ ],
+ },
+ },
+ {
+ ...createRelatedChangeAndCommitInfo(),
+ commit: {
+ ...createCommitInfoWithRequiredCommit(
+ 'af815dac54318826b7f1fa468acc76349ffc588e'
+ ),
+ parents: [
+ {
+ commit: '58f76e406e24cb8b0f5d64c7f5ac1e8616d0a22c' as CommitId,
+ subject: 'My parent commit',
+ },
+ ],
+ },
+ },
+ ];
+
+ connectedChanges = element._computeConnectedRevisions(
+ change,
+ patchNum,
+ relatedChanges
+ );
+ assert.deepEqual(connectedChanges, [
+ 'af815dac54318826b7f1fa468acc76349ffc588e',
+ '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6',
+ '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6',
+ 'a3e5d9d4902b915a39e2efba5577211b9b3ebe7b',
+ ]);
+ });
+
+ test('_changesEqual', () => {
+ const change1: ChangeInfo = {
+ ...createChange(),
+ change_id: '123' as ChangeId,
+ _number: 0 as NumericChangeId,
+ };
+ const change2: ChangeInfo = {
+ ...createChange(),
+ change_id: '456' as ChangeId,
+ _number: 1 as NumericChangeId,
+ };
+ const change3: ChangeInfo = {
+ ...createChange(),
+ change_id: '123' as ChangeId,
+ _number: 2 as NumericChangeId,
+ };
+ const change4: RelatedChangeAndCommitInfo = {
+ ...createRelatedChangeAndCommitInfo(),
+ change_id: '123' as ChangeId,
+ _change_number: 1 as NumericChangeId,
+ };
+
+ assert.isTrue(element._changesEqual(change1, change1));
+ assert.isFalse(element._changesEqual(change1, change2));
+ assert.isFalse(element._changesEqual(change1, change3));
+ assert.isTrue(element._changesEqual(change2, change4));
+ });
+
+ test('_getChangeNumber', () => {
+ const change1: ChangeInfo = {
+ ...createChange(),
+ change_id: '123' as ChangeId,
+ _number: 0 as NumericChangeId,
+ };
+ const change2: ChangeInfo = {
+ ...createChange(),
+ change_id: '456' as ChangeId,
+ _number: 1 as NumericChangeId,
+ };
+ assert.equal(element._getChangeNumber(change1), 0);
+ assert.equal(element._getChangeNumber(change2), 1);
+ });
+
+ test('event for section loaded fires for each section ', () => {
+ const loadedStub = sinon.stub();
+ element.patchNum = 7 as PatchSetNum;
+ element.change = {
+ ...createParsedChange(),
+ change_id: '123' as ChangeId,
+ status: ChangeStatus.NEW,
+ };
+ element.mergeable = true;
+ element.addEventListener('new-section-loaded', loadedStub);
+
+ return element.reload().then(() => {
+ assert.equal(loadedStub.callCount, 4);
+ });
+ });
+
+ suite('getChangeConflicts resolves undefined', () => {
+ let element: GrRelatedChangesList;
+
+ setup(() => {
+ element = basicFixture.instantiate();
+ });
+
+ test('_conflicts are an empty array', () => {
+ element.patchNum = 7 as PatchSetNum;
+ element.change = {
+ ...createParsedChange(),
+ change_id: '123' as ChangeId,
+ status: ChangeStatus.NEW,
+ };
+ element.mergeable = true;
+ element.reload();
+ assert.equal(element._conflicts.length, 0);
+ });
+ });
+
+ suite('get conflicts tests', () => {
+ let element: GrRelatedChangesList;
+ let conflictsStub: SinonStubbedMember<RestApiService['getChangeConflicts']>;
+
+ setup(() => {
+ element = basicFixture.instantiate();
+ conflictsStub = stubRestApi('getChangeConflicts').returns(
+ Promise.resolve(undefined)
+ );
+ });
+
+ test('request conflicts if open and mergeable', () => {
+ element.patchNum = 7 as PatchSetNum;
+ element.change = {
+ ...createParsedChange(),
+ change_id: '123' as ChangeId,
+ status: ChangeStatus.NEW,
+ };
+ element.mergeable = true;
+ element.reload();
+ assert.isTrue(conflictsStub.called);
+ });
+
+ test('does not request conflicts if closed and mergeable', () => {
+ element.patchNum = 7 as PatchSetNum;
+ element.change = {
+ ...createParsedChange(),
+ change_id: '123' as ChangeId,
+ status: ChangeStatus.NEW,
+ };
+ element.reload();
+ assert.isFalse(conflictsStub.called);
+ });
+
+ test('does not request conflicts if open and not mergeable', () => {
+ element.patchNum = 7 as PatchSetNum;
+ element.change = {
+ ...createParsedChange(),
+ change_id: '123' as ChangeId,
+ status: ChangeStatus.NEW,
+ };
+ element.mergeable = false;
+ element.reload();
+ assert.isFalse(conflictsStub.called);
+ });
+
+ test('doesnt request conflicts if closed and not mergeable', () => {
+ element.patchNum = 7 as PatchSetNum;
+ element.change = {
+ ...createParsedChange(),
+ change_id: '123' as ChangeId,
+ status: ChangeStatus.NEW,
+ };
+ element.mergeable = false;
+ element.reload();
+ assert.isFalse(conflictsStub.called);
+ });
+ });
+
+ test('_calculateHasParent', () => {
+ const changeId = '123' as ChangeId;
+ const relatedChanges: RelatedChangeAndCommitInfo[] = [];
+
+ assert.equal(element._calculateHasParent(changeId, relatedChanges), false);
+
+ relatedChanges.push({
+ ...createRelatedChangeAndCommitInfo(),
+ change_id: '123' as ChangeId,
+ });
+ assert.equal(element._calculateHasParent(changeId, relatedChanges), false);
+
+ relatedChanges.push({
+ ...createRelatedChangeAndCommitInfo(),
+ change_id: '234' as ChangeId,
+ });
+ assert.equal(element._calculateHasParent(changeId, relatedChanges), true);
+ });
+
+ suite('hidden attribute and update event', () => {
+ const changes: ChangeInfo[] = [
+ {
+ ...createChange(),
+ project: 'foo/bar' as RepoName,
+ change_id: 'Ideadbeef' as ChangeId,
+ status: ChangeStatus.NEW,
+ },
+ ];
+ const relatedChanges: RelatedChangeAndCommitInfo[] = [
+ {
+ ...createCommitInfoWithRequiredCommit(),
+ project: 'foo/bar' as RepoName,
+ change_id: 'Ideadbeef' as ChangeId,
+ commit: {
+ ...createCommit(),
+ commit: 'deadbeef' as CommitId,
+ parents: [
+ {
+ commit: 'abc123' as CommitId,
+ subject: 'abc123',
+ },
+ ],
+ subject: 'do that thing',
+ },
+ _change_number: 12345 as NumericChangeId,
+ _revision_number: 1,
+ _current_revision_number: 1,
+ status: ChangeStatus.NEW,
+ },
+ ];
+
+ test('clear and empties', () => {
+ element._relatedResponse = {changes: relatedChanges};
+ element._submittedTogether = {
+ changes,
+ non_visible_changes: 0,
+ };
+ element._conflicts = changes;
+ element._cherryPicks = changes;
+ element._sameTopic = changes;
+
+ element.hidden = false;
+ element.clear();
+ assert.isTrue(element.hidden);
+ assert.equal(element._relatedResponse.changes.length, 0);
+ assert.equal(element._submittedTogether?.changes.length, 0);
+ assert.equal(element._conflicts.length, 0);
+ assert.equal(element._cherryPicks.length, 0);
+ assert.equal(element._sameTopic?.length, 0);
+ });
+
+ test('update fires', () => {
+ const updateHandler = sinon.stub();
+ element.addEventListener('update', updateHandler);
+
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ []
+ );
+ assert.isTrue(element.hidden);
+ assert.isFalse(updateHandler.called);
+
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ changes
+ );
+ assert.isFalse(element.hidden);
+ assert.isTrue(updateHandler.called);
+ updateHandler.reset();
+
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ []
+ );
+ assert.isTrue(element.hidden);
+ assert.isFalse(updateHandler.called);
+
+ element._resultsChanged(
+ {changes: []},
+ {changes, non_visible_changes: 0},
+ [],
+ [],
+ []
+ );
+ assert.isFalse(element.hidden);
+ assert.isTrue(updateHandler.called);
+ updateHandler.reset();
+
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 1},
+ [],
+ [],
+ []
+ );
+ assert.isFalse(element.hidden);
+ assert.isTrue(updateHandler.called);
+ });
+
+ suite('hiding and unhiding', () => {
+ test('related response', () => {
+ assert.isTrue(element.hidden);
+ element._resultsChanged(
+ {changes: relatedChanges},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ []
+ );
+ assert.isFalse(element.hidden);
+ });
+
+ test('submitted together', () => {
+ assert.isTrue(element.hidden);
+ element._resultsChanged(
+ {changes: []},
+ {changes, non_visible_changes: 0},
+ [],
+ [],
+ []
+ );
+ assert.isFalse(element.hidden);
+ });
+
+ test('conflicts', () => {
+ assert.isTrue(element.hidden);
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ changes,
+ [],
+ []
+ );
+ assert.isFalse(element.hidden);
+ });
+
+ test('cherrypicks', () => {
+ assert.isTrue(element.hidden);
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ changes,
+ []
+ );
+ assert.isFalse(element.hidden);
+ });
+
+ test('same topic', () => {
+ assert.isTrue(element.hidden);
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ changes
+ );
+ assert.isFalse(element.hidden);
+ });
+ });
+ });
+
+ test('_computeChangeURL uses GerritNav', () => {
+ const getUrlStub = sinon.stub(GerritNav, 'getUrlForChangeById');
+ element._computeChangeURL(
+ 123 as NumericChangeId,
+ 'abc/def' as RepoName,
+ 12 as PatchSetNum
+ );
+ assert.isTrue(getUrlStub.called);
+ });
+
+ suite('submitted together changes', () => {
+ const change: ChangeInfo = {
+ ...createChange(),
+ project: 'foo/bar' as RepoName,
+ change_id: 'Ideadbeef' as ChangeId,
+ status: ChangeStatus.NEW,
+ };
+
+ test('_computeSubmittedTogetherClass', () => {
+ assert.strictEqual(
+ element._computeSubmittedTogetherClass(undefined),
+ 'hidden'
+ );
+ assert.strictEqual(
+ element._computeSubmittedTogetherClass({
+ changes: [],
+ non_visible_changes: 0,
+ }),
+ 'hidden'
+ );
+ assert.strictEqual(
+ element._computeSubmittedTogetherClass({
+ changes: [change],
+ non_visible_changes: 0,
+ }),
+ ''
+ );
+ assert.strictEqual(
+ element._computeSubmittedTogetherClass({
+ changes: [],
+ non_visible_changes: 0,
+ }),
+ 'hidden'
+ );
+ assert.strictEqual(
+ element._computeSubmittedTogetherClass({
+ changes: [],
+ non_visible_changes: 1,
+ }),
+ ''
+ );
+ assert.strictEqual(
+ element._computeSubmittedTogetherClass({
+ changes: [],
+ non_visible_changes: 1,
+ }),
+ ''
+ );
+ });
+
+ test('no submitted together changes', () => {
+ flush();
+ assert.include(element.$.submittedTogether.className, 'hidden');
+ });
+
+ test('no non-visible submitted together changes', () => {
+ element._submittedTogether = {changes: [change], non_visible_changes: 0};
+ flush();
+ assert.notInclude(element.$.submittedTogether.className, 'hidden');
+ assert.isUndefined(query(element, '.note'));
+ });
+
+ test('no visible submitted together changes', () => {
+ // Technically this should never happen, but worth asserting the logic.
+ element._submittedTogether = {changes: [], non_visible_changes: 1};
+ flush();
+ assert.notInclude(element.$.submittedTogether.className, 'hidden');
+ assert.strictEqual(
+ queryAndAssert<HTMLDivElement>(element, '.note').innerText.trim(),
+ '(+ 1 non-visible change)'
+ );
+ });
+
+ test('visible and non-visible submitted together changes', () => {
+ element._submittedTogether = {changes: [change], non_visible_changes: 2};
+ flush();
+ assert.notInclude(element.$.submittedTogether.className, 'hidden');
+ assert.strictEqual(
+ queryAndAssert<HTMLDivElement>(element, '.note').innerText.trim(),
+ '(+ 2 non-visible changes)'
+ );
+ });
+ });
+
+ suite('gr-related-changes-list plugin tests', () => {
+ let element: GrRelatedChangesList;
+
+ setup(() => {
+ resetPlugins();
+ element = basicFixture.instantiate();
+ });
+
+ teardown(() => {
+ resetPlugins();
+ });
+
+ test('endpoint params', done => {
+ element.change = {...createParsedChange(), labels: {}};
+ interface RelatedChangesListGrEndpointDecorator
+ extends GrEndpointDecorator {
+ plugin: PluginApi;
+ change: ParsedChangeInfo;
+ }
+ let hookEl: RelatedChangesListGrEndpointDecorator;
+ let plugin: PluginApi;
+ pluginApi.install(
+ p => {
+ plugin = p;
+ plugin
+ .hook('related-changes-section')
+ .getLastAttached()
+ .then(el => (hookEl = el as RelatedChangesListGrEndpointDecorator));
+ },
+ '0.1',
+ 'http://some/plugins/url1.html'
+ );
+ getPluginLoader().loadPlugins([]);
+ flush(() => {
+ assert.strictEqual(hookEl.plugin, plugin);
+ assert.strictEqual(hookEl.change, element.change);
+ done();
+ });
+ });
+ });
+
+ test('hiding and unhiding', done => {
+ element.change = {...createParsedChange(), labels: {}};
+ let hookEl: HTMLElement;
+ let plugin;
+
+ // No changes, and no plugin. The element is still hidden.
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ []
+ );
+ assert.isTrue(element.hidden);
+ pluginApi.install(
+ p => {
+ plugin = p;
+ plugin
+ .hook('related-changes-section')
+ .getLastAttached()
+ .then(el => (hookEl = el));
+ },
+ '0.1',
+ 'http://some/plugins/url2.html'
+ );
+ getPluginLoader().loadPlugins([]);
+ flush(() => {
+ // No changes, and plugin without hidden attribute. So it's visible.
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ []
+ );
+ assert.isFalse(element.hidden);
+
+ // No changes, but plugin with true hidden attribute. So it's invisible.
+ hookEl.hidden = true;
+
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ []
+ );
+ assert.isTrue(element.hidden);
+
+ // No changes, and plugin with false hidden attribute. So it's visible.
+ hookEl.hidden = false;
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ []
+ );
+ assert.isFalse(element.hidden);
+
+ // Hiding triggered by plugin itself
+ hookEl.hidden = true;
+ hookEl.dispatchEvent(
+ new CustomEvent('new-section-loaded', {
+ composed: true,
+ bubbles: true,
+ })
+ );
+ assert.isTrue(element.hidden);
+
+ // Unhiding triggered by plugin itself
+ hookEl.hidden = false;
+ hookEl.dispatchEvent(
+ new CustomEvent('new-section-loaded', {
+ composed: true,
+ bubbles: true,
+ })
+ );
+ assert.isFalse(element.hidden);
+
+ // Hiding plugin keeps list visible, if there are changes
+ hookEl.hidden = false;
+ const change = createChange();
+ element._sameTopic = [change];
+ element._resultsChanged(
+ {changes: []},
+ {changes: [], non_visible_changes: 0},
+ [],
+ [],
+ [change]
+ );
+ assert.isFalse(element.hidden);
+ hookEl.hidden = true;
+ hookEl.dispatchEvent(
+ new CustomEvent('new-section-loaded', {
+ composed: true,
+ bubbles: true,
+ })
+ );
+ assert.isFalse(element.hidden);
+
+ done();
+ });
+ });
+});
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
index b09510ba..c16ed12 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
@@ -16,12 +16,11 @@
*/
import '../../../test/common-test-setup-karma.js';
-import {resetPlugins} from '../../../test/test-utils.js';
+import {resetPlugins, stubRestApi} from '../../../test/test-utils.js';
import './gr-reply-dialog.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
-import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-reply-dialog');
const pluginApi = _testOnly_initGerritPluginApi();
@@ -75,7 +74,6 @@
changeNum = 42;
patchNum = 1;
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('getAccount').returns(Promise.resolve({_account_id: 42}));
element = basicFixture.instantiate();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index a8e89b6..a735147 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -26,7 +26,6 @@
import '../gr-label-scores/gr-label-scores';
import '../gr-thread-list/gr-thread-list';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-reply-dialog_html';
@@ -96,7 +95,7 @@
assertNever,
containsAll,
} from '../../../utils/common-util';
-import {CommentThread} from '../../../utils/comment-util';
+import {CommentThread, isUnresolved} from '../../../utils/comment-util';
import {GrTextarea} from '../../shared/gr-textarea/gr-textarea';
import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
@@ -107,7 +106,6 @@
getApprovalInfo,
getMaxAccounts,
} from '../../../utils/label-util';
-import {isUnresolved} from '../../../utils/comment-util';
import {pluralize} from '../../../utils/string-util';
import {fireAlert, fireEvent, fireServerError} from '../../../utils/event-util';
import {ErrorCallback} from '../../../api/rest';
@@ -173,7 +171,7 @@
@customElement('gr-reply-dialog')
export class GrReplyDialog extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -395,8 +393,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
((IronA11yAnnouncer as unknown) as FixIronA11yAnnouncer).requestAvailability();
this._getAccount().then(account => {
if (account) this._account = account;
@@ -429,8 +427,9 @@
}
/** @override */
- detached() {
+ disconnectedCallback() {
this.cancelDebouncer(DEBOUNCER_STORE);
+ super.disconnectedCallback();
}
open(focusTarget?: FocusTarget) {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js
index 0575239..466f7bd 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js
@@ -65,7 +65,6 @@
changeNum = 42;
patchNum = 1;
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('getAccount').returns(Promise.resolve({}));
stubRestApi('getChange').returns(Promise.resolve({}));
stubRestApi('getChangeSuggestedReviewers').returns(Promise.resolve([]));
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
index 30931c3..ec895d9 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
@@ -18,7 +18,6 @@
import '../../shared/gr-button/gr-button';
import '../../../styles/shared-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-reviewer-list_html';
@@ -45,9 +44,7 @@
import {KnownExperimentId} from '../../../services/flags/flags';
@customElement('gr-reviewer-list')
-export class GrReviewerList extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrReviewerList extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.js b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.js
index f77f736..83b7794 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.js
@@ -28,7 +28,6 @@
element = basicFixture.instantiate();
element.serverConfig = {};
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('removeChangeReviewer').returns(Promise.resolve({ok: true}));
});
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
index f0ec7cd..b7592b0 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
@@ -18,7 +18,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-comment-thread/gr-comment-thread';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-thread-list_html';
@@ -57,9 +56,7 @@
}
@customElement('gr-thread-list')
-export class GrThreadList extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrThreadList extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.ts b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.ts
index d52da80..161bc2f 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.ts
@@ -17,7 +17,6 @@
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-shell-command/gr-shell-command';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-upload-help-dialog_html';
@@ -32,9 +31,7 @@
const PREFERRED_FETCH_COMMAND_ORDER = ['checkout', 'cherry pick', 'pull'];
@customElement('gr-upload-help-dialog')
-export class GrUploadHelpDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrUploadHelpDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -69,8 +66,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.restApiService
.getLoggedIn()
.then(loggedIn =>
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_html.ts
index d44cbb0..d633de5 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_html.ts
@@ -43,7 +43,10 @@
the files.
</p>
<template is="dom-if" if="[[_fetchCommand]]">
- <gr-shell-command command="[[_fetchCommand]]"></gr-shell-command>
+ <gr-shell-command
+ class="fetch-command"
+ command="[[_fetchCommand]]"
+ ></gr-shell-command>
</template>
</li>
<li>
@@ -51,14 +54,20 @@
Update the local commit with your modifications using the following
command.
</p>
- <gr-shell-command command="[[_commitCommand]]"></gr-shell-command>
+ <gr-shell-command
+ class="commit-command"
+ command="[[_commitCommand]]"
+ ></gr-shell-command>
<p>
Leave the "Change-Id:" line of the commit message as is.
</p>
</li>
<li>
<p>Push the updated commit to Gerrit.</p>
- <gr-shell-command command="[[_pushCommand]]"></gr-shell-command>
+ <gr-shell-command
+ class="push-command"
+ command="[[_pushCommand]]"
+ ></gr-shell-command>
</li>
<li>
<p>Refresh this page to view the the update.</p>
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index ef2430c..bf37e20 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -24,6 +24,7 @@
query,
} from 'lit-element';
import {GrLitElement} from '../lit/gr-lit-element';
+import '@polymer/paper-tooltip/paper-tooltip';
import {
Category,
CheckRun,
@@ -35,14 +36,17 @@
import {sharedStyles} from '../../styles/shared-styles';
import {RunResult} from '../../services/checks/checks-model';
import {
- hasCompleted,
+ allResults,
hasCompletedWithoutResults,
+ hasResultsOf,
iconForCategory,
- isRunning,
} from '../../services/checks/checks-util';
import {assertIsDefined} from '../../utils/common-util';
import {whenVisible} from '../../utils/dom-util';
import {durationString} from '../../utils/date-util';
+import {charsOnly, pluralize} from '../../utils/string-util';
+import {fireRunSelectionReset} from './gr-checks-util';
+import {ChecksTabState} from '../../types/events';
@customElement('gr-result-row')
class GrResultRow extends GrLitElement {
@@ -229,6 +233,7 @@
}
renderLink(link: Link) {
+ const tooltipText = link.tooltip ?? 'Link to details';
return html`
<a href="${link.url}" target="_blank">
<iron-icon
@@ -236,6 +241,7 @@
class="launch"
icon="gr-icons:launch"
></iron-icon>
+ <paper-tooltip offset="5">${tooltipText}</paper-tooltip>
</a>
`;
}
@@ -303,8 +309,31 @@
@property()
runs: CheckRun[] = [];
+ @property()
+ tabState?: ChecksTabState;
+
+ /**
+ * How many runs are selected in the runs panel?
+ * If 0, then the `runs` property contains all the runs there are.
+ * If >0, then it only contains the data of certain selected runs.
+ */
+ @property()
+ selectedRunsCount = 0;
+
+ /**
+ * This is the current state of whether a section is expanded or not. As long
+ * as isSectionExpandedByUser is false this will be computed by a default rule
+ * on every render.
+ */
private isSectionExpanded = new Map<Category | 'SUCCESS', boolean>();
+ /**
+ * Keeps track of whether the user intentionally changed the expansion state.
+ * Once this is true the default rule for showing a section expanded or not
+ * is not applied anymore.
+ */
+ private isSectionExpandedByUser = new Map<Category | 'SUCCESS', boolean>();
+
static get styles() {
return [
sharedStyles,
@@ -313,17 +342,32 @@
display: block;
padding: var(--spacing-xl);
}
- input#filterInput {
+ .filterDiv {
+ display: flex;
margin-top: var(--spacing-s);
+ align-items: center;
+ }
+ .filterDiv input#filterInput {
padding: var(--spacing-s) var(--spacing-m);
min-width: 400px;
}
+ .filterDiv .selection {
+ padding: var(--spacing-s) var(--spacing-m);
+ }
+ .filterDiv iron-icon.filter {
+ color: var(--selected-foreground);
+ }
+ .filterDiv gr-button.reset {
+ margin: calc(0px - var(--spacing-s)) var(--spacing-l);
+ }
.categoryHeader {
margin-top: var(--spacing-l);
margin-left: var(--spacing-l);
- text-transform: capitalize;
cursor: default;
}
+ .categoryHeader .title {
+ text-transform: capitalize;
+ }
.categoryHeader .expandIcon {
width: var(--line-height-h3);
height: var(--line-height-h3);
@@ -345,6 +389,10 @@
.categoryHeader .statusIcon.success {
color: var(--success-foreground);
}
+ .categoryHeader .filtered {
+ color: var(--deemphasized-text-color);
+ }
+ .collapsed .noResultsMessage,
.collapsed table {
display: none;
}
@@ -352,8 +400,14 @@
border-bottom: 1px solid var(--border-color);
padding-bottom: var(--spacing-m);
}
- .noCompleted {
- margin-top: var(--spacing-l);
+ .noResultsMessage {
+ width: 100%;
+ max-width: 1280px;
+ margin-top: var(--spacing-m);
+ background-color: var(--background-color-primary);
+ box-shadow: var(--elevation-level-1);
+ padding: var(--spacing-s)
+ calc(20px + var(--spacing-l) + var(--spacing-m) + var(--spacing-s));
}
table.resultsTable {
width: 100%;
@@ -371,62 +425,115 @@
];
}
+ protected updated(changedProperties: PropertyValues) {
+ super.updated(changedProperties);
+ if (changedProperties.has('tabState') && this.tabState) {
+ const {statusOrCategory, checkName} = this.tabState;
+ if (
+ statusOrCategory &&
+ statusOrCategory !== RunStatus.RUNNING &&
+ statusOrCategory !== RunStatus.RUNNABLE
+ ) {
+ let cat = statusOrCategory.toString().toLowerCase();
+ if (statusOrCategory === RunStatus.COMPLETED) cat = 'success';
+ this.scrollElIntoView(`.categoryHeader .${cat}`);
+ } else if (checkName) {
+ this.scrollElIntoView(`gr-result-row.${charsOnly(checkName)}`);
+ }
+ }
+ }
+
+ scrollElIntoView(selector: string) {
+ this.updateComplete.then(() => {
+ let el = this.shadowRoot?.querySelector(selector);
+ // <gr-result-row> has display:contents and cannot be scrolled into view
+ // itself. Thus we are preferring to scroll the first child into view.
+ el = el?.shadowRoot?.firstElementChild ?? el;
+ el?.scrollIntoView({block: 'center'});
+ });
+ }
+
render() {
return html`
<div><h2 class="heading-2">Results</h2></div>
- ${this.renderFilter()} ${this.renderNoCompleted()}
- ${this.renderSection(Category.ERROR)}
+ ${this.renderFilter()} ${this.renderSection(Category.ERROR)}
${this.renderSection(Category.WARNING)}
${this.renderSection(Category.INFO)} ${this.renderSection('SUCCESS')}
`;
}
renderFilter() {
- if (this.runs.length === 0) return;
+ if (this.selectedRunsCount === 0 && allResults(this.runs).length <= 3) {
+ if (this.filterRegExp.source.length > 0) {
+ this.filterRegExp = new RegExp('');
+ }
+ return;
+ }
return html`
- <input
- id="filterInput"
- type="text"
- placeholder="Filter results by regular expression"
- @input="${this.onInput}"
- />
+ <div class="filterDiv">
+ <input
+ id="filterInput"
+ type="text"
+ placeholder="Filter results by regular expression"
+ @input="${this.onInput}"
+ />
+ <div class="selection">
+ ${this.renderSelectionFilter()}
+ </div>
+ </div>
`;
}
+ renderSelectionFilter() {
+ const count = this.selectedRunsCount;
+ if (count === 0) return;
+ return html`
+ <iron-icon class="filter" icon="gr-icons:filter"></iron-icon>
+ <span>Filtered by ${pluralize(count, 'run')}</span>
+ <gr-button link class="reset" @click="${this.handleClick}"
+ >Reset View</gr-button
+ >
+ `;
+ }
+
+ handleClick() {
+ this.filterRegExp = new RegExp('');
+ fireRunSelectionReset(this);
+ }
+
onInput() {
assertIsDefined(this.filterInput, 'filter <input> element');
this.filterRegExp = new RegExp(this.filterInput.value, 'i');
}
- renderNoCompleted() {
- if (this.runs.some(hasCompleted)) return;
- let text = 'No results';
- if (this.runs.some(isRunning)) {
- text = 'Checks are running ...';
- }
- return html`<div class="noCompleted">${text}</div>`;
- }
-
renderSection(category: Category | 'SUCCESS') {
const catString = category.toString().toLowerCase();
let runs = this.runs;
if (category === 'SUCCESS') {
- runs = runs
- .filter(hasCompletedWithoutResults)
- .filter(r => this.filterRegExp.test(r.checkName));
+ runs = runs.filter(hasCompletedWithoutResults);
} else {
- runs = runs.filter(r =>
- (r.results ?? []).some(res => res.category === category)
- );
+ runs = runs.filter(r => hasResultsOf(r, category));
}
- if (runs.length === 0) return;
- const expanded = this.isSectionExpanded.get(category) ?? true;
+ const all = runs.reduce((allResults: RunResult[], run) => {
+ return [...allResults, ...this.computeRunResults(category, run)];
+ }, []);
+ const filtered = all.filter(
+ result =>
+ this.filterRegExp.test(result.checkName) ||
+ this.filterRegExp.test(result.summary)
+ );
+ let expanded = this.isSectionExpanded.get(category);
+ const expandedByUser = this.isSectionExpandedByUser.get(category) ?? false;
+ if (!expandedByUser || expanded === undefined) {
+ expanded = all.length > 0;
+ this.isSectionExpanded.set(category, expanded);
+ }
const expandedClass = expanded ? 'expanded' : 'collapsed';
- const icon = expanded ? 'gr-icons:expand-more' : 'gr-icons:expand-less';
+ const icon = expanded ? 'gr-icons:expand-less' : 'gr-icons:expand-more';
return html`
<div class="${expandedClass}">
<h3
- class="categoryHeader heading-3"
+ class="categoryHeader ${catString} heading-3"
@click="${() => this.toggleExpanded(category)}"
>
<iron-icon class="expandIcon" icon="${icon}"></iron-icon>
@@ -434,50 +541,83 @@
icon="gr-icons:${iconForCategory(category)}"
class="statusIcon ${catString}"
></iron-icon>
- ${catString}
+ <span class="title">${catString}</span>
+ <span class="count">${this.renderCount(all, filtered)}</span>
</h3>
- <table class="resultsTable">
- <thead>
- <tr class="headerRow">
- <th class="iconCol"></th>
- <th class="nameCol">Run</th>
- <th class="summaryCol">Summary</th>
- <th class="expanderCol"></th>
- </tr>
- </thead>
- <tbody>
- ${runs.map(run =>
- category === 'SUCCESS'
- ? this.renderSuccessfulRun(run)
- : this.renderRun(category, run)
- )}
- </tbody>
- </table>
+ ${this.renderResults(all, filtered)}
</div>
`;
}
+ renderResults(all: RunResult[], filtered: RunResult[]) {
+ if (all.length === 0 && this.selectedRunsCount > 0) {
+ return html`<div class="noResultsMessage">
+ No results for this filtered view
+ </div>`;
+ }
+ if (all.length === 0) {
+ return html`<div class="noResultsMessage">No results</div>`;
+ }
+ if (filtered.length === 0) {
+ return html`<div class="noResultsMessage">
+ No results match the regular expression
+ </div>`;
+ }
+ return html`
+ <table class="resultsTable">
+ <thead>
+ <tr class="headerRow">
+ <th class="iconCol"></th>
+ <th class="nameCol">Run</th>
+ <th class="summaryCol">Summary</th>
+ <th class="expanderCol"></th>
+ </tr>
+ </thead>
+ <tbody>
+ ${filtered.map(
+ result => html`
+ <gr-result-row
+ class="${charsOnly(result.checkName)}"
+ .result="${result}"
+ ></gr-result-row>
+ `
+ )}
+ </tbody>
+ </table>
+ `;
+ }
+
+ renderCount(all: RunResult[], filtered: RunResult[]) {
+ if (this.selectedRunsCount > 0) {
+ return html`<span class="filtered"> - filtered</span>`;
+ }
+ if (all.length === filtered.length) {
+ return html`(${all.length})`;
+ } else {
+ return html`(${filtered.length} of ${all.length})`;
+ }
+ }
+
toggleExpanded(category: Category | 'SUCCESS') {
- const expanded = this.isSectionExpanded.get(category) ?? true;
+ const expanded = this.isSectionExpanded.get(category);
+ assertIsDefined(expanded, 'expanded must have been set in initial render');
this.isSectionExpanded.set(category, !expanded);
+ this.isSectionExpandedByUser.set(category, true);
this.requestUpdate();
}
- renderRun(category: Category, run: CheckRun) {
- return html`${run.results
- ?.filter(result => result.category === category)
- .filter(
- result =>
- this.filterRegExp.test(run.checkName) ||
- this.filterRegExp.test(result.summary)
- )
- .map(
- result =>
- html`<gr-result-row .result="${{...run, ...result}}"></gr-result-row>`
- )}`;
+ computeRunResults(category: Category | 'SUCCESS', run: CheckRun) {
+ if (category === 'SUCCESS') return [this.computeSuccessfulRunResult(run)];
+ return (
+ run.results
+ ?.filter(result => result.category === category)
+ .map(result => {
+ return {...run, ...result};
+ }) ?? []
+ );
}
- renderSuccessfulRun(run: CheckRun) {
+ computeSuccessfulRunResult(run: CheckRun): RunResult {
const adaptedRun: RunResult = {
category: Category.INFO, // will not be used, but is required
summary: run.statusDescription ?? '',
@@ -501,7 +641,7 @@
},
];
}
- return html`<gr-result-row .result="${adaptedRun}"></gr-result-row>`;
+ return adaptedRun;
}
}
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index 31f17724..1d661c3 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -21,6 +21,7 @@
customElement,
internalProperty,
property,
+ PropertyValues,
query,
} from 'lit-element';
import {GrLitElement} from '../lit/gr-lit-element';
@@ -45,28 +46,8 @@
} from '../../services/checks/checks-model';
import {assertIsDefined} from '../../utils/common-util';
import {whenVisible} from '../../utils/dom-util';
-
-export interface RunSelectedEventDetail {
- checkName: string;
-}
-
-export type RunSelectedEvent = CustomEvent<RunSelectedEventDetail>;
-
-declare global {
- interface HTMLElementEventMap {
- 'run-selected': RunSelectedEvent;
- }
-}
-
-function fireRunSelected(target: EventTarget, checkName: string) {
- target.dispatchEvent(
- new CustomEvent('run-selected', {
- detail: {checkName},
- composed: true,
- bubbles: true,
- })
- );
-}
+import {fireRunSelected} from './gr-checks-util';
+import {ChecksTabState} from '../../types/events';
@customElement('gr-checks-run')
export class GrChecksRun extends GrLitElement {
@@ -130,21 +111,14 @@
}
/* Additional 'div' for increased specificity. */
div.chip.selected {
- border: 1px solid var(--selected-foreground);
+ border: 1px solid var(--selected-background);
background-color: var(--selected-background);
padding-left: calc(var(--spacing-m) + var(--thick-border) - 1px);
}
- div.chip.deselected {
- border: 1px solid var(--gray-foreground);
- background-color: transparent;
- padding-left: calc(var(--spacing-m) + var(--thick-border) - 1px);
- }
- div.chip.selected iron-icon {
+ div.chip.selected .name,
+ div.chip.selected iron-icon.filter {
color: var(--selected-foreground);
}
- div.chip.deselected iron-icon {
- color: var(--gray-foreground);
- }
.chip.selected gr-button.action,
.chip.deselected gr-button.action {
display: none;
@@ -188,7 +162,7 @@
render() {
if (!this.shouldRender) return html`<div class="chip">Loading ...</div>`;
- const icon = this.selected ? 'filter' : iconForRun(this.run);
+ const icon = iconForRun(this.run);
const classes = {
chip: true,
[icon]: true,
@@ -200,6 +174,7 @@
return html`
<div @click="${this.handleChipClick}" class="${classMap(classes)}">
<div class="left">
+ ${this.renderFilterIcon()}
<iron-icon class="${icon}" icon="gr-icons:${icon}"></iron-icon>
${this.renderAdditionalIcon()}
<span class="name">${this.run.checkName}</span>
@@ -218,6 +193,13 @@
`;
}
+ renderFilterIcon() {
+ if (!this.selected) return;
+ return html`
+ <iron-icon class="filter" icon="gr-icons:filter"></iron-icon>
+ `;
+ }
+
/**
* For RUNNING we also want to render an icon representing the worst result
* that has been reported until now - if there are any results already.
@@ -259,6 +241,9 @@
@property()
selectedRuns: string[] = [];
+ @property()
+ tabState?: ChecksTabState;
+
private isSectionExpanded = new Map<RunStatus, boolean>();
constructor() {
@@ -315,6 +300,23 @@
];
}
+ protected updated(changedProperties: PropertyValues) {
+ super.updated(changedProperties);
+ if (changedProperties.has('tabState') && this.tabState) {
+ const {statusOrCategory} = this.tabState;
+ if (
+ statusOrCategory === RunStatus.RUNNING ||
+ statusOrCategory === RunStatus.RUNNABLE
+ ) {
+ this.updateComplete.then(() => {
+ const s = statusOrCategory.toString().toLowerCase();
+ const el = this.shadowRoot?.querySelector(`.${s} .sectionHeader`);
+ el?.scrollIntoView({block: 'center'});
+ });
+ }
+ }
+ }
+
render() {
return html`
<h2 class="heading-2">Runs</h2>
@@ -322,6 +324,7 @@
id="filterInput"
type="text"
placeholder="Filter runs by regular expression"
+ ?hidden="${!this.showFilter()}"
@input="${this.onInput}"
/>
${this.renderSection(RunStatus.COMPLETED)}
@@ -384,7 +387,7 @@
if (runs.length === 0) return;
const expanded = this.isSectionExpanded.get(status) ?? true;
const expandedClass = expanded ? 'expanded' : 'collapsed';
- const icon = expanded ? 'gr-icons:expand-more' : 'gr-icons:expand-less';
+ const icon = expanded ? 'gr-icons:expand-less' : 'gr-icons:expand-more';
return html`
<div class="${status.toLowerCase()} ${expandedClass}">
<div
@@ -416,6 +419,14 @@
.deselected="${deselected}"
></gr-checks-run>`;
}
+
+ showFilter(): boolean {
+ const show = this.runs.length > 10;
+ if (!show && this.filterRegExp.source.length > 0) {
+ this.filterRegExp = new RegExp('');
+ }
+ return show;
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index ad4f2ae..f7f7054 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -28,19 +28,29 @@
allActions$,
allResults$,
allRuns$,
+ checksPatchsetNumber$,
+ someProvidersAreLoading$,
} from '../../services/checks/checks-model';
import './gr-checks-runs';
import './gr-checks-results';
import {sharedStyles} from '../../styles/shared-styles';
-import {changeNum$, currentPatchNum$} from '../../services/change/change-model';
-import {NumericChangeId, PatchSetNum} from '../../types/common';
+import {changeNum$, latestPatchNum$} from '../../services/change/change-model';
+import {NumericChangeId, PatchSetNumber} from '../../types/common';
import {
ActionTriggeredEvent,
fireActionTriggered,
} from '../../services/checks/checks-util';
-import {checkRequiredProperty} from '../../utils/common-util';
-import {RunSelectedEvent} from './gr-checks-runs';
+import {
+ assertIsDefined,
+ check,
+ checkRequiredProperty,
+} from '../../utils/common-util';
+import {RunSelectedEvent} from './gr-checks-util';
import {ChecksTabState} from '../../types/events';
+import {fireAlert} from '../../utils/event-util';
+import {appContext} from '../../services/app-context';
+import {from, timer} from 'rxjs';
+import {takeUntil} from 'rxjs/operators';
/**
* The "Checks" tab on the Gerrit change page. Gets its data from plugins that
@@ -59,21 +69,31 @@
tabState?: ChecksTabState;
@property()
- currentPatchNum: PatchSetNum | undefined = undefined;
+ checksPatchsetNumber: PatchSetNumber | undefined = undefined;
+
+ @property()
+ latestPatchsetNumber: PatchSetNumber | undefined = undefined;
@property()
changeNum: NumericChangeId | undefined = undefined;
+ @property()
+ someProvidersAreLoading = false;
+
@internalProperty()
selectedRuns: string[] = [];
+ private readonly checksService = appContext.checksService;
+
constructor() {
super();
this.subscribe('runs', allRuns$);
this.subscribe('actions', allActions$);
this.subscribe('results', allResults$);
- this.subscribe('currentPatchNum', currentPatchNum$);
+ this.subscribe('checksPatchsetNumber', checksPatchsetNumber$);
+ this.subscribe('latestPatchsetNumber', latestPatchNum$);
this.subscribe('changeNum', changeNum$);
+ this.subscribe('someProvidersAreLoading', someProvidersAreLoading$);
this.addEventListener('action-triggered', (e: ActionTriggeredEvent) =>
this.handleActionTriggered(e.detail.action, e.detail.run)
@@ -113,7 +133,6 @@
}
render() {
- const ps = `Patchset ${this.currentPatchNum} (Latest)`;
const filteredRuns = this.runs.filter(
r =>
this.selectedRuns.length === 0 ||
@@ -123,14 +142,11 @@
<div class="header">
<div class="left">
<gr-dropdown-list
- value="${ps}"
- .items="${[
- {
- value: `${ps}`,
- text: `${ps}`,
- },
- ]}"
+ value="${this.checksPatchsetNumber}"
+ .items="${this.createPatchsetDropdownItems()}"
+ @value-change="${this.onPatchsetSelected}"
></gr-dropdown-list>
+ <span ?hidden="${!this.someProvidersAreLoading}">Loading...</span>
</div>
<div class="right">
${this.actions.map(this.renderAction)}
@@ -141,22 +157,44 @@
class="runs"
.runs="${this.runs}"
.selectedRuns="${this.selectedRuns}"
+ .tabState="${this.tabState}"
@run-selected="${this.handleRunSelected}"
></gr-checks-runs>
<gr-checks-results
class="results"
+ .tabState="${this.tabState}"
.runs="${filteredRuns}"
+ .selectedRunsCount="${this.selectedRuns.length}"
+ @run-selected="${this.handleRunSelected}"
></gr-checks-results>
</div>
`;
}
+ private onPatchsetSelected(e: CustomEvent<{value: string}>) {
+ const patchset = Number(e.detail.value);
+ check(!isNaN(patchset), 'selected patchset must be a number');
+ this.checksService.setPatchset(patchset as PatchSetNumber);
+ }
+
+ private createPatchsetDropdownItems() {
+ if (!this.latestPatchsetNumber) return [];
+ return Array.from(Array(this.latestPatchsetNumber), (_, i) => {
+ assertIsDefined(this.latestPatchsetNumber, 'latestPatchsetNumber');
+ const index = this.latestPatchsetNumber - i;
+ const postfix = index === this.latestPatchsetNumber ? ' (latest)' : '';
+ return {
+ value: `${index}`,
+ text: `Patchset ${index}${postfix}`,
+ };
+ });
+ }
+
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (changedProperties.has('tabState')) {
- const check = this.tabState?.checkName;
- if (check) {
- this.selectedRuns = [check];
+ if (this.tabState) {
+ this.selectedRuns = [];
}
}
}
@@ -169,22 +207,44 @@
handleActionTriggered(action: Action, run?: CheckRun) {
if (!this.changeNum) return;
- if (!this.currentPatchNum) return;
- // TODO(brohlfs): The callback is supposed to be returning a promise.
- // A toast should be displayed until the promise completes. And then the
- // data should be updated.
- action.callback(
+ if (!this.checksPatchsetNumber) return;
+ const promise = action.callback(
this.changeNum,
- this.currentPatchNum as number,
+ this.checksPatchsetNumber,
run?.attempt,
run?.externalId,
run?.checkName,
action.name
);
+ // Plugins *should* return a promise, but you never know ...
+ if (promise?.then) {
+ const prefix = `Triggering action '${action.name}'`;
+ fireAlert(this, `${prefix} ...`);
+ from(promise)
+ // If the action takes longer than 5 seconds, then most likely the
+ // user is either not interested or the result not relevant anymore.
+ .pipe(takeUntil(timer(5000)))
+ .subscribe(result => {
+ if (result.errorMessage) {
+ fireAlert(this, `${prefix} failed with ${result.errorMessage}.`);
+ } else {
+ fireAlert(this, `${prefix} successful.`);
+ this.checksService.reloadForCheck(run?.checkName);
+ }
+ });
+ } else {
+ fireAlert(this, `Action '${action.name}' triggered.`);
+ }
}
handleRunSelected(e: RunSelectedEvent) {
- this.toggleSelected(e.detail.checkName);
+ if (e.detail.reset) {
+ this.selectedRuns = [];
+ return;
+ }
+ if (e.detail.checkName) {
+ this.toggleSelected(e.detail.checkName);
+ }
}
toggleSelected(checkName: string) {
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-util.ts b/polygerrit-ui/app/elements/checks/gr-checks-util.ts
new file mode 100644
index 0000000..4d8d8cb
--- /dev/null
+++ b/polygerrit-ui/app/elements/checks/gr-checks-util.ts
@@ -0,0 +1,49 @@
+/**
+ * @license
+ * Copyright (C) 2021 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.
+ */
+
+export interface RunSelectedEventDetail {
+ reset: boolean;
+ checkName?: string;
+}
+
+export type RunSelectedEvent = CustomEvent<RunSelectedEventDetail>;
+
+declare global {
+ interface HTMLElementEventMap {
+ 'run-selected': RunSelectedEvent;
+ }
+}
+
+export function fireRunSelected(target: EventTarget, checkName: string) {
+ target.dispatchEvent(
+ new CustomEvent('run-selected', {
+ detail: {reset: false, checkName},
+ composed: true,
+ bubbles: true,
+ })
+ );
+}
+
+export function fireRunSelectionReset(target: EventTarget) {
+ target.dispatchEvent(
+ new CustomEvent('run-selected', {
+ detail: {reset: true},
+ composed: true,
+ bubbles: true,
+ })
+ );
+}
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
index 0361b9a..c0f7320 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
@@ -18,7 +18,6 @@
import '../../shared/gr-dropdown/gr-dropdown';
import '../../../styles/shared-styles';
import '../../shared/gr-avatar/gr-avatar';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-account-dropdown_html';
@@ -37,9 +36,7 @@
}
@customElement('gr-account-dropdown')
-export class GrAccountDropdown extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAccountDropdown extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -68,8 +65,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._handleLocationChange();
this.listen(window, 'location-change', '_handleLocationChange');
this.restApiService.getConfig().then(cfg => {
@@ -85,9 +82,9 @@
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
this.unlisten(window, 'location-change', '_handleLocationChange');
+ super.disconnectedCallback();
}
_getLinks(switchAccountUrl: string, path: string) {
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.js b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.js
index dc5d1b4..d2d27b7 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.js
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.js
@@ -17,7 +17,6 @@
import '../../../test/common-test-setup-karma.js';
import './gr-account-dropdown.js';
-import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-account-dropdown');
@@ -25,7 +24,6 @@
let element;
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
element = basicFixture.instantiate();
});
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.ts b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.ts
index b28b13e..181e132 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.ts
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.ts
@@ -16,7 +16,6 @@
*/
import '../../shared/gr-dialog/gr-dialog';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-error-dialog_html';
@@ -29,9 +28,7 @@
}
@customElement('gr-error-dialog')
-export class GrErrorDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrErrorDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.js b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.ts
similarity index 63%
rename from polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.js
rename to polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.ts
index ea8f7c5..fa57a23 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.js
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.ts
@@ -15,21 +15,25 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-error-dialog.js';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
+import '../../../test/common-test-setup-karma';
+import {queryAndAssert} from '../../../test/test-utils';
+import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
+import {GrErrorDialog} from './gr-error-dialog';
const basicFixture = fixtureFromElement('gr-error-dialog');
suite('gr-error-dialog tests', () => {
- let element;
+ let element: GrErrorDialog;
setup(() => {
element = basicFixture.instantiate();
});
test('dismiss tap fires event', done => {
- element.addEventListener('dismiss', () => { done(); });
- MockInteractions.tap(element.$.dialog.$.confirm);
+ element.addEventListener('dismiss', () => done());
+ MockInteractions.tap(
+ (queryAndAssert(element, '#dialog') as GrDialog).$.confirm
+ );
});
});
-
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts
index 4a86005..b02dfa2 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts
@@ -19,7 +19,6 @@
import '../gr-error-dialog/gr-error-dialog';
import '../../shared/gr-alert/gr-alert';
import '../../shared/gr-overlay/gr-overlay';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-error-manager_html';
@@ -77,9 +76,7 @@
const DEBOUNCER_CHECK_LOGGED_IN = 'checkLoggedIn';
@customElement('gr-error-manager')
-export class GrErrorManager extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrErrorManager extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -120,8 +117,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.listen(document, EventType.SERVER_ERROR, '_handleServerError');
this.listen(document, EventType.NETWORK_ERROR, '_handleNetworkError');
this.listen(document, EventType.SHOW_ALERT, '_handleShowAlert');
@@ -141,8 +138,7 @@
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
this._clearHideAlertHandle();
this.unlisten(document, EventType.SERVER_ERROR, '_handleServerError');
this.unlisten(document, EventType.NETWORK_ERROR, '_handleNetworkError');
@@ -156,6 +152,7 @@
if (this._authErrorHandlerDeregistrationHook) {
this._authErrorHandlerDeregistrationHook();
}
+ super.disconnectedCallback();
}
_shouldSuppressError(msg: string) {
@@ -324,7 +321,7 @@
// Persist alert until navigation.
this.listen(document, 'location-change', '_hideAlert');
} else {
- this._hideAlertHandle = this.async(
+ this._hideAlertHandle = window.setTimeout(
this._hideAlert,
HIDE_ALERT_TIMEOUT_MS
);
@@ -350,7 +347,7 @@
_clearHideAlertHandle() {
if (this._hideAlertHandle !== null) {
- this.cancelAsync(this._hideAlertHandle);
+ window.clearTimeout(this._hideAlertHandle);
this._hideAlertHandle = null;
}
}
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
index 4bd90ea..4f37fad 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
@@ -17,7 +17,6 @@
import '../../shared/gr-button/gr-button';
import '../gr-key-binding-display/gr-key-binding-display';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-keyboard-shortcuts-dialog_html';
@@ -42,7 +41,7 @@
@customElement('gr-keyboard-shortcuts-dialog')
export class GrKeyboardShortcutsDialog extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -76,19 +75,19 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.addKeyboardShortcutDirectoryListener(
this.keyboardShortcutDirectoryListener
);
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
this.removeKeyboardShortcutDirectoryListener(
this.keyboardShortcutDirectoryListener
);
+ super.disconnectedCallback();
}
_handleCloseTap(e: MouseEvent) {
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
index bed07a6..9c088c2 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
@@ -19,7 +19,6 @@
import '../../shared/gr-icons/gr-icons';
import '../gr-account-dropdown/gr-account-dropdown';
import '../gr-smart-search/gr-smart-search';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-main-header_html';
@@ -101,9 +100,7 @@
]);
@customElement('gr-main-header')
-export class GrMainHeader extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrMainHeader extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -165,15 +162,15 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._loadAccount();
this._loadConfig();
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
+ super.disconnectedCallback();
}
reload() {
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
index d9c43d6..f764373 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
@@ -478,7 +478,7 @@
test('shows feedback icon when URL provided', async () => {
assert.isEmpty(element._feedbackURL);
- assert.isNull(query(element, '.feedbackButton'));
+ assert.isNotOk(query(element, '.feedbackButton'));
const url = 'report_bug_url';
const config: ServerInfo = {
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
index 632ce4c..fa81103 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
@@ -15,6 +15,7 @@
* limitations under the License.
*/
import {
+ BasePatchSetNum,
BranchName,
ChangeConfigInfo,
ChangeInfo,
@@ -253,7 +254,7 @@
changeNum: NumericChangeId;
project: RepoName;
patchNum?: PatchSetNum;
- basePatchNum?: PatchSetNum;
+ basePatchNum?: BasePatchSetNum;
edit?: boolean;
host?: string;
messageHash?: string;
@@ -310,7 +311,7 @@
project: RepoName;
path?: string;
patchNum?: PatchSetNum | null;
- basePatchNum?: PatchSetNum | null;
+ basePatchNum?: BasePatchSetNum | null;
lineNum?: number | string;
leftSide?: boolean;
commentId?: UrlEncodedCommentId;
@@ -435,7 +436,7 @@
mapCommentlinks: uninitializedMapCommentLinks,
- _checkPatchRange(patchNum?: PatchSetNum, basePatchNum?: PatchSetNum) {
+ _checkPatchRange(patchNum?: PatchSetNum, basePatchNum?: BasePatchSetNum) {
if (basePatchNum && !patchNum) {
throw new Error('Cannot use base patch number without patch number.');
}
@@ -591,7 +592,7 @@
getUrlForChange(
change: Pick<ChangeInfo, '_number' | 'project' | 'internalHost'>,
patchNum?: PatchSetNum,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
isEdit?: boolean,
messageHash?: string
) {
@@ -635,7 +636,7 @@
navigateToChange(
change: Pick<ChangeInfo, '_number' | 'project' | 'internalHost'>,
patchNum?: PatchSetNum,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
isEdit?: boolean,
redirect?: boolean
) {
@@ -652,7 +653,7 @@
change: ChangeInfo | ParsedChangeInfo,
filePath: string,
patchNum?: PatchSetNum,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
lineNum?: number
) {
return this.getUrlForDiffById(
@@ -686,7 +687,7 @@
project: RepoName,
filePath: string,
patchNum?: PatchSetNum,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
lineNum?: number,
leftSide?: boolean
) {
@@ -751,7 +752,7 @@
change: ChangeInfo | ParsedChangeInfo,
filePath: string,
patchNum?: PatchSetNum,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
lineNum?: number
) {
this._navigate(
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index 676ef7b..7804970 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {
@@ -23,7 +22,6 @@
PageNextCallback,
} from '../../../utils/page-wrapper-utils';
import {htmlTemplate} from './gr-router_html';
-import {encodeURL, getBaseUrl} from '../../../utils/url-util';
import {
DashboardSection,
GeneratedWebLink,
@@ -50,6 +48,7 @@
import {customElement, property} from '@polymer/decorators';
import {assertNever} from '../../../utils/common-util';
import {
+ BasePatchSetNum,
DashboardId,
GroupId,
NumericChangeId,
@@ -68,6 +67,14 @@
import {firePageError} from '../../../utils/event-util';
import {addQuotesWhen} from '../../../utils/string-util';
import {windowLocationReload} from '../../../utils/dom-util';
+import {
+ encodeURL,
+ getBaseUrl,
+ toPath,
+ toPathname,
+ toSearchParams,
+} from '../../../utils/url-util';
+import {Execution} from '../../../constants/reporting';
const RoutePattern = {
ROOT: '/',
@@ -282,13 +289,11 @@
interface PatchRangeParams {
patchNum?: PatchSetNum | null;
- basePatchNum?: PatchSetNum | null;
+ basePatchNum?: BasePatchSetNum | null;
}
@customElement('gr-router')
-export class GrRouter extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrRouter extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -762,20 +767,23 @@
/** Page.js middleware that try parse the querystring into queryMap. */
_queryStringMiddleware(ctx: PageContext, next: PageNextCallback) {
- let queryMap: Map<string, string> | URLSearchParams = new Map<
- string,
- string
- >();
+ (ctx as PageContextWithQueryMap).queryMap = this.createQueryMap(ctx);
+ next();
+ }
+
+ private createQueryMap(ctx: PageContext) {
if (ctx.querystring) {
// https://caniuse.com/#search=URLSearchParams
if (window.URLSearchParams) {
- queryMap = new URLSearchParams(ctx.querystring);
+ return new URLSearchParams(ctx.querystring);
} else {
- queryMap = new Map(this._parseQueryString(ctx.querystring));
+ this.reporting.reportExecution(Execution.REACHABLE_CODE, {
+ id: 'noURLSearchParams',
+ });
+ return new Map(this._parseQueryString(ctx.querystring));
}
}
- (ctx as PageContextWithQueryMap).queryMap = queryMap;
- next();
+ return new Map<string, string>();
}
/**
@@ -806,13 +814,13 @@
pattern,
(ctx, next) => this._loadUserMiddleware(ctx, next),
(ctx, next) => this._queryStringMiddleware(ctx, next),
- data => {
+ ctx => {
this.reporting.locationChanged(handlerName);
const promise = authRedirect
- ? this._redirectIfNotLoggedIn(data)
+ ? this._redirectIfNotLoggedIn(ctx)
: Promise.resolve();
promise.then(() => {
- this[handlerName](data as PageContextWithQueryMap);
+ this[handlerName](ctx as PageContextWithQueryMap);
});
}
);
@@ -846,6 +854,21 @@
next();
});
+ // Remove the tracking param 'usp' (User Source Parameter) from the URL,
+ // just to have users look at cleaner URLs.
+ page((ctx, next) => {
+ if (window.URLSearchParams) {
+ const pathname = toPathname(ctx.canonicalPath);
+ const searchParams = toSearchParams(ctx.canonicalPath);
+ if (searchParams.has('usp')) {
+ searchParams.delete('usp');
+ this._redirect(toPath(pathname, searchParams));
+ return;
+ }
+ }
+ next();
+ });
+
// Middleware
page((ctx, next) => {
document.body.scrollTop = 0;
@@ -860,7 +883,7 @@
// Fire asynchronously so that the URL is changed by the time the event
// is processed.
- this.async(() => {
+ setTimeout(() => {
const detail: LocationChangeEventDetail = {
hash: window.location.hash,
pathname: window.location.pathname,
@@ -1545,7 +1568,7 @@
const params: GenerateUrlChangeViewParameters = {
project: ctx.params[0] as RepoName,
changeNum,
- basePatchNum: convertToPatchSetNum(ctx.params[4]),
+ basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
patchNum: convertToPatchSetNum(ctx.params[6]),
view: GerritView.CHANGE,
queryMap: ctx.queryMap,
@@ -1576,7 +1599,7 @@
const params: GenerateUrlDiffViewParameters = {
project: ctx.params[0] as RepoName,
changeNum,
- basePatchNum: convertToPatchSetNum(ctx.params[4]),
+ basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
patchNum: convertToPatchSetNum(ctx.params[6]),
path: ctx.params[8],
view: GerritView.DIFF,
@@ -1595,7 +1618,7 @@
// Parameter order is based on the regex group number matched.
const params: GenerateUrlLegacyChangeViewParameters = {
changeNum: Number(ctx.params[0]) as NumericChangeId,
- basePatchNum: convertToPatchSetNum(ctx.params[3]),
+ basePatchNum: convertToPatchSetNum(ctx.params[3]) as BasePatchSetNum,
patchNum: convertToPatchSetNum(ctx.params[5]),
view: GerritView.CHANGE,
querystring: ctx.querystring,
@@ -1612,7 +1635,7 @@
// Parameter order is based on the regex group number matched.
const params: GenerateUrlLegacyDiffViewParameters = {
changeNum: Number(ctx.params[0]) as NumericChangeId,
- basePatchNum: convertToPatchSetNum(ctx.params[2]),
+ basePatchNum: convertToPatchSetNum(ctx.params[2]) as BasePatchSetNum,
patchNum: convertToPatchSetNum(ctx.params[4]),
path: ctx.params[5],
view: GerritView.DIFF,
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
index f7fe091..ad4ebcb 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
@@ -19,10 +19,8 @@
import './gr-router.js';
import {page} from '../../../utils/page-wrapper-utils.js';
import {GerritNav} from '../gr-navigation/gr-navigation.js';
-import {stubBaseUrl} from '../../../test/test-utils.js';
+import {stubBaseUrl, stubRestApi, addListenerForTest} from '../../../test/test-utils.js';
import {_testOnly_RoutePattern} from './gr-router.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-import {addListenerForTest} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-router');
@@ -804,7 +802,6 @@
});
test('redirects to dashboard if logged in', () => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(true));
const data = {
canonicalPath: '/', path: '/', querystring: '', hash: '',
};
@@ -934,7 +931,6 @@
});
test('dashboard while signed in sets params', () => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(true));
const data = {canonicalPath: '/dashboard/', params: {0: 'foo'}};
return element._handleDashboardRoute(data, '').then(() => {
assert.isFalse(redirectToLoginStub.called);
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index 97d5271..504a9e5 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -17,7 +17,6 @@
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../../styles/shared-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-search-bar_html';
@@ -40,10 +39,13 @@
// Possible static search options for auto complete, without negations.
const SEARCH_OPERATORS: ReadonlyArray<string> = [
'added:',
+ 'after:',
'age:',
'age:1week', // Give an example age
'assignee:',
+ 'attention:',
'author:',
+ 'before:',
'branch:',
'bug:',
'cc:',
@@ -77,6 +79,7 @@
'is:assigned',
'is:closed',
'is:ignored',
+ 'is:merge',
'is:merged',
'is:open',
'is:owner',
@@ -88,6 +91,8 @@
'is:watched',
'is:wip',
'label:',
+ 'mergedafter:',
+ 'mergedbefore:',
'message:',
'onlyexts:',
'onlyextensions:',
@@ -142,7 +147,7 @@
@customElement('gr-search-bar')
export class GrSearchBar extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -193,8 +198,8 @@
this.query = (input: string) => this._getSearchSuggestions(input);
}
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.restApiService.getConfig().then((serverConfig?: ServerInfo) => {
const mergeability =
serverConfig &&
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.js
index e553402..ae7e10c 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.js
@@ -18,10 +18,9 @@
import '../../../test/common-test-setup-karma.js';
import './gr-search-bar.js';
import '../../../scripts/util.js';
-import {TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
+import {TestKeyboardShortcutBinder, stubRestApi} from '../../../test/test-utils.js';
import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
import {_testOnly_clearDocsBaseUrlCache} from '../../../utils/url-util.js';
-import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-search-bar');
@@ -126,7 +125,6 @@
suite('_getSearchSuggestions', () => {
setup(() => {
// Ensure that config.change.mergeability_computation_behavior is not set.
- stubRestApi('getConfig').returns(Promise.resolve({}));
element = basicFixture.instantiate();
});
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts
index b698732..02036b4 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../gr-search-bar/gr-search-bar';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-smart-search_html';
@@ -35,9 +34,7 @@
const ME_EXPRESSION = 'me';
@customElement('gr-smart-search')
-export class GrSmartSearch extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrSmartSearch extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -66,8 +63,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.restApiService.getConfig().then(cfg => {
this._config = cfg;
});
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index e381213..5111337 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -19,7 +19,6 @@
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-overlay/gr-overlay';
import '../gr-diff/gr-diff';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-apply-fix-dialog_html';
@@ -32,6 +31,7 @@
FixSuggestionInfo,
PatchSetNum,
RobotId,
+ BasePatchSetNum,
} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
@@ -53,9 +53,7 @@
}
@customElement('gr-apply-fix-dialog')
-export class GrApplyFixDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrApplyFixDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -134,8 +132,8 @@
});
}
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.refitOverlay = () => {
// re-center the dialog as content changed
fireEvent(this.$.applyFixOverlay, 'iron-resize');
@@ -143,11 +141,11 @@
this.addEventListener('diff-context-expanded', this.refitOverlay);
}
- detached() {
- super.detached();
+ disconnectedCallback() {
if (this.refitOverlay) {
this.removeEventListener('diff-context-expanded', this.refitOverlay);
}
+ super.disconnectedCallback();
}
_showSelectedFixSuggestion(fixSuggestion: FixSuggestionInfo) {
@@ -280,7 +278,11 @@
.applyFixSuggestion(changeNum, patchNum, this._currentFix.fix_id)
.then(res => {
if (res && res.ok) {
- GerritNav.navigateToChange(change, EditPatchSetNum, patchNum);
+ GerritNav.navigateToChange(
+ change,
+ EditPatchSetNum,
+ patchNum as BasePatchSetNum
+ );
this._close();
}
this._isApplyFixLoading = false;
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
index b60a585..0ca6ea3 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-comment-api_html';
@@ -51,7 +50,6 @@
import {PatchSetFile, PatchNumOnly, isPatchSetFile} from '../../../types/types';
import {appContext} from '../../../services/app-context';
import {CommentSide, Side} from '../../../constants/constants';
-import {KnownExperimentId} from '../../../services/flags/flags';
import {pluralize} from '../../../utils/string-util';
export type CommentIdToCommentThreadMap = {
@@ -597,9 +595,7 @@
export const _testOnly_getCommentsForPath =
ChangeComments.prototype.getCommentsForPath;
@customElement('gr-comment-api')
-export class GrCommentApi extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrCommentApi extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -609,10 +605,6 @@
private readonly restApiService = appContext.restApiService;
- private readonly flagsService = appContext.flagsService;
-
- private isPortingCommentsExperimentEnabled = false;
-
/** @override */
created() {
super.created();
@@ -620,9 +612,6 @@
constructor() {
super();
- this.isPortingCommentsExperimentEnabled = this.flagsService.isEnabled(
- KnownExperimentId.PORTING_COMMENTS
- );
}
/**
@@ -636,12 +625,8 @@
this.restApiService.getDiffComments(changeNum),
this.restApiService.getDiffRobotComments(changeNum),
this.restApiService.getDiffDrafts(changeNum),
- this.isPortingCommentsExperimentEnabled
- ? this.restApiService.getPortedComments(changeNum, revision)
- : Promise.resolve({}),
- this.isPortingCommentsExperimentEnabled
- ? this.restApiService.getPortedDrafts(changeNum, revision)
- : Promise.resolve({}),
+ this.restApiService.getPortedComments(changeNum, revision),
+ this.restApiService.getPortedDrafts(changeNum, revision),
];
return Promise.all(commentsPromise).then(
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js
index 94ec78f..fbbe1fb 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js
@@ -60,7 +60,6 @@
test('loads logged-in', () => {
const changeNum = 1234;
- stubRestApi('getLoggedIn').returns(Promise.resolve(true));
const getCommentsStub = stubRestApi('getDiffComments').returns(
Promise.resolve({
'foo.c': [{id: '123', message: 'foo bar', in_reply_to: '321'}],
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts
index 6f9705f..388cc73 100644
--- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-coverage-layer_html';
@@ -35,8 +34,7 @@
]);
@customElement('gr-coverage-layer')
-export class GrCoverageLayer
- extends GestureEventListeners(LegacyElementMixin(PolymerElement))
+export class GrCoverageLayer extends LegacyElementMixin(PolymerElement)
implements DiffLayer {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
index de68554..4c10d5c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -19,7 +19,6 @@
import '../../shared/gr-hovercard/gr-hovercard';
import '../gr-ranged-comment-layer/gr-ranged-comment-layer';
import './gr-diff-builder-side-by-side';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-builder-element_html';
@@ -43,7 +42,7 @@
GrRangedCommentLayer,
} from '../gr-ranged-comment-layer/gr-ranged-comment-layer';
import {GrCoverageLayer} from '../gr-coverage-layer/gr-coverage-layer';
-import {DiffViewMode} from '../../../api/diff';
+import {DiffViewMode, RenderPreferences} from '../../../api/diff';
import {Side} from '../../../constants/constants';
import {GrDiffLine, LineNumber} from '../gr-diff/gr-diff-line';
import {GrDiffGroup} from '../gr-diff/gr-diff-group';
@@ -67,9 +66,7 @@
}
@customElement('gr-diff-builder')
-export class GrDiffBuilderElement extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDiffBuilderElement extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -164,11 +161,11 @@
_cancelableRenderPromise: CancelablePromise<unknown> | null = null;
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
if (this._builder) {
this._builder.clear();
}
+ super.disconnectedCallback();
}
get diffElement() {
@@ -183,7 +180,11 @@
return coverageRanges.filter(range => range && range.side === 'right');
}
- render(keyLocations: KeyLocations, prefs: DiffPreferencesInfo) {
+ render(
+ keyLocations: KeyLocations,
+ prefs: DiffPreferencesInfo,
+ renderPrefs?: RenderPreferences
+ ) {
// Setting up annotation layers must happen after plugins are
// installed, and |render| satisfies the requirement, however,
// |attached| doesn't because in the diff view page, the element is
@@ -202,7 +203,7 @@
if (!this.diff) {
throw Error('Cannot render a diff without DiffInfo.');
}
- this._builder = this._getDiffBuilder(this.diff, prefs);
+ this._builder = this._getDiffBuilder(this.diff, prefs, renderPrefs);
this.$.processor.context = prefs.context;
this.$.processor.keyLocations = keyLocations;
@@ -325,7 +326,7 @@
sectionEl.parentNode.removeChild(sectionEl);
}
- this.async(() => fireEvent(this, 'render-content'), 1);
+ setTimeout(() => fireEvent(this, 'render-content'), 1);
}
cancel() {
@@ -344,7 +345,11 @@
throw Error(`Invalid preference value: ${pref}`);
}
- _getDiffBuilder(diff: DiffInfo, prefs: DiffPreferencesInfo): GrDiffBuilder {
+ _getDiffBuilder(
+ diff: DiffInfo,
+ prefs: DiffPreferencesInfo,
+ renderPrefs?: RenderPreferences
+ ): GrDiffBuilder {
if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
this._handlePreferenceError('tab size');
}
@@ -367,7 +372,8 @@
localPrefs,
this.diffElement,
this.baseImage,
- this.revisionImage
+ this.revisionImage,
+ renderPrefs
);
} else if (diff.binary) {
// If the diff is binary, but not an image.
@@ -377,14 +383,16 @@
diff,
localPrefs,
this.diffElement,
- this._layers
+ this._layers,
+ renderPrefs
);
} else if (this.viewMode === DiffViewMode.UNIFIED) {
builder = new GrDiffBuilderUnified(
diff,
localPrefs,
this.diffElement,
- this._layers
+ this._layers,
+ renderPrefs
);
}
if (!builder) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
index 5b3f225..fb37349 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
@@ -19,6 +19,7 @@
import {ImageInfo} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
import {GrEndpointParam} from '../../plugins/gr-endpoint-param/gr-endpoint-param';
+import {RenderPreferences} from '../../../api/diff';
// MIME types for images we allow showing. Do not include SVG, it can contain
// arbitrary JavaScript.
@@ -30,9 +31,10 @@
prefs: DiffPreferencesInfo,
outputEl: HTMLElement,
private readonly _baseImage: ImageInfo | null,
- private readonly _revisionImage: ImageInfo | null
+ private readonly _revisionImage: ImageInfo | null,
+ renderPrefs?: RenderPreferences
) {
- super(diff, prefs, outputEl, []);
+ super(diff, prefs, outputEl, [], renderPrefs);
}
public renderDiff() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
index 2025732..51b8135 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
@@ -21,15 +21,17 @@
import {GrDiffLine, LineNumber} from '../gr-diff/gr-diff-line';
import {DiffViewMode, Side} from '../../../constants/constants';
import {DiffLayer} from '../../../types/types';
+import {RenderPreferences} from '../../../api/diff';
export class GrDiffBuilderSideBySide extends GrDiffBuilder {
constructor(
diff: DiffInfo,
prefs: DiffPreferencesInfo,
outputEl: HTMLElement,
- readonly layers: DiffLayer[] = []
+ readonly layers: DiffLayer[] = [],
+ renderPrefs?: RenderPreferences
) {
- super(diff, prefs, outputEl, layers);
+ super(diff, prefs, outputEl, layers, renderPrefs);
}
_getMoveControlsConfig() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
index 1bf3d69..e927fdf 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
@@ -20,15 +20,17 @@
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
import {DiffViewMode, Side} from '../../../constants/constants';
import {DiffLayer} from '../../../types/types';
+import {RenderPreferences} from '../../../api/diff';
export class GrDiffBuilderUnified extends GrDiffBuilder {
constructor(
diff: DiffInfo,
prefs: DiffPreferencesInfo,
outputEl: HTMLElement,
- readonly layers: DiffLayer[] = []
+ readonly layers: DiffLayer[] = [],
+ renderPrefs?: RenderPreferences
) {
- super(diff, prefs, outputEl, layers);
+ super(diff, prefs, outputEl, layers, renderPrefs);
}
_getMoveControlsConfig() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index 4943298..37142ff 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -17,6 +17,7 @@
import {
ContentLoadNeededEventDetail,
MovedLinkClickedEventDetail,
+ RenderPreferences,
} from '../../../api/diff';
import {getBaseUrl} from '../../../utils/url-util';
import {GrDiffLine, GrDiffLineType, LineNumber} from '../gr-diff/gr-diff-line';
@@ -76,6 +77,8 @@
private readonly _prefs: DiffPreferencesInfo;
+ private readonly _renderPrefs?: RenderPreferences;
+
protected readonly _outputEl: HTMLElement;
readonly groups: GrDiffGroup[];
@@ -92,7 +95,8 @@
diff: DiffInfo,
prefs: DiffPreferencesInfo,
outputEl: HTMLElement,
- readonly layers: DiffLayer[] = []
+ readonly layers: DiffLayer[] = [],
+ renderPrefs?: RenderPreferences
) {
this._diff = diff;
this._numLinesLeft = this._diff.content
@@ -102,6 +106,7 @@
}, 0)
: 0;
this._prefs = prefs;
+ this._renderPrefs = renderPrefs;
this._outputEl = outputEl;
this.groups = [];
this.blameInfo = null;
@@ -542,7 +547,9 @@
td.dataset['value'] = number.toString();
if (
- (this._prefs.show_file_comment_button === false && number === 'FILE') ||
+ ((this._prefs.show_file_comment_button === false ||
+ this._renderPrefs?.show_file_comment_button === false) &&
+ number === 'FILE') ||
number === 'LOST'
) {
return td;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
index cc3be07..0ba794f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
@@ -25,7 +25,6 @@
} from '../../shared/gr-cursor-manager/gr-cursor-manager';
import {afterNextRender} from '@polymer/polymer/lib/utils/render-status';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-cursor_html';
@@ -54,9 +53,7 @@
}
@customElement('gr-diff-cursor')
-export class GrDiffCursor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDiffCursor extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -131,8 +128,9 @@
/** @override */
disconnectedCallback() {
- super.disconnectedCallback();
window.removeEventListener('scroll', this._boundHandleWindowScroll);
+ this.$.cursorManager.unsetCursor();
+ super.disconnectedCallback();
}
// Don't remove - used by clients embedding gr-diff outside of Gerrit.
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
index 094f2d7..fb28e2b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
@@ -17,7 +17,6 @@
import '../../../styles/shared-styles';
import '../gr-selection-action-box/gr-selection-action-box';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-highlight_html';
@@ -57,9 +56,7 @@
const DEBOUNCER_SELECTION_CHANGE = 'selectionChange';
@customElement('gr-diff-highlight')
-export class GrDiffHighlight extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDiffHighlight extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -91,8 +88,9 @@
}
/** @override */
- detached() {
+ disconnectedCallback() {
this.cancelDebouncer(DEBOUNCER_SELECTION_CHANGE);
+ super.disconnectedCallback();
}
get diffBuilder() {
@@ -171,11 +169,11 @@
rangeNodes.forEach(rangeNode => {
rangeNode.classList.add('rangeHoverHighlight');
});
- const chipNode = threadEl.parentElement?.querySelector(
- `gr-ranged-comment-chip[threadElRootId="${threadEl.rootId}"]`
+ const hintNode = threadEl.parentElement?.querySelector(
+ `gr-ranged-comment-hint[threadElRootId="${threadEl.rootId}"]`
);
- if (chipNode) {
- chipNode.shadowRoot
+ if (hintNode) {
+ hintNode.shadowRoot
?.querySelectorAll('.rangeHighlight')
.forEach(highlightNode =>
highlightNode.classList.add('rangeHoverHighlight')
@@ -188,11 +186,11 @@
rangeNodes.forEach(rangeNode => {
rangeNode.classList.remove('rangeHoverHighlight');
});
- const chipNode = threadEl.parentElement?.querySelector(
- `gr-ranged-comment-chip[threadElRootId="${threadEl.rootId}"]`
+ const hintNode = threadEl.parentElement?.querySelector(
+ `gr-ranged-comment-hint[threadElRootId="${threadEl.rootId}"]`
);
- if (chipNode) {
- chipNode.shadowRoot
+ if (hintNode) {
+ hintNode.shadowRoot
?.querySelectorAll('.rangeHoverHighlight')
.forEach(highlightNode =>
highlightNode.classList.remove('rangeHoverHighlight')
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index 47b4a1f..d40a8a6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -16,8 +16,6 @@
*/
import '../../shared/gr-comment-thread/gr-comment-thread';
import '../gr-diff/gr-diff';
-import '../gr-syntax-layer/gr-syntax-layer';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-host_html';
@@ -77,6 +75,7 @@
fireAlert,
fireServerError,
fireEvent,
+ waitForEventOnce,
} from '../../../utils/event-util';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {assertIsDefined} from '../../../utils/common-util';
@@ -119,7 +118,6 @@
export interface GrDiffHost {
$: {
- syntaxLayer: GrSyntaxLayer & Element;
diff: GrDiff;
};
}
@@ -132,9 +130,7 @@
* specific component, while <gr-diff> is a re-usable component.
*/
@customElement('gr-diff-host')
-export class GrDiffHost extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDiffHost extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -237,7 +233,7 @@
@property({type: Object})
_revisionImage: Base64ImageFile | null = null;
- @property({type: Object, notify: true})
+ @property({type: Object, notify: true, observer: 'diffChanged'})
diff?: DiffInfo;
@property({type: Object})
@@ -258,6 +254,7 @@
@property({
type: Boolean,
computed: '_isSyntaxHighlightingEnabled(prefs.*, diff)',
+ observer: '_syntaxHighlightingEnabledChanged',
})
_syntaxHighlightingEnabled?: boolean;
@@ -270,6 +267,8 @@
private readonly jsAPI = appContext.jsApiService;
+ private readonly syntaxLayer = new GrSyntaxLayer();
+
/** @override */
created() {
super.created();
@@ -308,17 +307,17 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._getLoggedIn().then(loggedIn => {
this._loggedIn = loggedIn;
});
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
this.clear();
+ super.disconnectedCallback();
}
initLayers() {
@@ -336,6 +335,10 @@
});
}
+ diffChanged(diff?: DiffInfo) {
+ this.syntaxLayer.init(diff);
+ }
+
/**
* @param shouldReportMetric indicate a new Diff Page. This is a
* signal to report metrics event that started on location change.
@@ -374,7 +377,7 @@
this.filesWeblinks = this._getFilesWeblinks(diff);
this.diff = diff;
- const event = await this._onRenderOnce();
+ const event = (await waitForEventOnce(this, 'render')) as CustomEvent;
if (shouldReportMetric) {
// We report diffViewContentDisplayed only on reload caused
// by params changed - expected only on Diff Page.
@@ -384,7 +387,7 @@
if (needsSyntaxHighlighting) {
this.reporting.time(TimingLabel.SYNTAX);
try {
- await this.$.syntaxLayer.process();
+ await this.syntaxLayer.process();
} finally {
this.reporting.timeEnd(TimingLabel.SYNTAX);
}
@@ -402,17 +405,7 @@
private _getLayers(path: string, changeNum: NumericChangeId): DiffLayer[] {
// Get layers from plugins (if any).
- return [this.$.syntaxLayer, ...this.jsAPI.getDiffLayers(path, changeNum)];
- }
-
- private _onRenderOnce(): Promise<CustomEvent> {
- return new Promise<CustomEvent>(resolve => {
- const callback = (event: CustomEvent) => {
- this.removeEventListener('render', callback);
- resolve(event);
- };
- this.addEventListener('render', callback);
- });
+ return [this.syntaxLayer, ...this.jsAPI.getDiffLayers(path, changeNum)];
}
clear() {
@@ -510,7 +503,7 @@
/** Cancel any remaining diff builder rendering work. */
cancel() {
this.$.diff.cancel();
- this.$.syntaxLayer.cancel();
+ this.syntaxLayer.cancel();
}
getCursorStops() {
@@ -1001,6 +994,10 @@
fireEvent(this, 'diff-comments-modified');
}
+ _syntaxHighlightingEnabledChanged(_syntaxHighlightingEnabled: boolean) {
+ this.syntaxLayer.setEnabled(_syntaxHighlightingEnabled);
+ }
+
_isSyntaxHighlightingEnabled(
preferenceChangeRecord?: PolymerDeepPropertyChange<
DiffPreferencesInfo,
@@ -1048,11 +1045,11 @@
const renderUpdateListener: DiffLayerListener = start => {
if (start > NUM_OF_LINES_THRESHOLD_FOR_VIEWPORT) {
this.reporting.diffViewDisplayed();
- this.$.syntaxLayer.removeListener(renderUpdateListener);
+ this.syntaxLayer.removeListener(renderUpdateListener);
}
};
- this.$.syntaxLayer.addListener(renderUpdateListener);
+ this.syntaxLayer.addListener(renderUpdateListener);
}
_handleRenderStart() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_html.ts
index 84a2e4a..e82489c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_html.ts
@@ -42,9 +42,4 @@
show-newline-warning-right="[[_showNewlineWarningRight(diff)]]"
>
</gr-diff>
- <gr-syntax-layer
- id="syntaxLayer"
- enabled="[[_syntaxHighlightingEnabled]]"
- diff="[[diff]]"
- ></gr-syntax-layer>
`;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
index 01c78f0..0bb7e7e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
@@ -21,11 +21,10 @@
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
import {createCommentThreads} from '../../../utils/comment-util.js';
-import {Side} from '../../../constants/constants.js';
+import {Side, createDefaultDiffPrefs} from '../../../constants/constants.js';
import {createChange} from '../../../test/test-data-generators.js';
import {CoverageType} from '../../../types/types.js';
import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
-import {createDefaultDiffPrefs} from '../../../constants/constants.js';
import {EditPatchSetNum, ParentPatchSetNum} from '../../../types/common.js';
const basicFixture = fixtureFromElement('gr-diff-host');
@@ -130,7 +129,7 @@
test('ends total and syntax timer after syntax layer', async () => {
sinon.stub(element.reporting, 'diffViewContentDisplayed');
let notifySyntaxProcessed;
- sinon.stub(element.$.syntaxLayer, 'process').returns(
+ sinon.stub(element.syntaxLayer, 'process').returns(
new Promise(resolve => {
notifySyntaxProcessed = resolve;
})
@@ -170,7 +169,7 @@
test('completes reload promise after syntax layer processing', async () => {
let notifySyntaxProcessed;
- sinon.stub(element.$.syntaxLayer, 'process').returns(new Promise(
+ sinon.stub(element.syntaxLayer, 'process').returns(new Promise(
resolve => {
notifySyntaxProcessed = resolve;
}));
@@ -970,7 +969,7 @@
suite('create-comment', () => {
setup(async () => {
loggedIn = true;
- element.attached();
+ element.connectedCallback();
await flush();
});
@@ -1292,7 +1291,7 @@
test('gr-diff-host provides syntax highlighting layer', async () => {
stubRestApi('getDiff').returns(Promise.resolve({content: []}));
await element.reload();
- assert.equal(element.$.diff.layers[0], element.$.syntaxLayer);
+ assert.equal(element.$.diff.layers[0], element.syntaxLayer);
});
test('rendering normal-sized diff does not disable syntax', () => {
@@ -1301,7 +1300,7 @@
a: ['foo'],
}],
};
- assert.isTrue(element.$.syntaxLayer.enabled);
+ assert.isTrue(element.syntaxLayer.enabled);
});
test('rendering large diff disables syntax', () => {
@@ -1311,17 +1310,17 @@
a: [new Array(501).join('*')],
}],
};
- assert.isFalse(element.$.syntaxLayer.enabled);
+ assert.isFalse(element.syntaxLayer.enabled);
});
test('starts syntax layer processing on render event', async () => {
- sinon.stub(element.$.syntaxLayer, 'process')
+ sinon.stub(element.syntaxLayer, 'process')
.returns(Promise.resolve());
stubRestApi('getDiff').returns(Promise.resolve({content: []}));
await element.reload();
element.dispatchEvent(
new CustomEvent('render', {bubbles: true, composed: true}));
- assert.isTrue(element.$.syntaxLayer.process.called);
+ assert.isTrue(element.syntaxLayer.process.called);
});
});
@@ -1346,11 +1345,11 @@
test('gr-diff-host provides syntax highlighting layer', async () => {
stubRestApi('getDiff').returns(Promise.resolve({content: []}));
await element.reload();
- assert.equal(element.$.diff.layers[0], element.$.syntaxLayer);
+ assert.equal(element.$.diff.layers[0], element.syntaxLayer);
});
test('syntax layer should be disabled', () => {
- assert.isFalse(element.$.syntaxLayer.enabled);
+ assert.isFalse(element.syntaxLayer.enabled);
});
test('still disabled for large diff', () => {
@@ -1360,7 +1359,7 @@
a: [new Array(501).join('*')],
}],
};
- assert.isFalse(element.$.syntaxLayer.enabled);
+ assert.isFalse(element.syntaxLayer.enabled);
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
index 60f2853..cc7bb98 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
@@ -19,7 +19,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
import {DiffViewMode} from '../../../constants/constants';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-mode-selector_html';
@@ -29,9 +28,7 @@
import {appContext} from '../../../services/app-context';
@customElement('gr-diff-mode-selector')
-export class GrDiffModeSelector extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDiffModeSelector extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -48,7 +45,9 @@
private readonly restApiService = appContext.restApiService;
- attached() {
+ /** @override */
+ connectedCallback() {
+ super.connectedCallback();
((IronA11yAnnouncer as unknown) as FixIronA11yAnnouncer).requestAvailability();
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts
index 8829afc..96c2c03 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts
@@ -18,7 +18,6 @@
import '../../shared/gr-button/gr-button';
import '../../shared/gr-diff-preferences/gr-diff-preferences';
import '../../shared/gr-overlay/gr-overlay';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-preferences-dialog_html';
@@ -37,8 +36,8 @@
};
}
@customElement('gr-diff-preferences-dialog')
-export class GrDiffPreferencesDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
+export class GrDiffPreferencesDialog extends LegacyElementMixin(
+ PolymerElement
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
index 9fafcfa..1f0c246 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {
@@ -92,9 +91,7 @@
* the rest is not.
*/
@customElement('gr-diff-processor')
-export class GrDiffProcessor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDiffProcessor extends LegacyElementMixin(PolymerElement) {
@property({type: Number})
context = 3;
@@ -117,17 +114,17 @@
_isScrolling?: boolean;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.listen(window, 'scroll', '_handleWindowScroll');
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
this.cancelDebouncer(DEBOUNCER_RESET_IS_SCROLLING);
this.cancel();
this.unlisten(window, 'scroll', '_handleWindowScroll');
+ super.disconnectedCallback();
}
_handleWindowScroll() {
@@ -176,7 +173,7 @@
let currentBatch = 0;
const nextStep = () => {
if (this._isScrolling) {
- this._nextStepHandle = this.async(nextStep, 100);
+ this._nextStepHandle = window.setTimeout(nextStep, 100);
return;
}
// If we are done, resolve the promise.
@@ -199,7 +196,7 @@
state.chunkIndex = stateUpdate.newChunkIndex;
if (currentBatch >= this._asyncThreshold) {
currentBatch = 0;
- this._nextStepHandle = this.async(nextStep, 1);
+ this._nextStepHandle = window.setTimeout(nextStep, 1);
} else {
nextStep.call(this);
}
@@ -218,7 +215,7 @@
*/
cancel() {
if (this._nextStepHandle !== null) {
- this.cancelAsync(this._nextStepHandle);
+ window.clearTimeout(this._nextStepHandle);
this._nextStepHandle = null;
}
if (this._processPromise) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js
index b8f7498..5ecc962 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js
@@ -1113,7 +1113,7 @@
test('detaching cancels', () => {
element = basicFixture.instantiate();
sinon.stub(element, 'cancel');
- element.detached();
+ element.disconnectedCallback();
assert(element.cancel.called);
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
index 9493478..cee5ef6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
@@ -17,7 +17,6 @@
import '../../../styles/shared-styles';
import {addListener} from '@polymer/polymer/lib/utils/gestures';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-selection_html';
@@ -53,9 +52,7 @@
}
@customElement('gr-diff-selection')
-export class GrDiffSelection extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDiffSelection extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -77,8 +74,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.classList.add(SelectionClass.RIGHT);
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 2c4b8f6..ba36c67 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -31,7 +31,6 @@
import '../gr-diff-preferences-dialog/gr-diff-preferences-dialog';
import '../gr-patch-range-select/gr-patch-range-select';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-view_html';
@@ -64,6 +63,7 @@
import {ChangeComments, GrCommentApi} from '../gr-comment-api/gr-comment-api';
import {GrDiffModeSelector} from '../gr-diff-mode-selector/gr-diff-mode-selector';
import {
+ BasePatchSetNum,
ChangeInfo,
CommitId,
ConfigInfo,
@@ -95,7 +95,7 @@
} from '../../../utils/comment-util';
import {AppElementParams} from '../../gr-app-types';
import {CustomKeyboardEvent, OpenFixPreviewEvent} from '../../../types/events';
-import {fireAlert, fireTitleChange} from '../../../utils/event-util';
+import {fireAlert, fireEvent, fireTitleChange} from '../../../utils/event-util';
import {GerritView} from '../../../services/router/router-model';
import {assertIsDefined} from '../../../utils/common-util';
const ERR_REVIEW_STATUS = 'Couldn’t change file review status.';
@@ -127,7 +127,7 @@
@customElement('gr-diff-view')
export class GrDiffView extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -326,11 +326,6 @@
this._throttledToggleFileReviewed = this._throttleWrap(e =>
this._handleToggleFileReviewed(e as CustomKeyboardEvent)
);
- }
-
- /** @override */
- attached() {
- super.attached();
this._getLoggedIn().then(loggedIn => {
this._loggedIn = loggedIn;
});
@@ -344,10 +339,11 @@
}
/** @override */
- detached() {
+ disconnectedCallback() {
if (this._onRenderHandler) {
this.$.diffHost.removeEventListener('render', this._onRenderHandler);
}
+ super.disconnectedCallback();
}
_getLoggedIn() {
@@ -645,14 +641,20 @@
}
}
+ // Similar to gr-change-view._handleOpenReplyDialog
_handleOpenReplyDialog(e: CustomKeyboardEvent) {
if (this.shouldSuppressKeyboardShortcut(e)) return;
if (this.modifierPressed(e)) return;
- if (!this._loggedIn) return;
+ this._getLoggedIn().then(isLoggedIn => {
+ if (!isLoggedIn) {
+ fireEvent(this, 'show-auth-required');
+ return;
+ }
- this.set('changeViewState.showReplyDialog', true);
- e.preventDefault();
- this._navToChangeView();
+ this.set('changeViewState.showReplyDialog', true);
+ e.preventDefault();
+ this._navToChangeView();
+ });
}
_handleToggleLeftPane(e: CustomKeyboardEvent) {
@@ -1661,7 +1663,7 @@
this._change,
this._path,
this._patchRange.basePatchNum,
- 'PARENT' as PatchSetNum,
+ 'PARENT' as BasePatchSetNum,
this.params?.view === GerritView.DIFF && this.params?.commentLink
? this._focusLineNum
: undefined
@@ -1703,7 +1705,7 @@
this._change,
this._path,
latestPatchNum,
- this._patchRange.patchNum
+ this._patchRange.patchNum as BasePatchSetNum
);
}
@@ -1755,13 +1757,10 @@
return disableDiffPrefs || !loggedIn;
}
- _handleNextUnreviewedFile(e: CustomKeyboardEvent) {
- if (this.shouldSuppressKeyboardShortcut(e)) return;
+ _navigateToNextUnreviewedFile() {
if (!this._path) return;
if (!this._fileList) return;
if (!this._reviewedFiles) return;
-
- this._setReviewed(true);
// Ensure that the currently viewed file always appears in unreviewedFiles
// so we resolve the right "next" file.
const unreviewedFiles = this._fileList.filter(
@@ -1770,6 +1769,12 @@
this._navToFile(this._path, unreviewedFiles, 1);
}
+ _handleNextUnreviewedFile(e: CustomKeyboardEvent) {
+ if (this.shouldSuppressKeyboardShortcut(e)) return;
+ this._setReviewed(true);
+ this._navigateToNextUnreviewedFile();
+ }
+
_navigateToNextFileWithCommentThread() {
if (!this._path) return;
if (!this._fileList) return;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
index bfb0cd8..651e95b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
@@ -427,7 +427,7 @@
</gr-diff-preferences-dialog>
<gr-diff-cursor
id="cursor"
- on-navigate-to-next-unreviewed-file="_handleNextUnreviewedFile"
+ on-navigate-to-next-unreviewed-file="_navigateToNextUnreviewedFile"
on-navigate-to-next-file-with-comments="_navigateToNextFileWithCommentThread"
></gr-diff-cursor>
<gr-comment-api id="commentAPI"></gr-comment-api>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
index b5473a3..5c3dfdc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
@@ -19,7 +19,7 @@
import './gr-diff-view.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {ChangeStatus} from '../../../constants/constants.js';
-import {TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
+import {TestKeyboardShortcutBinder, stubRestApi} from '../../../test/test-utils.js';
import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
import {ChangeComments, _testOnly_findCommentById, _testOnly_getCommentsForPath} from '../gr-comment-api/gr-comment-api.js';
import {GerritView} from '../../../services/router/router-model.js';
@@ -28,7 +28,6 @@
createRevisions,
createComment,
} from '../../../test/test-data-generators.js';
-import {stubRestApi} from '../../../test/test-utils.js';
import {EditPatchSetNum} from '../../../types/common.js';
const basicFixture = fixtureFromElement('gr-diff-view');
@@ -624,6 +623,76 @@
assert.isNotOk(args[3]);
});
+ test('A fires an error event when not logged in', done => {
+ const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
+ sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(false));
+ const loggedInErrorSpy = sinon.spy();
+ element.addEventListener('show-auth-required', loggedInErrorSpy);
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
+ flush(() => {
+ assert.isTrue(changeNavStub.notCalled, 'The `a` keyboard shortcut ' +
+ 'should only work when the user is logged in.');
+ assert.isNull(window.sessionStorage.getItem(
+ 'changeView.showReplyDialog'));
+ assert.isTrue(loggedInErrorSpy.called);
+ done();
+ });
+ });
+
+ test('A navigates to change with logged in', done => {
+ element._changeNum = '42';
+ element._patchRange = {
+ basePatchNum: 5,
+ patchNum: 10,
+ };
+ element._change = {
+ _number: 42,
+ revisions: {
+ a: {_number: 10, commit: {parents: []}},
+ b: {_number: 5, commit: {parents: []}},
+ },
+ };
+ const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
+ sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+ const loggedInErrorSpy = sinon.spy();
+ element.addEventListener('show-auth-required', loggedInErrorSpy);
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
+ flush(() => {
+ assert.isTrue(element.changeViewState.showReplyDialog);
+ assert(changeNavStub.lastCall.calledWithExactly(element._change, 10,
+ 5), 'Should navigate to /c/42/5..10');
+ assert.isFalse(loggedInErrorSpy.called);
+ done();
+ });
+ });
+
+ test('A navigates to change with old patch number with logged in', done => {
+ element._changeNum = '42';
+ element._patchRange = {
+ basePatchNum: PARENT,
+ patchNum: 1,
+ };
+ element._change = {
+ _number: 42,
+ revisions: {
+ a: {_number: 1, commit: {parents: []}},
+ b: {_number: 2, commit: {parents: []}},
+ },
+ };
+ const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
+ sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+ const loggedInErrorSpy = sinon.spy();
+ element.addEventListener('show-auth-required', loggedInErrorSpy);
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
+ flush(() => {
+ assert.isTrue(element.changeViewState.showReplyDialog);
+ assert(changeNavStub.lastCall.calledWithExactly(element._change, 1,
+ PARENT), 'Should navigate to /c/42/1');
+ assert.isFalse(loggedInErrorSpy.called);
+ done();
+ });
+ });
+
test('keyboard shortcuts with patch range', () => {
element._changeNum = '42';
element._patchRange = {
@@ -644,19 +713,6 @@
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
- MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
- assert.isTrue(changeNavStub.notCalled, 'The `a` keyboard shortcut ' +
- 'should only work when the user is logged in.');
- assert.isNull(window.sessionStorage.getItem(
- 'changeView.showReplyDialog'));
-
- element._loggedIn = true;
- MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
- assert.isTrue(element.changeViewState.showReplyDialog);
-
- assert(changeNavStub.lastCall.calledWithExactly(element._change, 10,
- 5), 'Should navigate to /c/42/5..10');
-
MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(changeNavStub.lastCall.calledWithExactly(element._change, 10,
5), 'Should navigate to /c/42/5..10');
@@ -717,19 +773,6 @@
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
- MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
- assert.isTrue(changeNavStub.notCalled, 'The `a` keyboard shortcut ' +
- 'should only work when the user is logged in.');
- assert.isNull(window.sessionStorage.getItem(
- 'changeView.showReplyDialog'));
-
- element._loggedIn = true;
- MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
- assert.isTrue(element.changeViewState.showReplyDialog);
-
- assert(changeNavStub.lastCall.calledWithExactly(element._change, 1,
- PARENT), 'Should navigate to /c/42/1');
-
MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(changeNavStub.lastCall.calledWithExactly(element._change, 1,
PARENT), 'Should navigate to /c/42/1');
@@ -1872,7 +1915,7 @@
'a/b/test.c': {},
};
stubRestApi('getConfig').returns(Promise.resolve({change: {}}));
- stubRestApi('getLoggedIn').returns(Promise.resolve(true));
+
stubRestApi('getProjectConfig').returns(Promise.resolve({}));
stubRestApi('getDiffChangeDetail').returns(Promise.resolve({}));
stubRestApi('getChangeFiles').returns(Promise.resolve(changedFiles));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
index 6974a76..2e97765 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -22,10 +22,9 @@
import '../gr-diff-selection/gr-diff-selection';
import '../gr-syntax-themes/gr-syntax-theme';
import '../gr-ranged-comment-themes/gr-ranged-comment-theme';
-import '../gr-ranged-comment-chip/gr-ranged-comment-chip';
+import '../gr-ranged-comment-hint/gr-ranged-comment-hint';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {htmlTemplate} from './gr-diff_html';
import {LineNumber} from './gr-diff-line';
@@ -113,9 +112,7 @@
}
@customElement('gr-diff')
-export class GrDiff extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDiff extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -298,17 +295,17 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._observeNodes();
}
/** @override */
- detached() {
+ disconnectedCallback() {
this.cancelDebouncer(RENDER_DIFF_TABLE_DEBOUNCE_NAME);
- super.detached();
this._unobserveIncrementalNodes();
this._unobserveNodes();
+ super.disconnectedCallback();
}
showNoChangeMessage(
@@ -755,7 +752,7 @@
this.classList.add('no-left');
}
if (renderPrefs.disable_context_control_buttons) {
- this.updateStyles({'--context-control-display': 'none'});
+ this.classList.add('disable-context-control-buttons');
}
}
@@ -804,19 +801,21 @@
const keyLocations = this._computeKeyLocations();
const bypassPrefs = this._getBypassPrefs(this.prefs);
- this.$.diffBuilder.render(keyLocations, bypassPrefs).then(() => {
- this.dispatchEvent(
- new CustomEvent('render', {
- bubbles: true,
- composed: true,
- detail: {contentRendered: true},
- })
- );
- });
+ this.$.diffBuilder
+ .render(keyLocations, bypassPrefs, this.renderPrefs)
+ .then(() => {
+ this.dispatchEvent(
+ new CustomEvent('render', {
+ bubbles: true,
+ composed: true,
+ detail: {contentRendered: true},
+ })
+ );
+ });
}
_handleRenderContent() {
- this.querySelectorAll('gr-ranged-comment-chip').forEach(element =>
+ this.querySelectorAll('gr-ranged-comment-hint').forEach(element =>
element.remove()
);
this._setLoading(false);
@@ -864,14 +863,14 @@
const slotAtt = threadEl.getAttribute('slot');
if (range && isLongCommentRange(range) && slotAtt) {
- const longRangeCommentChip = document.createElement(
- 'gr-ranged-comment-chip'
+ const longRangeCommentHint = document.createElement(
+ 'gr-ranged-comment-hint'
);
- longRangeCommentChip.range = range;
- longRangeCommentChip.setAttribute('threadElRootId', threadEl.rootId);
- longRangeCommentChip.setAttribute('slot', slotAtt);
- this.insertBefore(longRangeCommentChip, threadEl);
- this._redispatchHoverEvents(longRangeCommentChip, threadEl);
+ longRangeCommentHint.range = range;
+ longRangeCommentHint.setAttribute('threadElRootId', threadEl.rootId);
+ longRangeCommentHint.setAttribute('slot', slotAtt);
+ this.insertBefore(longRangeCommentHint, threadEl);
+ this._redispatchHoverEvents(longRangeCommentHint, threadEl);
}
// Create a slot for the thread and attach it to the thread group.
@@ -896,7 +895,7 @@
const removedThreadEls = info.removedNodes.filter(isThreadEl);
for (const threadEl of removedThreadEls) {
this.querySelector(
- `gr-ranged-comment-chip[threadElRootId="${threadEl.rootId}"]`
+ `gr-ranged-comment-hint[threadElRootId="${threadEl.rootId}"]`
)?.remove();
}
});
@@ -905,7 +904,7 @@
_portedCommentsWithoutRangeMessage() {
const div = document.createElement('div');
const icon = document.createElement('iron-icon');
- icon.setAttribute('icon', 'gr-icons:info');
+ icon.setAttribute('icon', 'gr-icons:info-outline');
div.appendChild(icon);
const span = document.createElement('span');
span.innerText = 'Original comment position not found in this patchset';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
index 4d0e566..12803b3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
@@ -24,6 +24,12 @@
:host(.no-left) .sideBySide .right:not([data-value]) + td {
display: none;
}
+ :host(.disable-context-control-buttons) {
+ --context-control-display: none;
+ }
+ :host(.disable-context-control-buttons) .section {
+ border-right: none;
+ }
:host {
font-family: var(--monospace-font-family, ''), 'Roboto Mono';
font-size: var(--font-size, var(--font-size-code, 12px));
@@ -223,25 +229,22 @@
}
.delta.dueToMove .movedIn .moveDescription {
- color: var(--diff-moved-in-background);
- background-color: var(--diff-moved-in-label-background);
+ color: var(--diff-moved-in-label-color);
}
.delta.dueToMove .movedOut .moveDescription {
- color: var(--diff-moved-out-background);
- background-color: var(--diff-moved-out-label-background);
+ color: var(--diff-moved-out-label-color);
}
.moveLabel {
- display: flex;
- justify-content: flex-end;
font-family: var(--font-family, ''), 'Roboto Mono';
font-size: var(--font-size-small, 12px);
- }
- .delta.dueToMove .moveDescription {
- border-radius: var(--fully-rounded-radius, 1000px);
+ font-weight: var(--code-hint-font-weight, 500);
+ line-height: var(--line-height-small, 16px);
padding: var(--spacing-s) var(--spacing-m);
margin: var(--spacing-s);
- line-height: var(--line-height-small, 16px);
+ }
+ .delta.dueToMove .moveDescription {
display: flex;
+ justify-content: flex-end;
}
.moveDescription iron-icon {
@@ -430,10 +433,16 @@
}
td.lost div {
background-color: var(--blue-50);
- padding: var(--spacing-s);
+ padding: var(--spacing-s) 0 0 0;
+ }
+ td.lost div:first-of-type {
+ font-family: var(--font-family, 'Roboto');
+ font-size: var(--font-size-normal, 14px);
+ line-height: var(--line-height-normal);
}
td.lost iron-icon {
- margin-right: var(--spacing-s);
+ padding: 0 var(--spacing-s) 0 var(--spacing-m);
+ color: var(--blue-700);
}
col.blame {
display: none;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
index 49eac72..c6bd8d6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
@@ -555,7 +555,7 @@
.calledWithExactly(fakeLineEl, 42));
});
- test('adds long range comment chip', async () => {
+ test('adds long range comment hint', async () => {
const range = {
start_line: 1,
end_line: 12,
@@ -580,10 +580,10 @@
await flush();
assert.deepEqual(
- element.querySelector('gr-ranged-comment-chip').range, range);
+ element.querySelector('gr-ranged-comment-hint').range, range);
});
- test('no duplicate range chip for same thread', async () => {
+ test('no duplicate range hint for same thread', async () => {
const range = {
start_line: 1,
end_line: 12,
@@ -596,10 +596,10 @@
threadEl.setAttribute('line-num', 1);
threadEl.setAttribute('range', JSON.stringify(range));
threadEl.setAttribute('slot', 'right-1');
- const firstChip = document.createElement('gr-ranged-comment-chip');
- firstChip.range = range;
- firstChip.setAttribute('threadElRootId', threadEl.rootId);
- firstChip.setAttribute('slot', 'right-1');
+ const firstHint = document.createElement('gr-ranged-comment-hint');
+ firstHint.range = range;
+ firstHint.setAttribute('threadElRootId', threadEl.rootId);
+ firstHint.setAttribute('slot', 'right-1');
const content = [{
a: [],
b: [],
@@ -608,7 +608,7 @@
}];
setupSampleDiff({content});
- element.appendChild(firstChip);
+ element.appendChild(firstHint);
await flush();
element._handleRenderContent();
await flush();
@@ -616,10 +616,10 @@
await flush();
assert.equal(
- element.querySelectorAll('gr-ranged-comment-chip').length, 1);
+ element.querySelectorAll('gr-ranged-comment-hint').length, 1);
});
- test('removes long range comment chip when comment is discarded',
+ test('removes long range comment hint when comment is discarded',
async () => {
const range = {
start_line: 1,
@@ -646,7 +646,7 @@
threadEl.remove();
await flush();
- assert.isEmpty(element.querySelectorAll('gr-ranged-comment-chip'));
+ assert.isEmpty(element.querySelectorAll('gr-ranged-comment-hint'));
});
suite('change in preferences', () => {
@@ -804,7 +804,7 @@
assert.equal(element.prefs.context, 3);
assert.equal(element._safetyBypass, -1);
- assert.equal(renderStub.firstCall.lastArg.context, -1);
+ assert.equal(renderStub.firstCall.args[1].context, -1);
});
test('toggles collapse context from bypass', async () => {
@@ -817,7 +817,7 @@
assert.equal(element.prefs.context, 3);
assert.isNull(element._safetyBypass);
- assert.equal(renderStub.firstCall.lastArg.context, 3);
+ assert.equal(renderStub.firstCall.args[1].context, 3);
});
test('toggles collapse context from pref using default', async () => {
@@ -829,7 +829,7 @@
assert.equal(element.prefs.context, -1);
assert.equal(element._safetyBypass, 10);
- assert.equal(renderStub.firstCall.lastArg.context, 10);
+ assert.equal(renderStub.firstCall.args[1].context, 10);
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
index 1ce2506..ad894ce 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
@@ -18,7 +18,6 @@
import '../../shared/gr-dropdown-list/gr-dropdown-list';
import '../../shared/gr-select/gr-select';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-patch-range-select_html';
@@ -38,6 +37,7 @@
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
import {hasOwnProperty} from '../../../utils/common-util';
import {
+ BasePatchSetNum,
ParentPatchSetNum,
PatchSetNum,
RevisionInfo,
@@ -58,7 +58,7 @@
export interface PatchRangeChangeDetail {
patchNum?: PatchSetNum;
- basePatchNum?: PatchSetNum;
+ basePatchNum?: BasePatchSetNum;
}
export type PatchRangeChangeEvent = CustomEvent<PatchRangeChangeDetail>;
@@ -84,9 +84,7 @@
* @extends PolymerElement
*/
@customElement('gr-patch-range-select')
-export class GrPatchRangeSelect extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrPatchRangeSelect extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -123,7 +121,7 @@
patchNum?: PatchSetNum;
@property({type: String})
- basePatchNum?: PatchSetNum;
+ basePatchNum?: BasePatchSetNum;
@property({type: Object})
revisions?: RevisionInfo[];
@@ -222,7 +220,7 @@
_computePatchDropdownContent(
availablePatches?: PatchSet[],
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
_sortedRevisions?: RevisionInfo[],
changeComments?: ChangeComments
): DropdownItem[] | undefined {
@@ -449,7 +447,7 @@
patchNum: patchSetValue,
}),
});
- detail.basePatchNum = patchSetValue;
+ detail.basePatchNum = patchSetValue as BasePatchSetNum;
}
this.dispatchEvent(
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-chip/gr-ranged-comment-chip.ts b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
similarity index 83%
rename from polygerrit-ui/app/elements/diff/gr-ranged-comment-chip/gr-ranged-comment-chip.ts
rename to polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
index 6948283..3d35c26 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-chip/gr-ranged-comment-chip.ts
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
@@ -17,11 +17,11 @@
import {customElement, property} from '@polymer/decorators';
import {CommentRange} from '../../../types/common';
-import {htmlTemplate} from './gr-ranged-comment-chip_html';
+import {htmlTemplate} from './gr-ranged-comment-hint_html';
import {PolymerElement} from '@polymer/polymer/polymer-element';
-@customElement('gr-ranged-comment-chip')
-export class GrRangedCommentChip extends PolymerElement {
+@customElement('gr-ranged-comment-hint')
+export class GrRangedCommentHint extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -37,6 +37,6 @@
declare global {
interface HTMLElementTagNameMap {
- 'gr-ranged-comment-chip': GrRangedCommentChip;
+ 'gr-ranged-comment-hint': GrRangedCommentHint;
}
}
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-chip/gr-ranged-comment-chip_html.ts b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_html.ts
similarity index 74%
rename from polygerrit-ui/app/elements/diff/gr-ranged-comment-chip/gr-ranged-comment-chip_html.ts
rename to polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_html.ts
index c2861fa..670bfd2 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-chip/gr-ranged-comment-chip_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_html.ts
@@ -22,31 +22,25 @@
</style>
<style include="shared-styles">
.row {
- color: var(--ranged-comment-chip-text-color);
+ color: var(--ranged-comment-hint-text-color);
display: flex;
font-family: var(--font-family, ''), 'Roboto Mono';
font-size: var(--font-size-small, 12px);
+ font-weight: var(--code-hint-font-weight, 500);
line-height: var(--line-height-small, 16px);
justify-content: flex-end;
margin: var(--spacing-xs) 0;
+ padding: var(--spacing-s) var(--spacing-l);
}
.icon {
- color: var(--ranged-comment-chip-text-color);
+ color: var(--ranged-comment-hint-text-color);
height: var(--line-height-small, 16px);
width: var(--line-height-small, 16px);
margin-right: var(--spacing-s);
}
- .chip {
- background-color: var(--ranged-comment-chip-background);
- border-radius: var(--fully-rounded-radius, 1000px);
- margin: var(--spacing-s);
- padding: var(--spacing-s) var(--spacing-m);
- }
</style>
<div class="row rangeHighlight">
- <div class="chip">
- <iron-icon class="icon" icon="gr-icons:comment-outline"></iron-icon>
- [[_computeRangeLabel(range)]]
- </div>
+ <iron-icon class="icon" icon="gr-icons:comment"></iron-icon>
+ [[_computeRangeLabel(range)]]
</div>
`;
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-chip/gr-ranged-comment-chip_test.ts b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts
similarity index 83%
rename from polygerrit-ui/app/elements/diff/gr-ranged-comment-chip/gr-ranged-comment-chip_test.ts
rename to polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts
index 8bce99d..932c367 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-chip/gr-ranged-comment-chip_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts
@@ -17,13 +17,13 @@
import '../../../test/common-test-setup-karma';
import {CommentRange} from '../../../types/common';
-import {GrRangedCommentChip} from './gr-ranged-comment-chip';
+import {GrRangedCommentHint} from './gr-ranged-comment-hint';
-suite('gr-ranged-comment-chip tests', () => {
- let element: GrRangedCommentChip;
+suite('gr-ranged-comment-hint tests', () => {
+ let element: GrRangedCommentHint;
setup(() => {
- element = fixtureFromElement('gr-ranged-comment-chip').instantiate();
+ element = fixtureFromElement('gr-ranged-comment-hint').instantiate();
});
test('shows line range', async () => {
@@ -34,7 +34,7 @@
end_character: 3,
} as CommentRange;
await flush();
- const textDiv = element.root!.querySelector<HTMLDivElement>('.chip');
+ const textDiv = element.root!.querySelector<HTMLDivElement>('.row');
assert.equal(textDiv!.innerText.trim(), 'Long comment range 2 - 5');
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts
index f33c2ce..45a7de6 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-ranged-comment-layer_html';
@@ -72,8 +71,7 @@
const HOVER_HIGHLIGHT = 'style-scope gr-diff range rangeHoverHighlight';
@customElement('gr-ranged-comment-layer')
-export class GrRangedCommentLayer
- extends GestureEventListeners(LegacyElementMixin(PolymerElement))
+export class GrRangedCommentLayer extends LegacyElementMixin(PolymerElement)
implements DiffLayer {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.ts b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.ts
index ee52ab6..3e67c49 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.ts
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.ts
@@ -18,7 +18,6 @@
import {GrTooltip} from '../../shared/gr-tooltip/gr-tooltip';
import {customElement, property} from '@polymer/decorators';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-selection-action-box_html';
@@ -37,9 +36,7 @@
}
@customElement('gr-selection-action-box')
-export class GrSelectionActionBox extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrSelectionActionBox extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
index 1150674..081d28d 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
@@ -15,13 +15,8 @@
* limitations under the License.
*/
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
import {FILE, GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
import {CancelablePromise, util} from '../../../scripts/util';
-import {customElement, property} from '@polymer/decorators';
import {DiffFileMetaInfo, DiffInfo} from '../../../types/diff';
import {DiffLayer, DiffLayerListener, HighlightJS} from '../../../types/types';
import {GrLibLoader} from '../../shared/gr-lib-loader/gr-lib-loader';
@@ -158,52 +153,46 @@
lastNotify: {left: number; right: number};
}
-@customElement('gr-syntax-layer')
-export class GrSyntaxLayer
- extends GestureEventListeners(LegacyElementMixin(PolymerElement))
- implements DiffLayer {
- static get template() {
- return html``;
- }
-
- @property({type: Object, observer: '_diffChanged'})
+export class GrSyntaxLayer implements DiffLayer {
diff?: DiffInfo;
- @property({type: Boolean})
enabled = true;
- @property({type: Array})
- _baseRanges: SyntaxLayerRange[][] = [];
+ private baseRanges: SyntaxLayerRange[][] = [];
- @property({type: Array})
- _revisionRanges: SyntaxLayerRange[][] = [];
+ private revisionRanges: SyntaxLayerRange[][] = [];
- @property({type: String})
- _baseLanguage?: string;
+ private baseLanguage?: string;
- @property({type: String})
- _revisionLanguage?: string;
+ private revisionLanguage?: string;
- @property({type: Array})
- _listeners: DiffLayerListener[] = [];
+ private listeners: DiffLayerListener[] = [];
- @property({type: Number})
- _processHandle: number | null = null;
+ private processHandle: number | null = null;
- @property({type: Object})
- _processPromise: CancelablePromise<unknown> | null = null;
+ private processPromise: CancelablePromise<unknown> | null = null;
- @property({type: Object})
- _hljs?: HighlightJS;
+ private hljs?: HighlightJS;
private readonly libLoader = new GrLibLoader();
+ init(diff?: DiffInfo) {
+ this.cancel();
+ this.baseRanges = [];
+ this.revisionRanges = [];
+ this.diff = diff;
+ }
+
+ setEnabled(enabled: boolean) {
+ this.enabled = enabled;
+ }
+
addListener(listener: DiffLayerListener) {
- this.push('_listeners', listener);
+ this.listeners.push(listener);
}
removeListener(listener: DiffLayerListener) {
- this._listeners = this._listeners.filter(f => f !== listener);
+ this.listeners = this.listeners.filter(f => f !== listener);
}
/**
@@ -231,13 +220,13 @@
// Find the relevant syntax ranges, if any.
let ranges: SyntaxLayerRange[] = [];
- if (side === 'left' && this._baseRanges.length >= line.beforeNumber) {
- ranges = this._baseRanges[line.beforeNumber - 1] || [];
+ if (side === 'left' && this.baseRanges.length >= line.beforeNumber) {
+ ranges = this.baseRanges[line.beforeNumber - 1] || [];
} else if (
side === 'right' &&
- this._revisionRanges.length >= line.afterNumber
+ this.revisionRanges.length >= line.afterNumber
) {
- ranges = this._revisionRanges[line.afterNumber - 1] || [];
+ ranges = this.revisionRanges[line.afterNumber - 1] || [];
}
// Apply the ranges to the element.
@@ -263,24 +252,24 @@
*/
process() {
// Cancel any still running process() calls, because they append to the
- // same _baseRanges and _revisionRanges fields.
+ // same baseRanges and revisionRanges fields.
this.cancel();
// Discard existing ranges.
- this._baseRanges = [];
- this._revisionRanges = [];
+ this.baseRanges = [];
+ this.revisionRanges = [];
if (!this.enabled || !this.diff?.content.length) {
return Promise.resolve();
}
if (this.diff.meta_a) {
- this._baseLanguage = this._getLanguage(this.diff.meta_a);
+ this.baseLanguage = this._getLanguage(this.diff.meta_a);
}
if (this.diff.meta_b) {
- this._revisionLanguage = this._getLanguage(this.diff.meta_b);
+ this.revisionLanguage = this._getLanguage(this.diff.meta_b);
}
- if (!this._baseLanguage && !this._revisionLanguage) {
+ if (!this.baseLanguage && !this.revisionLanguage) {
return Promise.resolve();
}
@@ -295,12 +284,12 @@
const rangesCache = new Map<string, SyntaxLayerRange[]>();
- this._processPromise = util.makeCancelable(
+ this.processPromise = util.makeCancelable(
this._loadHLJS().then(
() =>
new Promise<void>(resolve => {
const nextStep = () => {
- this._processHandle = null;
+ this.processHandle = null;
this._processNextLine(state, rangesCache);
// Move to the next line in the section.
@@ -324,18 +313,18 @@
if (state.lineIndex % 100 === 0) {
this._notify(state);
- this._processHandle = this.async(nextStep, ASYNC_DELAY);
+ this.processHandle = window.setTimeout(nextStep, ASYNC_DELAY);
} else {
nextStep.call(this);
}
};
- this._processHandle = this.async(nextStep, 1);
+ this.processHandle = window.setTimeout(nextStep, 1);
})
)
);
- return this._processPromise.finally(() => {
- this._processPromise = null;
+ return this.processPromise.finally(() => {
+ this.processPromise = null;
});
}
@@ -343,21 +332,15 @@
* Cancel any asynchronous syntax processing jobs.
*/
cancel() {
- if (this._processHandle !== null) {
- this.cancelAsync(this._processHandle);
- this._processHandle = null;
+ if (this.processHandle !== null) {
+ clearTimeout(this.processHandle);
+ this.processHandle = null;
}
- if (this._processPromise) {
- this._processPromise.cancel();
+ if (this.processPromise) {
+ this.processPromise.cancel();
}
}
- _diffChanged() {
- this.cancel();
- this._baseRanges = [];
- this._revisionRanges = [];
- }
-
/**
* Take a string of HTML with the (potentially nested) syntax markers
* Highlight.js emits and emit a list of text ranges and classes for the
@@ -420,7 +403,7 @@
rangesCache: Map<string, SyntaxLayerRange[]>
) {
if (!this.diff) return;
- if (!this._hljs) return;
+ if (!this.hljs) return;
let baseLine;
let revisionLine;
@@ -439,44 +422,45 @@
revisionLine = section.b[state.lineIndex];
state.lineNums.right++;
}
+ if (section.skip) {
+ state.lineNums.left += section.skip;
+ state.lineNums.right += section.skip;
+ for (let i = 0; i < section.skip; i++) this.revisionRanges.push([]);
+ }
}
// To store the result of the syntax highlighter.
let result;
if (
- this._baseLanguage &&
+ this.baseLanguage &&
baseLine !== undefined &&
- this._hljs.getLanguage(this._baseLanguage)
+ this.hljs.getLanguage(this.baseLanguage)
) {
- baseLine = this._workaround(this._baseLanguage, baseLine);
- result = this._hljs.highlight(
- this._baseLanguage,
+ baseLine = this._workaround(this.baseLanguage, baseLine);
+ result = this.hljs.highlight(
+ this.baseLanguage,
baseLine,
true,
state.baseContext
);
- this.push(
- '_baseRanges',
- this._rangesFromString(result.value, rangesCache)
- );
+ this.baseRanges.push(this._rangesFromString(result.value, rangesCache));
state.baseContext = result.top;
}
if (
- this._revisionLanguage &&
+ this.revisionLanguage &&
revisionLine !== undefined &&
- this._hljs.getLanguage(this._revisionLanguage)
+ this.hljs.getLanguage(this.revisionLanguage)
) {
- revisionLine = this._workaround(this._revisionLanguage, revisionLine);
- result = this._hljs.highlight(
- this._revisionLanguage,
+ revisionLine = this._workaround(this.revisionLanguage, revisionLine);
+ result = this.hljs.highlight(
+ this.revisionLanguage,
revisionLine,
true,
state.revisionContext
);
- this.push(
- '_revisionRanges',
+ this.revisionRanges.push(
this._rangesFromString(result.value, rangesCache)
);
state.revisionContext = result.top;
@@ -586,20 +570,14 @@
}
_notifyRange(start: number, end: number, side: Side) {
- for (const listener of this._listeners) {
+ for (const listener of this.listeners) {
listener(start, end, side);
}
}
_loadHLJS() {
return this.libLoader.getHLJS().then(hljs => {
- this._hljs = hljs;
+ this.hljs = hljs;
});
}
}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-syntax-layer': GrSyntaxLayer;
- }
-}
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
index 20106d8..f9100a2 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
@@ -20,8 +20,7 @@
import './gr-syntax-layer.js';
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation.js';
import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line.js';
-
-const basicFixture = fixtureFromElement('gr-syntax-layer');
+import {GrSyntaxLayer} from './gr-syntax-layer.js';
suite('gr-syntax-layer tests', () => {
let diff;
@@ -48,7 +47,7 @@
}
setup(() => {
- element = basicFixture.instantiate();
+ element = new GrSyntaxLayer();
diff = getMockDiffResponse();
element.diff = diff;
});
@@ -76,7 +75,7 @@
el.textContent = str;
const line = new GrDiffLine(GrDiffLineType.REMOVE);
line.beforeNumber = 12;
- element._baseRanges[11] = [{
+ element.baseRanges[11] = [{
start,
length,
className,
@@ -103,7 +102,7 @@
el.textContent = str;
const line = new GrDiffLine(GrDiffLineType.REMOVE);
line.beforeNumber = 12;
- element._baseRanges[11] = [{
+ element.baseRanges[11] = [{
start,
length,
className,
@@ -127,8 +126,8 @@
processPromise.then(() => {
assert.isFalse(processNextSpy.called);
- assert.equal(element._baseRanges.length, 0);
- assert.equal(element._revisionRanges.length, 0);
+ assert.equal(element.baseRanges.length, 0);
+ assert.equal(element.revisionRanges.length, 0);
done();
});
});
@@ -145,8 +144,8 @@
processPromise.then(() => {
assert.isFalse(processNextSpy.called);
- assert.equal(element._baseRanges.length, 0);
- assert.equal(element._revisionRanges.length, 0);
+ assert.equal(element.baseRanges.length, 0);
+ assert.equal(element.revisionRanges.length, 0);
done();
});
});
@@ -160,8 +159,8 @@
processPromise.then(() => {
assert.isFalse(processNextSpy.called);
- assert.equal(element._baseRanges.length, 0);
- assert.equal(element._revisionRanges.length, 0);
+ assert.equal(element.baseRanges.length, 0);
+ assert.equal(element.revisionRanges.length, 0);
assert.isFalse(loadHLJSSpy.called);
done();
});
@@ -183,13 +182,13 @@
const linesB = diff.meta_b.lines;
assert.isTrue(processNextSpy.called);
- assert.equal(element._baseRanges.length, linesA);
- assert.equal(element._revisionRanges.length, linesB);
+ assert.equal(element.baseRanges.length, linesA);
+ assert.equal(element.revisionRanges.length, linesB);
assert.equal(highlightSpy.callCount, linesA + linesB);
// The first line of both sides have a range.
- let ranges = [element._baseRanges[0], element._revisionRanges[0]];
+ let ranges = [element.baseRanges[0], element.revisionRanges[0]];
for (const range of ranges) {
assert.equal(range.length, 1);
assert.equal(range[0].className,
@@ -200,8 +199,8 @@
// There are no ranges from ll.1-12 on the left and ll.1-11 on the
// right.
- ranges = element._baseRanges.slice(1, 12)
- .concat(element._revisionRanges.slice(1, 11));
+ ranges = element.baseRanges.slice(1, 12)
+ .concat(element.revisionRanges.slice(1, 11));
for (const range of ranges) {
assert.equal(range.length, 0);
@@ -209,7 +208,7 @@
// There should be another pair of ranges on l.13 for the left and
// l.12 for the right.
- ranges = [element._baseRanges[13], element._revisionRanges[12]];
+ ranges = [element.baseRanges[13], element.revisionRanges[12]];
for (const range of ranges) {
assert.equal(range.length, 1);
@@ -221,13 +220,13 @@
// The next group should have a similar instance on either side.
- let range = element._baseRanges[15];
+ let range = element.baseRanges[15];
assert.equal(range.length, 1);
assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
assert.equal(range[0].start, 34);
assert.equal(range[0].length, 'ipsum'.length);
- range = element._revisionRanges[14];
+ range = element.revisionRanges[14];
assert.equal(range.length, 1);
assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
assert.equal(range[0].start, 35);
@@ -237,9 +236,9 @@
});
});
- test('_diffChanged calls cancel', () => {
- const cancelSpy = sinon.spy(element, '_diffChanged');
- element.diff = {content: []};
+ test('init calls cancel', () => {
+ const cancelSpy = sinon.spy(element, 'cancel');
+ element.init({content: []});
assert.isTrue(cancelSpy.called);
});
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
index 7df3d75..580571d 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
@@ -17,7 +17,6 @@
import '../../../styles/gr-table-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-list-view/gr-list-view';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-documentation-search_html';
@@ -33,7 +32,7 @@
@customElement('gr-documentation-search')
export class GrDocumentationSearch extends ListViewMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -57,8 +56,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
fireTitleChange(this, 'Documentation Search');
}
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
index 4c63303..eff2d94 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-default-editor_html';
@@ -33,9 +32,7 @@
@customElement('gr-default-editor')
/** @extends PolymerElement */
-export class GrDefaultEditor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDefaultEditor extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts
index bc153ee..c8f91cc 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts
@@ -22,7 +22,6 @@
import '../../shared/gr-overlay/gr-overlay';
import '../../../styles/shared-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-edit-controls_html';
@@ -49,9 +48,7 @@
}
@customElement('gr-edit-controls')
-export class GrEditControls extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrEditControls extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -167,7 +164,7 @@
if (autocomplete) {
autocomplete.focus();
}
- this.async(() => {
+ setTimeout(() => {
this.$.overlay.center();
}, 1);
});
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
index 9f3d1bc..609cda2 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
@@ -17,7 +17,6 @@
import '../../shared/gr-button/gr-button';
import '../../shared/gr-dropdown/gr-dropdown';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-edit-file-controls_html';
@@ -31,9 +30,7 @@
/** @extends PolymerElement */
@customElement('gr-edit-file-controls')
-class GrEditFileControls extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrEditFileControls extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index 1e08a5c..86dd5b6 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -21,7 +21,6 @@
import '../../shared/gr-storage/gr-storage';
import '../gr-default-editor/gr-default-editor';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-editor-view_html';
@@ -60,7 +59,7 @@
@customElement('gr-editor-view')
export class GrEditorView extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -141,16 +140,17 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._getEditPrefs().then(prefs => {
this._prefs = prefs;
});
}
/** @override */
- detached() {
+ disconnectedCallback() {
this.cancelDebouncer(DEBOUNCER_STORE);
+ super.disconnectedCallback();
}
get storageKey() {
@@ -179,7 +179,7 @@
// NOTE: This may be called before attachment (e.g. while parentElement is
// null). Fire title-change in an async so that, if attachment to the DOM
// has been queued, the event can bubble up to the handler in gr-app.
- this.async(() => {
+ setTimeout(() => {
const title = `Editing ${computeTruncatedPath(value.path)}`;
fireTitleChange(this, title);
});
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index ec3bd0f..f33ed8b 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -37,7 +37,6 @@
import './settings/gr-cla-view/gr-cla-view';
import './settings/gr-registration-dialog/gr-registration-dialog';
import './settings/gr-settings-view/gr-settings-view';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-app-element_html';
@@ -80,6 +79,7 @@
import {EventType} from '../utils/event-util';
import {GerritView} from '../services/router/router-model';
import {windowLocationReload} from '../utils/dom-util';
+import {LifeCycle} from '../constants/reporting';
interface ErrorInfo {
text: string;
@@ -99,7 +99,7 @@
// TODO(TS): implement AppElement interface from gr-app-types.ts
@customElement('gr-app-element')
export class GrAppElement extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -254,8 +254,11 @@
this.restApiService.getAccount().then(account => {
this._account = account;
- const role = account ? 'user' : 'guest';
- this.reporting.reportLifeCycle(`Started as ${role}`);
+ if (account) {
+ this.reporting.reportLifeCycle(LifeCycle.STARTED_AS_USER);
+ } else {
+ this.reporting.reportLifeCycle(LifeCycle.STARTED_AS_GUEST);
+ }
});
this.restApiService.getConfig().then(config => {
this._serverConfig = config;
@@ -482,7 +485,7 @@
// because _showPluginScreen value does not change. To force restamp,
// change _showPluginScreen value between true and false.
if (isPluginScreen) {
- this.async(() => this.set('_showPluginScreen', true), 1);
+ setTimeout(() => this.set('_showPluginScreen', true), 1);
}
this.set(
'_showDocumentationSearch',
diff --git a/polygerrit-ui/app/elements/gr-app-types.ts b/polygerrit-ui/app/elements/gr-app-types.ts
index 4809562..c0779a7 100644
--- a/polygerrit-ui/app/elements/gr-app-types.ts
+++ b/polygerrit-ui/app/elements/gr-app-types.ts
@@ -20,6 +20,7 @@
RepoDetailView,
} from './core/gr-navigation/gr-navigation';
import {
+ BasePatchSetNum,
DashboardId,
GroupId,
NumericChangeId,
@@ -100,7 +101,7 @@
commentId?: UrlEncodedCommentId;
path?: string;
patchNum?: PatchSetNum;
- basePatchNum?: PatchSetNum;
+ basePatchNum?: BasePatchSetNum;
lineNum: number;
leftSide?: boolean;
commentLink?: boolean;
@@ -111,7 +112,7 @@
project: RepoName;
edit?: boolean;
patchNum?: PatchSetNum;
- basePatchNum?: PatchSetNum;
+ basePatchNum?: BasePatchSetNum;
queryMap?: Map<string, string> | URLSearchParams;
}
diff --git a/polygerrit-ui/app/elements/gr-app.ts b/polygerrit-ui/app/elements/gr-app.ts
index f19931f..2e7618a 100644
--- a/polygerrit-ui/app/elements/gr-app.ts
+++ b/polygerrit-ui/app/elements/gr-app.ts
@@ -36,7 +36,6 @@
import {initGlobalVariables} from './gr-app-global-var-init';
import './gr-app-element';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-app_html';
@@ -47,7 +46,7 @@
installPolymerResin(safeTypesBridge);
@customElement('gr-app')
-class GrApp extends GestureEventListeners(LegacyElementMixin(PolymerElement)) {
+class GrApp extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts
index 897be67..7a91c68 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts
@@ -16,6 +16,7 @@
*/
import {EventType, PluginApi} from '../../../api/plugin';
import {AdminPluginApi, MenuLink} from '../../../api/admin';
+import {appContext} from '../../../services/app-context';
/**
* GrAdminApi class.
@@ -26,15 +27,20 @@
// TODO(TS): maybe define as enum if its a limited set
private menuLinks: MenuLink[] = [];
+ private readonly reporting = appContext.reportingService;
+
constructor(private readonly plugin: PluginApi) {
+ this.reporting.trackApi(this.plugin, 'admin', 'constructor');
this.plugin.on(EventType.ADMIN_MENU_LINKS, this);
}
addMenuLink(text: string, url: string, capability?: string) {
+ this.reporting.trackApi(this.plugin, 'admin', 'addMenuLink');
this.menuLinks.push({text, url, capability: capability || null});
}
getMenuLinks(): MenuLink[] {
+ this.reporting.trackApi(this.plugin, 'admin', 'getMenuLinks');
return this.menuLinks.slice(0);
}
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
index e0b4ee9..ab2ce6a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
@@ -15,14 +15,20 @@
* limitations under the License.
*/
import {AttributeHelperPluginApi} from '../../../api/attribute-helper';
+import {PluginApi} from '../../../api/plugin';
+import {appContext} from '../../../services/app-context';
export class GrAttributeHelper implements AttributeHelperPluginApi {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private readonly _promises = new Map<string, Promise<any>>();
+ private readonly reporting = appContext.reportingService;
+
// TODO(TS): Change any to something more like HTMLElement.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- constructor(public element: any) {}
+ constructor(readonly plugin: PluginApi, public element: any) {
+ this.reporting.trackApi(this.plugin, 'attribute', 'constructor');
+ }
_getChangedEventName(name: string): string {
return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() + '-changed';
@@ -52,6 +58,7 @@
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
bind(name: string, callback: (value: any) => void) {
+ this.reporting.trackApi(this.plugin, 'attribute', 'bind');
const attributeChangedEventName = this._getChangedEventName(name);
const changedHandler = (e: CustomEvent) =>
this._reportValue(callback, e.detail.value);
@@ -72,6 +79,7 @@
* to be initialized if it isn't defined.
*/
get(name: string): Promise<unknown> {
+ this.reporting.trackApi(this.plugin, 'attribute', 'get');
if (this._elementHasProperty(name)) {
return Promise.resolve(this.element[name]);
}
@@ -93,6 +101,7 @@
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
set(name: string, value: any) {
+ this.reporting.trackApi(this.plugin, 'attribute', 'set');
this.element[name] = value;
this.element.dispatchEvent(
new CustomEvent(this._getChangedEventName(name), {detail: {value}})
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.js b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.js
index 7ea3be3..2d83012 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.js
@@ -17,7 +17,7 @@
import '../../../test/common-test-setup-karma.js';
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
-import {GrAttributeHelper} from './gr-attribute-helper.js';
+import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
Polymer({
is: 'gr-attribute-helper-some-element',
@@ -31,13 +31,18 @@
const basicFixture = fixtureFromElement('gr-attribute-helper-some-element');
+const pluginApi = _testOnly_initGerritPluginApi();
+
suite('gr-attribute-helper tests', () => {
let element;
let instance;
setup(() => {
+ let plugin;
+ pluginApi.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
element = basicFixture.instantiate();
- instance = new GrAttributeHelper(element);
+ instance = plugin.attributeHelper(element);
});
test('resolved on value change from undefined', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts
index a03c5dc..bbb58fc 100644
--- a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts
@@ -17,15 +17,19 @@
import {PluginApi} from '../../../api/plugin';
import {ChangeMetadataPluginApi} from '../../../api/change-metadata';
import {HookApi} from '../../../api/hook';
+import {appContext} from '../../../services/app-context';
export class GrChangeMetadataApi implements ChangeMetadataPluginApi {
private hook: HookApi | null;
public plugin: PluginApi;
+ private readonly reporting = appContext.reportingService;
+
constructor(plugin: PluginApi) {
this.plugin = plugin;
this.hook = null;
+ this.reporting.trackApi(this.plugin, 'metadata', 'constructor');
}
_createHook() {
@@ -33,6 +37,7 @@
}
onLabelsChanged(callback: (value: unknown) => void) {
+ this.reporting.trackApi(this.plugin, 'metadata', 'onLabelsChanged');
if (!this.hook) {
this._createHook();
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts b/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts
index 404fc71..39d3c8b 100644
--- a/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts
@@ -43,13 +43,19 @@
private readonly checksService = appContext.checksService;
- constructor(readonly plugin: PluginApi) {}
+ private readonly reporting = appContext.reportingService;
+
+ constructor(readonly plugin: PluginApi) {
+ this.reporting.trackApi(this.plugin, 'checks', 'constructor');
+ }
announceUpdate() {
+ this.reporting.trackApi(this.plugin, 'checks', 'announceUpdate');
this.checksService.reload(this.plugin.getPluginName());
}
register(provider: ChecksProvider, config?: ChecksApiConfig): void {
+ this.reporting.trackApi(this.plugin, 'checks', 'register');
if (this.state === State.REGISTERED)
throw new Error('Only one provider can be registered per plugin.');
this.state = State.REGISTERED;
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
index 423cff9..b52fef0 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-endpoint-decorator_html';
@@ -30,9 +29,7 @@
const INIT_PROPERTIES_TIMEOUT_MS = 10000;
@customElement('gr-endpoint-decorator')
-export class GrEndpointDecorator extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrEndpointDecorator extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -54,12 +51,12 @@
_endpointCallBack: (info: ModuleInfo) => void = () => {};
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
for (const [el, domHook] of this._domHooks) {
domHook.handleInstanceDetached(el);
}
getPluginEndpoints().onDetachedEndpoint(this.name, this._endpointCallBack);
+ super.disconnectedCallback();
}
_initDecoration(
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts
index 9e9f348..f48bb0f 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {customElement, property} from '@polymer/decorators';
@@ -26,9 +25,7 @@
}
@customElement('gr-endpoint-param')
-export class GrEndpointParam extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrEndpointParam extends LegacyElementMixin(PolymerElement) {
@property({type: String, reflectToAttribute: true})
name = '';
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts
index 4b34d56..0c2f412 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts
@@ -18,6 +18,8 @@
EventHelperPluginApi,
UnsubscribeCallback,
} from '../../../api/event-helper';
+import {PluginApi} from '../../../api/plugin';
+import {appContext} from '../../../services/app-context';
export interface ListenOptions {
event?: string;
@@ -25,13 +27,18 @@
}
export class GrEventHelper implements EventHelperPluginApi {
- constructor(readonly element: HTMLElement) {}
+ private readonly reporting = appContext.reportingService;
+
+ constructor(readonly plugin: PluginApi, readonly element: HTMLElement) {
+ this.reporting.trackApi(this.plugin, 'event', 'constructor');
+ }
/**
* Add a callback to arbitrary event.
* The callback may return false to prevent event bubbling.
*/
on(event: string, callback: (event: Event) => boolean) {
+ this.reporting.trackApi(this.plugin, 'event', 'on');
return this._listen(this.element, callback, {event});
}
@@ -39,6 +46,7 @@
* Alias for @see onClick
*/
onTap(callback: (event: Event) => boolean) {
+ this.reporting.trackApi(this.plugin, 'event', 'onTap');
return this.onClick(callback);
}
@@ -47,6 +55,7 @@
* The callback may return false to prevent event bubbling.
*/
onClick(callback: (event: Event) => boolean) {
+ this.reporting.trackApi(this.plugin, 'event', 'onClick');
return this._listen(this.element, callback);
}
@@ -54,6 +63,7 @@
* Alias for @see captureClick
*/
captureTap(callback: (event: Event) => boolean) {
+ this.reporting.trackApi(this.plugin, 'event', 'captureTap');
return this.captureClick(callback);
}
@@ -64,6 +74,7 @@
* The callback may return false to cancel regular event listeners.
*/
captureClick(callback: (event: Event) => boolean) {
+ this.reporting.trackApi(this.plugin, 'event', 'captureClick');
const parent = this.element.parentElement!;
return this._listen(parent, callback, {capture: true});
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
index 25c0d43..547b575 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
@@ -17,8 +17,8 @@
import '../../../test/common-test-setup-karma.js';
import {addListener} from '@polymer/polymer/lib/utils/gestures.js';
-import {GrEventHelper} from './gr-event-helper.js';
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
+import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
Polymer({
is: 'gr-event-helper-some-element',
@@ -33,13 +33,18 @@
const basicFixture = fixtureFromElement('gr-event-helper-some-element');
+const pluginApi = _testOnly_initGerritPluginApi();
+
suite('gr-event-helper tests', () => {
let element;
let instance;
setup(() => {
+ let plugin;
+ pluginApi.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
element = basicFixture.instantiate();
- instance = new GrEventHelper(element);
+ instance = plugin.eventHelper(element);
});
test('onTap()', done => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.ts b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.ts
index dc3ebcf..25cc354 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import {updateStyles} from '@polymer/polymer/lib/mixins/element-mixin';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-external-style_html';
@@ -24,9 +23,7 @@
import {customElement, property} from '@polymer/decorators';
@customElement('gr-external-style')
-class GrExternalStyle extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrExternalStyle extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -67,8 +64,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._importAndApply();
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts
index 81b3a16..70087a5 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
@@ -22,9 +21,7 @@
import {ServerInfo} from '../../../types/common';
@customElement('gr-plugin-host')
-class GrPluginHost extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrPluginHost extends LegacyElementMixin(PolymerElement) {
@property({type: Object, observer: '_configChanged'})
config?: ServerInfo;
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.ts b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.ts
index 7c6587a..227fab1 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../shared/gr-overlay/gr-overlay';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-plugin-popup_html';
@@ -34,9 +33,7 @@
};
}
@customElement('gr-plugin-popup')
-export class GrPluginPopup extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrPluginPopup extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
index dcabc80..13d18b5 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
@@ -19,6 +19,7 @@
import {GrPluginPopup} from './gr-plugin-popup';
import {PluginApi} from '../../../api/plugin';
import {PopupPluginApi} from '../../../api/popup';
+import {appContext} from '../../../services/app-context';
interface CustomPolymerPluginEl extends HTMLElement {
plugin: PluginApi;
@@ -35,10 +36,14 @@
private popup: GrPluginPopup | null = null;
+ private readonly reporting = appContext.reportingService;
+
constructor(
readonly plugin: PluginApi,
private moduleName: string | null = null
- ) {}
+ ) {
+ this.reporting.trackApi(this.plugin, 'popup', 'constructor');
+ }
_getElement() {
// TODO(TS): maybe consider removing this if no one is using
@@ -52,6 +57,7 @@
* if it was provided with constructor.
*/
open(): Promise<PopupPluginApi> {
+ this.reporting.trackApi(this.plugin, 'popup', 'open');
if (!this.openingPromise) {
this.openingPromise = this.plugin
.hook('plugin-overlay')
@@ -76,6 +82,7 @@
* Hides the popup.
*/
close() {
+ this.reporting.trackApi(this.plugin, 'popup', 'close');
if (!this.popup) {
return;
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts
index 0418edb..51e9112 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts
@@ -19,6 +19,7 @@
import {PluginApi} from '../../../api/plugin';
import {RepoCommandCallback, RepoPluginApi} from '../../../api/repo';
import {HookApi} from '../../../api/hook';
+import {appContext} from '../../../services/app-context';
/**
* Parameters provided on repo-command endpoint
@@ -31,7 +32,11 @@
export class GrRepoApi implements RepoPluginApi {
private hook?: HookApi;
- constructor(readonly plugin: PluginApi) {}
+ private readonly reporting = appContext.reportingService;
+
+ constructor(readonly plugin: PluginApi) {
+ this.reporting.trackApi(this.plugin, 'repo', 'constructor');
+ }
// TODO(TS): should mark as public since used in gr-change-metadata-api
_createHook(title: string) {
@@ -43,6 +48,7 @@
}
createCommand(title: string, callback: RepoCommandCallback) {
+ this.reporting.trackApi(this.plugin, 'repo', 'createCommand');
if (this.hook) {
console.warn('Already set up.');
return this;
@@ -57,6 +63,7 @@
}
onTap(callback: (event: Event) => boolean) {
+ this.reporting.trackApi(this.plugin, 'repo', 'onTap');
if (!this.hook) {
console.warn('Call createCommand first.');
return this;
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts
index 4bdd40e..3f75c0a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts
@@ -18,6 +18,7 @@
import '../../settings/gr-settings-view/gr-settings-menu-item';
import {PluginApi} from '../../../api/plugin';
import {SettingsPluginApi} from '../../../api/settings';
+import {appContext} from '../../../services/app-context';
export class GrSettingsApi implements SettingsPluginApi {
private _token: string;
@@ -26,27 +27,34 @@
private _moduleName?: string;
+ private readonly reporting = appContext.reportingService;
+
constructor(readonly plugin: PluginApi) {
+ this.reporting.trackApi(this.plugin, 'settings', 'constructor');
// Generate default screen URL token, specific to plugin, and unique(ish).
this._token = plugin.getPluginName() + Math.random().toString(36).substr(5);
}
title(newTitle: string) {
+ this.reporting.trackApi(this.plugin, 'settings', 'title');
this._title = newTitle;
return this;
}
token(newToken: string) {
+ this.reporting.trackApi(this.plugin, 'settings', 'token');
this._token = newToken;
return this;
}
module(newModuleName: string) {
+ this.reporting.trackApi(this.plugin, 'settings', 'module');
this._moduleName = newModuleName;
return this;
}
build() {
+ this.reporting.trackApi(this.plugin, 'settings', 'build');
if (!this._moduleName) {
throw new Error('Settings screen custom element not defined!');
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts
index a91b8d3..9a15bf5 100644
--- a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts
@@ -15,6 +15,8 @@
* limitations under the License.
*/
import {StyleObject, StylesPluginApi} from '../../../api/styles';
+import {appContext} from '../../../services/app-context';
+import {PluginApi} from '../../../api/plugin';
/**
* @fileoverview We should consider dropping support for this API:
@@ -77,10 +79,17 @@
* TODO(TS): move to util
*/
export class GrStylesApi implements StylesPluginApi {
+ private readonly reporting = appContext.reportingService;
+
+ constructor(readonly plugin: PluginApi) {
+ this.reporting.trackApi(this.plugin, 'styles', 'constructor');
+ }
+
/**
* Creates a new GrStyleObject with specified style properties.
*/
css(ruleStr: string) {
+ this.reporting.trackApi(this.plugin, 'styles', 'css');
return new GrStyleObject(ruleStr);
}
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts
index 894ec6c..c7be7f6 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts
@@ -18,14 +18,20 @@
import {GrCustomPluginHeader} from './gr-custom-plugin-header';
import {PluginApi} from '../../../api/plugin';
import {ThemePluginApi} from '../../../api/theme';
+import {appContext} from '../../../services/app-context';
/**
* Defines api for theme, can be used to set header logo and title.
*/
export class GrThemeApi implements ThemePluginApi {
- constructor(private readonly plugin: PluginApi) {}
+ private readonly reporting = appContext.reportingService;
+
+ constructor(private readonly plugin: PluginApi) {
+ this.reporting.trackApi(this.plugin, 'theme', 'constructor');
+ }
setHeaderLogoAndTitle(logoUrl: string, title: string) {
+ this.reporting.trackApi(this.plugin, 'theme', 'setHeaderLogoAndTitle');
this.plugin.hook('header-title', {replace: true}).onAttached(element => {
const customHeader: GrCustomPluginHeader = document.createElement(
'gr-custom-plugin-header'
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
index 5786576..1bb32a1 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
@@ -19,7 +19,6 @@
import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-account-info_html';
@@ -30,9 +29,7 @@
import {fireEvent} from '../../../utils/event-util';
@customElement('gr-account-info')
-export class GrAccountInfo extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAccountInfo extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.ts
index fe4ef8e..01ebfbd 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.ts
@@ -67,13 +67,16 @@
<span class="title">Username</span>
<span hidden$="[[usernameMutable]]" class="value">[[_username]]</span>
<span hidden$="[[!usernameMutable]]" class="value">
- <iron-input on-keydown="_handleKeydown" bind-value="{{_username}}">
+ <iron-input
+ on-keydown="_handleKeydown"
+ bind-value="{{_username}}"
+ id="usernameIronInput"
+ >
<input
is="iron-input"
id="usernameInput"
disabled="[[_saving]]"
on-keydown="_handleKeydown"
- bind-value="{{_username}}"
/>
</iron-input>
</span>
@@ -82,13 +85,16 @@
<label class="title" for="nameInput">Full name</label>
<span hidden$="[[nameMutable]]" class="value">[[_account.name]]</span>
<span hidden$="[[!nameMutable]]" class="value">
- <iron-input on-keydown="_handleKeydown" bind-value="{{_account.name}}">
+ <iron-input
+ on-keydown="_handleKeydown"
+ bind-value="{{_account.name}}"
+ id="nameIronInput"
+ >
<input
is="iron-input"
id="nameInput"
disabled="[[_saving]]"
on-keydown="_handleKeydown"
- bind-value="{{_account.name}}"
/>
</iron-input>
</span>
@@ -105,7 +111,6 @@
id="displayNameInput"
disabled="[[_saving]]"
on-keydown="_handleKeydown"
- bind-value="{{_account.display_name}}"
/>
</iron-input>
</span>
@@ -122,7 +127,6 @@
id="statusInput"
disabled="[[_saving]]"
on-keydown="_handleKeydown"
- bind-value="{{_account.status}}"
/>
</iron-input>
</span>
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.js b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
similarity index 67%
rename from polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.js
rename to polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
index c7cb44e..6aa7f7c 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
@@ -15,52 +15,63 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-account-info.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import '../../../test/common-test-setup-karma';
+import './gr-account-info';
+import {SinonSpyMember, stubRestApi} from '../../../test/test-utils';
+import {GrAccountInfo} from './gr-account-info';
+import {AccountDetailInfo, ServerInfo} from '../../../types/common';
+import {
+ createAccountWithIdNameAndEmail,
+ createPreferences,
+ createServerInfo,
+} from '../../../test/test-data-generators';
+import {IronInputElement} from '@polymer/iron-input';
+import {SinonStubbedMember} from 'sinon/pkg/sinon-esm';
+import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
const basicFixture = fixtureFromElement('gr-account-info');
suite('gr-account-info tests', () => {
- let element;
- let account;
- let config;
+ let element!: GrAccountInfo;
+ let account: AccountDetailInfo;
+ let config: ServerInfo;
- function valueOf(title) {
- const sections = element.root.querySelectorAll('section');
+ function queryIronInput(selector: string): IronInputElement {
+ const input = element.root?.querySelector<IronInputElement>(selector);
+ if (!input) assert.fail(`<iron-input> with id ${selector} not found.`);
+ return input;
+ }
+
+ function valueOf(title: string): Element {
+ const sections = element.root?.querySelectorAll('section') ?? [];
let titleEl;
for (let i = 0; i < sections.length; i++) {
titleEl = sections[i].querySelector('.title');
- if (titleEl.textContent === title) {
- return sections[i].querySelector('.value');
+ if (titleEl?.textContent === title) {
+ const el = sections[i].querySelector('.value');
+ if (el) return el;
}
}
+ assert.fail(`element with title ${title} not found`);
}
- setup(done => {
- account = {
- _account_id: 123,
- name: 'user name',
- email: 'user@email',
- username: 'user username',
- registered: '2000-01-01 00:00:00.000000000',
- };
- config = {auth: {editable_account_fields: []}};
+ setup(async () => {
+ account = createAccountWithIdNameAndEmail(123) as AccountDetailInfo;
+ config = createServerInfo();
stubRestApi('getAccount').returns(Promise.resolve(account));
stubRestApi('getConfig').returns(Promise.resolve(config));
- stubRestApi('getPreferences').returns(
- Promise.resolve({time_format: 'HHMM_12'}));
+ stubRestApi('getPreferences').returns(Promise.resolve(createPreferences()));
element = basicFixture.instantiate();
- // Allow the element to render.
- element.loadData().then(() => { flush(done); });
+ await element.loadData();
+ await flush();
});
test('basic account info render', () => {
assert.isFalse(element._loading);
- assert.equal(valueOf('ID').textContent, account._account_id);
+ assert.equal(valueOf('ID').textContent, `${account._account_id}`);
assert.equal(valueOf('Email').textContent, account.email);
assert.equal(valueOf('Username').textContent, account.username);
});
@@ -77,8 +88,9 @@
});
test('full name render (mutable)', () => {
- element.set('_serverConfig',
- {auth: {editable_account_fields: ['FULL_NAME']}});
+ element.set('_serverConfig', {
+ auth: {editable_account_fields: ['FULL_NAME']},
+ });
const section = element.$.nameSection;
const displaySpan = section.querySelectorAll('.value')[0];
@@ -86,7 +98,7 @@
assert.isTrue(element.nameMutable);
assert.isTrue(displaySpan.hasAttribute('hidden'));
- assert.equal(element.$.nameInput.bindValue, account.name);
+ assert.equal(queryIronInput('#nameIronInput').bindValue, account.name);
assert.isFalse(inputSpan.hasAttribute('hidden'));
});
@@ -102,8 +114,9 @@
});
test('username render (mutable)', () => {
- element.set('_serverConfig',
- {auth: {editable_account_fields: ['USER_NAME']}});
+ element.set('_serverConfig', {
+ auth: {editable_account_fields: ['USER_NAME']},
+ });
element.set('_account.username', '');
element.set('_username', '');
@@ -113,32 +126,34 @@
assert.isTrue(element.usernameMutable);
assert.isTrue(displaySpan.hasAttribute('hidden'));
- assert.equal(element.$.usernameInput.bindValue, account.username);
+ assert.equal(
+ queryIronInput('#usernameIronInput').bindValue,
+ account.username
+ );
assert.isFalse(inputSpan.hasAttribute('hidden'));
});
suite('account info edit', () => {
- let nameChangedSpy;
- let usernameChangedSpy;
- let statusChangedSpy;
- let nameStub;
- let usernameStub;
- let statusStub;
+ let nameChangedSpy: SinonSpyMember<typeof element._nameChanged>;
+ let usernameChangedSpy: SinonSpyMember<typeof element._usernameChanged>;
+ let statusChangedSpy: SinonSpyMember<typeof element._statusChanged>;
+ let nameStub: SinonStubbedMember<RestApiService['setAccountName']>;
+ let usernameStub: SinonStubbedMember<RestApiService['setAccountUsername']>;
+ let statusStub: SinonStubbedMember<RestApiService['setAccountStatus']>;
setup(() => {
nameChangedSpy = sinon.spy(element, '_nameChanged');
usernameChangedSpy = sinon.spy(element, '_usernameChanged');
statusChangedSpy = sinon.spy(element, '_statusChanged');
- element.set('_serverConfig',
- {auth: {editable_account_fields: ['FULL_NAME', 'USER_NAME']}});
+ element.set('_serverConfig', {
+ auth: {editable_account_fields: ['FULL_NAME', 'USER_NAME']},
+ });
- nameStub = stubRestApi('setAccountName').callsFake(
- name => Promise.resolve());
- usernameStub = stubRestApi('setAccountUsername')
- .callsFake(username => Promise.resolve());
- statusStub = stubRestApi(
- 'setAccountStatus').callsFake(
- status => Promise.resolve());
+ nameStub = stubRestApi('setAccountName').returns(Promise.resolve());
+ usernameStub = stubRestApi('setAccountUsername').returns(
+ Promise.resolve()
+ );
+ statusStub = stubRestApi('setAccountStatus').returns(Promise.resolve());
});
test('name', done => {
@@ -206,24 +221,21 @@
});
suite('edit name and status', () => {
- let nameChangedSpy;
- let statusChangedSpy;
- let nameStub;
- let statusStub;
+ let nameChangedSpy: SinonSpyMember<typeof element._nameChanged>;
+ let statusChangedSpy: SinonSpyMember<typeof element._statusChanged>;
+ let nameStub: SinonStubbedMember<RestApiService['setAccountName']>;
+ let statusStub: SinonStubbedMember<RestApiService['setAccountStatus']>;
setup(() => {
nameChangedSpy = sinon.spy(element, '_nameChanged');
statusChangedSpy = sinon.spy(element, '_statusChanged');
- element.set('_serverConfig',
- {auth: {editable_account_fields: ['FULL_NAME']}});
+ element.set('_serverConfig', {
+ auth: {editable_account_fields: ['FULL_NAME']},
+ });
- nameStub = stubRestApi('setAccountName').callsFake(
- name => Promise.resolve());
- statusStub = stubRestApi(
- 'setAccountStatus').callsFake(
- status => Promise.resolve());
- stubRestApi('setAccountUsername').callsFake(
- username => Promise.resolve());
+ nameStub = stubRestApi('setAccountName').returns(Promise.resolve());
+ statusStub = stubRestApi('setAccountStatus').returns(Promise.resolve());
+ stubRestApi('setAccountUsername').returns(Promise.resolve());
});
test('set name and status', done => {
@@ -254,17 +266,14 @@
});
suite('set status but read name', () => {
- let statusChangedSpy;
- let statusStub;
+ let statusChangedSpy: SinonSpyMember<typeof element._statusChanged>;
+ let statusStub: SinonStubbedMember<RestApiService['setAccountStatus']>;
setup(() => {
statusChangedSpy = sinon.spy(element, '_statusChanged');
- element.set('_serverConfig',
- {auth: {editable_account_fields: []}});
+ element.set('_serverConfig', {auth: {editable_account_fields: []}});
- statusStub = stubRestApi(
- 'setAccountStatus').callsFake(
- status => Promise.resolve());
+ statusStub = stubRestApi('setAccountStatus').returns(Promise.resolve());
});
test('read full name but set status', done => {
@@ -320,4 +329,3 @@
assert.equal(element._hideAvatarChangeUrl('https://example.com'), '');
});
});
-
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
index da180c3..6768497 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
@@ -17,7 +17,6 @@
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-agreements-list_html';
@@ -27,9 +26,7 @@
import {appContext} from '../../../services/app-context';
@customElement('gr-agreements-list')
-export class GrAgreementsList extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAgreementsList extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -40,8 +37,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.loadData();
}
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.js b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.ts
similarity index 61%
rename from polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.js
rename to polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.ts
index c8ce574..f08976f 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.ts
@@ -14,41 +14,43 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-agreements-list.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import '../../../test/common-test-setup-karma';
+import './gr-agreements-list';
+import {stubRestApi} from '../../../test/test-utils';
+import {GrAgreementsList} from './gr-agreements-list';
+import {ContributorAgreementInfo} from '../../../types/common';
const basicFixture = fixtureFromElement('gr-agreements-list');
suite('gr-agreements-list tests', () => {
- let element;
- let agreements;
+ let element: GrAgreementsList;
setup(done => {
- agreements = [{
- url: 'some url',
- description: 'Agreements 1 description',
- name: 'Agreements 1',
- }];
+ const agreements: ContributorAgreementInfo[] = [
+ {
+ url: 'some url',
+ description: 'Agreements 1 description',
+ name: 'Agreements 1',
+ },
+ ];
stubRestApi('getAccountAgreements').returns(Promise.resolve(agreements));
element = basicFixture.instantiate();
- element.loadData().then(() => { flush(done); });
+ element.loadData().then(() => {
+ flush(done);
+ });
});
test('renders', () => {
- const rows = element.root.querySelectorAll('tbody tr');
-
+ const rows = element.root?.querySelectorAll('tbody tr') ?? [];
assert.equal(rows.length, 1);
const nameCells = Array.from(rows).map(row =>
- row.querySelectorAll('td')[0].textContent.trim()
+ row.querySelectorAll('td')[0].textContent?.trim()
);
assert.equal(nameCells[0], 'Agreements 1');
});
});
-
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
index df927d6..970fb5f 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
@@ -19,7 +19,6 @@
import '../../../styles/shared-styles';
import '../../../styles/gr-form-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-table-editor_html';
@@ -29,15 +28,15 @@
import {appContext} from '../../../services/app-context';
@customElement('gr-change-table-editor')
-class GrChangeTableEditor extends ChangeTableMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+export class GrChangeTableEditor extends ChangeTableMixin(
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
}
@property({type: Array, notify: true})
- displayedColumns?: string[];
+ displayedColumns: string[] = [];
@property({type: Boolean, notify: true})
showNumber?: boolean;
@@ -46,7 +45,7 @@
serverConfig?: ServerInfo;
@property({type: Array})
- defaultColumns?: string[];
+ defaultColumns: string[] = [];
flagsService = appContext.flagsService;
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.js b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.js
deleted file mode 100644
index db5c035..0000000
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.js
+++ /dev/null
@@ -1,173 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 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.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-change-table-editor.js';
-
-const basicFixture = fixtureFromElement('gr-change-table-editor');
-
-suite('gr-change-table-editor tests', () => {
- let element;
- let columns;
-
- setup(() => {
- element = basicFixture.instantiate();
-
- columns = [
- 'Subject',
- 'Status',
- 'Owner',
- 'Assignee',
- 'Reviewers',
- 'Comments',
- 'Repo',
- 'Branch',
- 'Updated',
- ];
-
- element.set('displayedColumns', columns);
- element.showNumber = false;
- element.serverConfig = {
- change: {},
- };
- flush();
- });
-
- test('renders', () => {
- const rows = element.shadowRoot
- .querySelector('tbody').querySelectorAll('tr');
- let tds;
-
- // The `+ 1` is for the number column, which isn't included in the change
- // table behavior's list.
- assert.equal(rows.length, element.defaultColumns.length + 1);
- for (let i = 0; i < element.defaultColumns.length; i++) {
- tds = rows[i + 1].querySelectorAll('td');
- assert.equal(tds[0].textContent, element.defaultColumns[i]);
- }
- });
-
- test('disabled experiments are hidden', () => {
- assert.isFalse(element.displayedColumns.includes('Assignee'));
- element.set('displayedColumns', columns);
- element.serverConfig = {
- change: {
- enable_assignee: true,
- },
- };
- flush();
- assert.isTrue(element.displayedColumns.includes('Assignee'));
- });
-
- test('hide item', () => {
- const checkbox = element.shadowRoot
- .querySelector('table tr:nth-child(2) input');
- const isChecked = checkbox.checked;
- const displayedLength = element.displayedColumns.length;
- assert.isTrue(isChecked);
-
- MockInteractions.tap(checkbox);
- flush();
-
- assert.equal(element.displayedColumns.length, displayedLength - 1);
- });
-
- test('show item', () => {
- element.set('displayedColumns', [
- 'Status',
- 'Owner',
- 'Assignee',
- 'Repo',
- 'Branch',
- 'Updated',
- ]);
- // trigger computation of enabled displayed columns
- element.serverConfig = {
- change: {},
- };
- flush();
- const checkbox = element.shadowRoot
- .querySelector('table tr:nth-child(2) input');
- const isChecked = checkbox.checked;
- const displayedLength = element.displayedColumns.length;
- assert.isFalse(isChecked);
- assert.equal(element.shadowRoot
- .querySelector('table').style.display, '');
-
- MockInteractions.tap(checkbox);
- flush();
-
- assert.equal(element.displayedColumns.length,
- displayedLength + 1);
- });
-
- test('_getDisplayedColumns', () => {
- const enabledColumns = columns.filter(column => element.isColumnEnabled(
- column, element.serverConfig, []
- ));
- assert.deepEqual(element._getDisplayedColumns(), enabledColumns);
- MockInteractions.tap(
- element.shadowRoot
- .querySelector('.checkboxContainer input[name=Subject]'));
- assert.deepEqual(element._getDisplayedColumns(),
- enabledColumns.filter(c => c !== 'Subject'));
- });
-
- test('_handleCheckboxContainerClick relays taps to checkboxes', () => {
- sinon.stub(element, '_handleNumberCheckboxClick');
- sinon.stub(element, '_handleTargetClick');
-
- MockInteractions.tap(
- element.shadowRoot
- .querySelector('table tr:first-of-type .checkboxContainer'));
- assert.isTrue(element._handleNumberCheckboxClick.calledOnce);
- assert.isFalse(element._handleTargetClick.called);
-
- MockInteractions.tap(
- element.shadowRoot
- .querySelector('table tr:last-of-type .checkboxContainer'));
- assert.isTrue(element._handleNumberCheckboxClick.calledOnce);
- assert.isTrue(element._handleTargetClick.calledOnce);
- });
-
- test('_handleNumberCheckboxClick', () => {
- sinon.spy(element, '_handleNumberCheckboxClick');
-
- MockInteractions
- .tap(element.shadowRoot
- .querySelector('.checkboxContainer input[name=number]'));
- assert.isTrue(element._handleNumberCheckboxClick.calledOnce);
- assert.isTrue(element.showNumber);
-
- MockInteractions
- .tap(element.shadowRoot
- .querySelector('.checkboxContainer input[name=number]'));
- assert.isTrue(element._handleNumberCheckboxClick.calledTwice);
- assert.isFalse(element.showNumber);
- });
-
- test('_handleTargetClick', () => {
- sinon.spy(element, '_handleTargetClick');
- assert.include(element.displayedColumns, 'Subject');
- MockInteractions
- .tap(element.shadowRoot
- .querySelector('.checkboxContainer input[name=Subject]'));
- assert.isTrue(element._handleTargetClick.calledOnce);
- assert.notInclude(element.displayedColumns, 'Subject');
- });
-});
-
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts
new file mode 100644
index 0000000..4f61972
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts
@@ -0,0 +1,182 @@
+/**
+ * @license
+ * Copyright (C) 2016 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.
+ */
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
+import '../../../test/common-test-setup-karma';
+import './gr-change-table-editor';
+import {GrChangeTableEditor} from './gr-change-table-editor';
+import {queryAndAssert} from '../../../test/test-utils';
+import {createServerInfo} from '../../../test/test-data-generators';
+import {ServerInfo} from '../../../types/common';
+
+const basicFixture = fixtureFromElement('gr-change-table-editor');
+
+suite('gr-change-table-editor tests', () => {
+ let element: GrChangeTableEditor;
+ let columns: string[];
+
+ setup(async () => {
+ element = basicFixture.instantiate();
+
+ columns = [
+ 'Subject',
+ 'Status',
+ 'Owner',
+ 'Assignee',
+ 'Reviewers',
+ 'Comments',
+ 'Repo',
+ 'Branch',
+ 'Updated',
+ ];
+
+ element.set('displayedColumns', columns);
+ element.showNumber = false;
+ element.serverConfig = createServerInfo();
+ await flush();
+ });
+
+ test('renders', () => {
+ const rows = queryAndAssert(element, 'tbody').querySelectorAll('tr');
+ let tds;
+
+ // The `+ 1` is for the number column, which isn't included in the change
+ // table behavior's list.
+ assert.equal(rows.length, element.defaultColumns.length + 1);
+ for (let i = 0; i < element.defaultColumns.length; i++) {
+ tds = rows[i + 1].querySelectorAll('td');
+ assert.equal(tds[0].textContent, element.defaultColumns[i]);
+ }
+ });
+
+ test('disabled experiments are hidden', () => {
+ assert.isFalse(element.displayedColumns.includes('Assignee'));
+ element.set('displayedColumns', columns);
+ const config: ServerInfo = {...createServerInfo()};
+ config.change.enable_assignee = true;
+ element.serverConfig = config;
+ flush();
+ assert.isTrue(element.displayedColumns.includes('Assignee'));
+ });
+
+ test('hide item', () => {
+ const checkbox = queryAndAssert<HTMLInputElement>(
+ element,
+ 'table tr:nth-child(2) input'
+ );
+ const isChecked = checkbox.checked;
+ const displayedLength = element.displayedColumns.length;
+ assert.isTrue(isChecked);
+
+ MockInteractions.tap(checkbox);
+ flush();
+
+ assert.equal(element.displayedColumns.length, displayedLength - 1);
+ });
+
+ test('show item', () => {
+ element.set('displayedColumns', [
+ 'Status',
+ 'Owner',
+ 'Assignee',
+ 'Repo',
+ 'Branch',
+ 'Updated',
+ ]);
+ // trigger computation of enabled displayed columns
+ element.serverConfig = createServerInfo();
+ flush();
+ const checkbox = queryAndAssert<HTMLInputElement>(
+ element,
+ 'table tr:nth-child(2) input'
+ );
+ const isChecked = checkbox.checked;
+ const displayedLength = element.displayedColumns.length;
+ assert.isFalse(isChecked);
+ const table = queryAndAssert<HTMLTableElement>(element, 'table');
+ assert.equal(table.style.display, '');
+
+ MockInteractions.tap(checkbox);
+ flush();
+
+ assert.equal(element.displayedColumns.length, displayedLength + 1);
+ });
+
+ test('_getDisplayedColumns', () => {
+ const enabledColumns = columns.filter(column =>
+ element.isColumnEnabled(column, element.serverConfig!, [])
+ );
+ assert.deepEqual(element._getDisplayedColumns(), enabledColumns);
+ const input = queryAndAssert<HTMLInputElement>(
+ element,
+ '.checkboxContainer input[name=Subject]'
+ );
+ MockInteractions.tap(input);
+ assert.deepEqual(
+ element._getDisplayedColumns(),
+ enabledColumns.filter(c => c !== 'Subject')
+ );
+ });
+
+ test('_handleCheckboxContainerClick relays taps to checkboxes', () => {
+ const checkBoxClickStub = sinon.stub(element, '_handleNumberCheckboxClick');
+ const targetClickStub = sinon.stub(element, '_handleTargetClick');
+
+ const firstContainer = queryAndAssert(
+ element,
+ 'table tr:first-of-type .checkboxContainer'
+ );
+ MockInteractions.tap(firstContainer);
+ assert.isTrue(checkBoxClickStub.calledOnce);
+ assert.isFalse(targetClickStub.called);
+
+ const lastContainer = queryAndAssert(
+ element,
+ 'table tr:last-of-type .checkboxContainer'
+ );
+ MockInteractions.tap(lastContainer);
+ assert.isTrue(checkBoxClickStub.calledOnce);
+ assert.isTrue(targetClickStub.calledOnce);
+ });
+
+ test('_handleNumberCheckboxClick', () => {
+ const checkBoxClickSpy = sinon.spy(element, '_handleNumberCheckboxClick');
+
+ const numberInput = queryAndAssert(
+ element,
+ '.checkboxContainer input[name=number]'
+ );
+ MockInteractions.tap(numberInput);
+ assert.isTrue(checkBoxClickSpy.calledOnce);
+ assert.isTrue(element.showNumber);
+
+ MockInteractions.tap(numberInput);
+ assert.isTrue(checkBoxClickSpy.calledTwice);
+ assert.isFalse(element.showNumber);
+ });
+
+ test('_handleTargetClick', () => {
+ const targetClickSpy = sinon.spy(element, '_handleTargetClick');
+ assert.include(element.displayedColumns, 'Subject');
+ const subjectInput = queryAndAssert(
+ element,
+ '.checkboxContainer input[name=Subject]'
+ );
+ MockInteractions.tap(subjectInput);
+ assert.isTrue(targetClickSpy.calledOnce);
+ assert.notInclude(element.displayedColumns, 'Subject');
+ });
+});
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
index 9f7fadf..2ae1634 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
@@ -19,7 +19,6 @@
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-cla-view_html';
@@ -40,9 +39,7 @@
}
@customElement('gr-cla-view')
-export class GrClaView extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrClaView extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -71,8 +68,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.loadData();
fireTitleChange(this, 'New Contributor Agreement');
@@ -155,7 +152,7 @@
_disableAgreements(
item: ContributorAgreementInfo,
- groups: GroupInfo[],
+ groups?: GroupInfo[],
signedAgreements?: ContributorAgreementInfo[]
) {
if (!groups) return false;
@@ -189,7 +186,7 @@
// then hides the text box and submit button.
_computeHideAgreementClass(
name: string,
- contributorAgreements: ContributorAgreementInfo[]
+ contributorAgreements?: ContributorAgreementInfo[]
) {
if (!contributorAgreements) return '';
return contributorAgreements.some(
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.js b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.js
deleted file mode 100644
index 7c11136..0000000
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.js
+++ /dev/null
@@ -1,180 +0,0 @@
-/**
- * @license
- * Copyright (C) 2018 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.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-cla-view.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-
-const basicFixture = fixtureFromElement('gr-cla-view');
-
-suite('gr-cla-view tests', () => {
- let element;
- const signedAgreements = [{
- name: 'CLA',
- description: 'Contributor License Agreement',
- url: 'static/cla.html',
- }];
- const auth = {
- name: 'Individual',
- description: 'test-description',
- url: 'static/cla_individual.html',
- auto_verify_group: {
- url: '#/admin/groups/uuid-e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
- options: {
- visible_to_all: true,
- },
- group_id: 20,
- owner: 'CLA Accepted - Individual',
- owner_id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
- created_on: '2017-07-31 15:11:04.000000000',
- id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
- name: 'CLA Accepted - Individual',
- },
- };
-
- const auth2 = {
- name: 'Individual2',
- description: 'test-description2',
- url: 'static/cla_individual2.html',
- auto_verify_group: {
- url: '#/admin/groups/uuid-bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
- options: {},
- group_id: 21,
- owner: 'CLA Accepted - Individual2',
- owner_id: 'bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
- created_on: '2017-07-31 15:25:42.000000000',
- id: 'bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
- name: 'CLA Accepted - Individual2',
- },
- };
-
- const auth3 = {
- name: 'CLA',
- description: 'Contributor License Agreement',
- url: 'static/cla_individual.html',
- };
-
- const config = {
- auth: {
- use_contributor_agreements: true,
- contributor_agreements: [
- {
- name: 'Individual',
- description: 'test-description',
- url: 'static/cla_individual.html',
- },
- {
- name: 'CLA',
- description: 'Contributor License Agreement',
- url: 'static/cla.html',
- }],
- },
- };
- const config2 = {
- auth: {
- use_contributor_agreements: true,
- contributor_agreements: [
- {
- name: 'Individual2',
- description: 'test-description2',
- url: 'static/cla_individual2.html',
- },
- ],
- },
- };
- const groups = [{
- options: {visible_to_all: true},
- id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
- group_id: 3,
- name: 'CLA Accepted - Individual',
- },
- ];
-
- setup(done => {
- stubRestApi('getConfig').returns(Promise.resolve(config));
- stubRestApi('getAccountGroups').returns(Promise.resolve(groups));
- stubRestApi('getAccountAgreements').returns(
- Promise.resolve(signedAgreements));
- element = basicFixture.instantiate();
- element.loadData().then(() => { flush(done); });
- });
-
- test('renders as expected with signed agreement', () => {
- const agreementSections = dom(element.root)
- .querySelectorAll('.contributorAgreementButton');
- const agreementSubmittedTexts = dom(element.root)
- .querySelectorAll('.alreadySubmittedText');
- assert.equal(agreementSections.length, 2);
- assert.isFalse(agreementSections[0].querySelector('input').disabled);
- assert.equal(getComputedStyle(agreementSubmittedTexts[0]).display,
- 'none');
- assert.isTrue(agreementSections[1].querySelector('input').disabled);
- assert.notEqual(getComputedStyle(agreementSubmittedTexts[1]).display,
- 'none');
- });
-
- test('_disableAgreements', () => {
- // In the auto verify group and have not yet signed agreement
- assert.isTrue(
- element._disableAgreements(auth, groups, signedAgreements));
- // Not in the auto verify group and have not yet signed agreement
- assert.isFalse(
- element._disableAgreements(auth2, groups, signedAgreements));
- // Not in the auto verify group, have signed agreement
- assert.isTrue(
- element._disableAgreements(auth3, groups, signedAgreements));
- // Make sure the undefined check works
- assert.isFalse(
- element._disableAgreements(auth, undefined, signedAgreements));
- });
-
- test('_hideAgreements', () => {
- // Not in the auto verify group and have not yet signed agreement
- assert.equal(
- element._hideAgreements(auth, groups, signedAgreements), '');
- // In the auto verify group
- assert.equal(
- element._hideAgreements(auth2, groups, signedAgreements), 'hide');
- // Not in the auto verify group, have signed agreement
- assert.equal(
- element._hideAgreements(auth3, groups, signedAgreements), '');
- });
-
- test('_disableAgreementsText', () => {
- assert.isFalse(element._disableAgreementsText('I AGREE'));
- assert.isTrue(element._disableAgreementsText('I DO NOT AGREE'));
- });
-
- test('_computeHideAgreementClass', () => {
- assert.equal(
- element._computeHideAgreementClass(
- auth.name, config.auth.contributor_agreements),
- 'hideAgreementsTextBox');
- assert.isNotOk(
- element._computeHideAgreementClass(
- auth.name, config2.auth.contributor_agreements));
- });
-
- test('_getAgreementsUrl', () => {
- assert.equal(element._getAgreementsUrl(
- 'http://test.org/test.html'), 'http://test.org/test.html');
- assert.equal(element._getAgreementsUrl(
- 'test_cla.html'), '/test_cla.html');
- });
-});
-
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.ts
new file mode 100644
index 0000000..9f54cd1
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.ts
@@ -0,0 +1,210 @@
+/**
+ * @license
+ * Copyright (C) 2018 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.
+ */
+import '../../../test/common-test-setup-karma';
+import './gr-cla-view';
+import {queryAll, queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {GrClaView} from './gr-cla-view';
+import {
+ ContributorAgreementInfo,
+ GroupId,
+ GroupInfo,
+ GroupName,
+ ServerInfo,
+} from '../../../types/common';
+import {AuthType} from '../../../constants/constants';
+import {createServerInfo} from '../../../test/test-data-generators';
+
+const basicFixture = fixtureFromElement('gr-cla-view');
+
+suite('gr-cla-view tests', () => {
+ let element: GrClaView;
+ const signedAgreements = [
+ {
+ name: 'CLA',
+ description: 'Contributor License Agreement',
+ url: 'static/cla.html',
+ },
+ ];
+ const auth: ContributorAgreementInfo = {
+ name: 'Individual',
+ description: 'test-description',
+ url: 'static/cla_individual.html',
+ auto_verify_group: {
+ url: '#/admin/groups/uuid-e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
+ options: {
+ visible_to_all: true,
+ },
+ group_id: '20',
+ owner: 'CLA Accepted - Individual',
+ owner_id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
+ created_on: '2017-07-31 15:11:04.000000000',
+ id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0' as GroupId,
+ name: 'CLA Accepted - Individual' as GroupName,
+ },
+ };
+
+ const auth2: ContributorAgreementInfo = {
+ name: 'Individual2',
+ description: 'test-description2',
+ url: 'static/cla_individual2.html',
+ auto_verify_group: {
+ url: '#/admin/groups/uuid-bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
+ options: {
+ visible_to_all: false,
+ },
+ group_id: '21',
+ owner: 'CLA Accepted - Individual2',
+ owner_id: 'bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
+ created_on: '2017-07-31 15:25:42.000000000',
+ id: 'bc53f2738ef8ad0b3a4f53846ff59b05822caecb' as GroupId,
+ name: 'CLA Accepted - Individual2' as GroupName,
+ },
+ };
+
+ const auth3: ContributorAgreementInfo = {
+ name: 'CLA',
+ description: 'Contributor License Agreement',
+ url: 'static/cla_individual.html',
+ };
+
+ const config: ServerInfo = {
+ ...createServerInfo(),
+ auth: {
+ auth_type: AuthType.HTTP,
+ editable_account_fields: [],
+ use_contributor_agreements: true,
+ contributor_agreements: [
+ {
+ name: 'Individual',
+ description: 'test-description',
+ url: 'static/cla_individual.html',
+ },
+ {
+ name: 'CLA',
+ description: 'Contributor License Agreement',
+ url: 'static/cla.html',
+ },
+ ],
+ },
+ };
+ const config2: ServerInfo = {
+ ...createServerInfo(),
+ auth: {
+ auth_type: AuthType.HTTP,
+ editable_account_fields: [],
+ use_contributor_agreements: true,
+ contributor_agreements: [
+ {
+ name: 'Individual2',
+ description: 'test-description2',
+ url: 'static/cla_individual2.html',
+ },
+ ],
+ },
+ };
+ const groups: GroupInfo[] = [
+ {
+ options: {visible_to_all: true},
+ id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0' as GroupId,
+ group_id: '3',
+ name: 'CLA Accepted - Individual' as GroupName,
+ },
+ ];
+
+ setup(done => {
+ stubRestApi('getConfig').returns(Promise.resolve(config));
+ stubRestApi('getAccountGroups').returns(Promise.resolve(groups));
+ stubRestApi('getAccountAgreements').returns(
+ Promise.resolve(signedAgreements)
+ );
+ element = basicFixture.instantiate();
+ element.loadData().then(() => {
+ flush(done);
+ });
+ });
+
+ test('renders as expected with signed agreement', () => {
+ const agreementSections = queryAll(element, '.contributorAgreementButton');
+ const agreementSubmittedTexts = queryAll(element, '.alreadySubmittedText');
+ assert.equal(agreementSections.length, 2);
+ assert.isFalse(
+ queryAndAssert<HTMLInputElement>(agreementSections[0], 'input').disabled
+ );
+ assert.equal(getComputedStyle(agreementSubmittedTexts[0]).display, 'none');
+ assert.isTrue(
+ queryAndAssert<HTMLInputElement>(agreementSections[1], 'input').disabled
+ );
+ assert.notEqual(
+ getComputedStyle(agreementSubmittedTexts[1]).display,
+ 'none'
+ );
+ });
+
+ test('_disableAgreements', () => {
+ // In the auto verify group and have not yet signed agreement
+ assert.isTrue(element._disableAgreements(auth, groups, signedAgreements));
+ // Not in the auto verify group and have not yet signed agreement
+ assert.isFalse(element._disableAgreements(auth2, groups, signedAgreements));
+ // Not in the auto verify group, have signed agreement
+ assert.isTrue(element._disableAgreements(auth3, groups, signedAgreements));
+ // Make sure the undefined check works
+ assert.isFalse(
+ element._disableAgreements(auth, undefined, signedAgreements)
+ );
+ });
+
+ test('_hideAgreements', () => {
+ // Not in the auto verify group and have not yet signed agreement
+ assert.equal(element._hideAgreements(auth, groups, signedAgreements), '');
+ // In the auto verify group
+ assert.equal(
+ element._hideAgreements(auth2, groups, signedAgreements),
+ 'hide'
+ );
+ // Not in the auto verify group, have signed agreement
+ assert.equal(element._hideAgreements(auth3, groups, signedAgreements), '');
+ });
+
+ test('_disableAgreementsText', () => {
+ assert.isFalse(element._disableAgreementsText('I AGREE'));
+ assert.isTrue(element._disableAgreementsText('I DO NOT AGREE'));
+ });
+
+ test('_computeHideAgreementClass', () => {
+ assert.equal(
+ element._computeHideAgreementClass(
+ auth.name,
+ config.auth.contributor_agreements
+ ),
+ 'hideAgreementsTextBox'
+ );
+ assert.isNotOk(
+ element._computeHideAgreementClass(
+ auth.name,
+ config2.auth.contributor_agreements
+ )
+ );
+ });
+
+ test('_getAgreementsUrl', () => {
+ assert.equal(
+ element._getAgreementsUrl('http://test.org/test.html'),
+ 'http://test.org/test.html'
+ );
+ assert.equal(element._getAgreementsUrl('test_cla.html'), '/test_cla.html');
+ });
+});
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.ts b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.ts
index 411d3aa..5d8cc51 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.ts
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.ts
@@ -18,7 +18,6 @@
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-select/gr-select';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-edit-preferences_html';
@@ -37,9 +36,7 @@
};
}
@customElement('gr-edit-preferences')
-export class GrEditPreferences extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrEditPreferences extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.js b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.js
deleted file mode 100644
index cffd1ae..0000000
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.js
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * @license
- * Copyright (C) 2018 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.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-edit-preferences.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-
-const basicFixture = fixtureFromElement('gr-edit-preferences');
-
-suite('gr-edit-preferences tests', () => {
- let element;
-
- let editPreferences;
-
- function valueOf(title, fieldsetid) {
- const sections = element.$[fieldsetid].querySelectorAll('section');
- let titleEl;
- for (let i = 0; i < sections.length; i++) {
- titleEl = sections[i].querySelector('.title');
- if (titleEl.textContent.trim() === title) {
- return sections[i].querySelector('.value');
- }
- }
- }
-
- setup(() => {
- editPreferences = {
- auto_close_brackets: false,
- cursor_blink_rate: 0,
- hide_line_numbers: false,
- hide_top_menu: false,
- indent_unit: 2,
- indent_with_tabs: false,
- key_map_type: 'DEFAULT',
- line_length: 100,
- line_wrapping: false,
- match_brackets: true,
- show_base: false,
- show_tabs: true,
- show_whitespace_errors: true,
- syntax_highlighting: true,
- tab_size: 8,
- theme: 'DEFAULT',
- };
-
- stubRestApi('getEditPreferences').returns(Promise.resolve(editPreferences));
-
- element = basicFixture.instantiate();
-
- return element.loadData();
- });
-
- test('renders', () => {
- // Rendered with the expected preferences selected.
- assert.equal(valueOf('Tab width', 'editPreferences')
- .firstElementChild.bindValue, editPreferences.tab_size);
- assert.equal(valueOf('Columns', 'editPreferences')
- .firstElementChild.bindValue, editPreferences.line_length);
- assert.equal(valueOf('Indent unit', 'editPreferences')
- .firstElementChild.bindValue, editPreferences.indent_unit);
- assert.equal(valueOf('Syntax highlighting', 'editPreferences')
- .firstElementChild.checked, editPreferences.syntax_highlighting);
- assert.equal(valueOf('Show tabs', 'editPreferences')
- .firstElementChild.checked, editPreferences.show_tabs);
- assert.equal(valueOf('Match brackets', 'editPreferences')
- .firstElementChild.checked, editPreferences.match_brackets);
- assert.equal(valueOf('Line wrapping', 'editPreferences')
- .firstElementChild.checked, editPreferences.line_wrapping);
- assert.equal(valueOf('Indent with tabs', 'editPreferences')
- .firstElementChild.checked, editPreferences.indent_with_tabs);
- assert.equal(valueOf('Auto close brackets', 'editPreferences')
- .firstElementChild.checked, editPreferences.auto_close_brackets);
-
- assert.isFalse(element.hasUnsavedChanges);
- });
-
- test('save changes', () => {
- stubRestApi('saveEditPreferences')
- .returns(Promise.resolve());
- const showTabsCheckbox = valueOf('Show tabs', 'editPreferences')
- .firstElementChild;
- showTabsCheckbox.checked = false;
- element._handleEditShowTabsChanged();
-
- assert.isTrue(element.hasUnsavedChanges);
-
- // Save the change.
- return element.save().then(() => {
- assert.isFalse(element.hasUnsavedChanges);
- });
- });
-});
-
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.ts b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.ts
new file mode 100644
index 0000000..bfdcaa0
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.ts
@@ -0,0 +1,125 @@
+/**
+ * @license
+ * Copyright (C) 2018 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.
+ */
+
+import '../../../test/common-test-setup-karma';
+import './gr-edit-preferences';
+import {stubRestApi} from '../../../test/test-utils';
+import {GrEditPreferences} from './gr-edit-preferences';
+import {EditPreferencesInfo} from '../../../types/common';
+import {IronInputElement} from '@polymer/iron-input';
+
+const basicFixture = fixtureFromElement('gr-edit-preferences');
+
+suite('gr-edit-preferences tests', () => {
+ let element: GrEditPreferences;
+
+ let editPreferences: EditPreferencesInfo;
+
+ function valueOf(title: string, id: string): Element {
+ const sections = element.root?.querySelectorAll(`#${id} section`) ?? [];
+ let titleEl;
+ for (let i = 0; i < sections.length; i++) {
+ titleEl = sections[i].querySelector('.title');
+ if (titleEl?.textContent?.trim() === title) {
+ const el = sections[i].querySelector('.value');
+ if (el) return el;
+ }
+ }
+ assert.fail(`element with title ${title} not found`);
+ }
+
+ setup(async () => {
+ editPreferences = {
+ auto_close_brackets: false,
+ cursor_blink_rate: 0,
+ hide_line_numbers: false,
+ hide_top_menu: false,
+ indent_unit: 2,
+ indent_with_tabs: false,
+ key_map_type: 'DEFAULT',
+ line_length: 100,
+ line_wrapping: false,
+ match_brackets: true,
+ show_base: false,
+ show_tabs: true,
+ show_whitespace_errors: true,
+ syntax_highlighting: true,
+ tab_size: 8,
+ theme: 'DEFAULT',
+ };
+
+ stubRestApi('getEditPreferences').returns(Promise.resolve(editPreferences));
+
+ element = basicFixture.instantiate();
+
+ await element.loadData();
+ await flush();
+ });
+
+ test('renders', () => {
+ // Rendered with the expected preferences selected.
+ const tabWidthInput = valueOf('Tab width', 'editPreferences')
+ .firstElementChild as IronInputElement;
+ assert.equal(tabWidthInput.bindValue, `${editPreferences.tab_size}`);
+
+ const columnsInput = valueOf('Columns', 'editPreferences')
+ .firstElementChild as IronInputElement;
+ assert.equal(columnsInput.bindValue, `${editPreferences.line_length}`);
+
+ const indentInput = valueOf('Indent unit', 'editPreferences')
+ .firstElementChild as IronInputElement;
+ assert.equal(indentInput.bindValue, `${editPreferences.indent_unit}`);
+
+ const syntaxInput = valueOf('Syntax highlighting', 'editPreferences')
+ .firstElementChild as HTMLInputElement;
+ assert.equal(syntaxInput.checked, editPreferences.syntax_highlighting);
+
+ const tabsInput = valueOf('Show tabs', 'editPreferences')
+ .firstElementChild as HTMLInputElement;
+ assert.equal(tabsInput.checked, editPreferences.show_tabs);
+
+ const bracketsInput = valueOf('Match brackets', 'editPreferences')
+ .firstElementChild as HTMLInputElement;
+ assert.equal(bracketsInput.checked, editPreferences.match_brackets);
+
+ const wrappingInput = valueOf('Line wrapping', 'editPreferences')
+ .firstElementChild as HTMLInputElement;
+ assert.equal(wrappingInput.checked, editPreferences.line_wrapping);
+
+ const indentTabsInput = valueOf('Indent with tabs', 'editPreferences')
+ .firstElementChild as HTMLInputElement;
+ assert.equal(indentTabsInput.checked, editPreferences.indent_with_tabs);
+
+ const autoCloseInput = valueOf('Auto close brackets', 'editPreferences')
+ .firstElementChild as HTMLInputElement;
+ assert.equal(autoCloseInput.checked, editPreferences.auto_close_brackets);
+
+ assert.isFalse(element.hasUnsavedChanges);
+ });
+
+ test('save changes', async () => {
+ const showTabsCheckbox = valueOf('Show tabs', 'editPreferences')
+ .firstElementChild as HTMLInputElement;
+ showTabsCheckbox.checked = false;
+ element._handleEditShowTabsChanged();
+
+ assert.isTrue(element.hasUnsavedChanges);
+
+ await element.save();
+ assert.isFalse(element.hasUnsavedChanges);
+ });
+});
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.ts b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.ts
index 9960d83..925d9fb 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.ts
@@ -18,7 +18,6 @@
import '../../shared/gr-button/gr-button';
import '../../../styles/shared-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-email-editor_html';
@@ -27,9 +26,7 @@
import {appContext} from '../../../services/app-context';
@customElement('gr-email-editor')
-export class GrEmailEditor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrEmailEditor extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts
index 0fd0ad9..9cce48e 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts
@@ -21,7 +21,6 @@
import '../../shared/gr-overlay/gr-overlay';
import '../../../styles/shared-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-gpg-editor_html';
@@ -46,9 +45,7 @@
}
}
@customElement('gr-gpg-editor')
-export class GrGpgEditor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrGpgEditor extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts
index c69bebd..c24eaef 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts
@@ -16,7 +16,6 @@
*/
import '../../../styles/shared-styles';
import '../../../styles/gr-form-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-group-list_html';
@@ -31,9 +30,7 @@
}
}
@customElement('gr-group-list')
-export class GrGroupList extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrGroupList extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts
index 9bb4b00..0842b3a 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts
@@ -19,7 +19,6 @@
import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
import '../../shared/gr-overlay/gr-overlay';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-http-password_html';
@@ -40,9 +39,7 @@
}
@customElement('gr-http-password')
-export class GrHttpPassword extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrHttpPassword extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -59,8 +56,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.loadData();
}
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.ts b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.ts
index 837332a..221df1f 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.ts
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.ts
@@ -19,7 +19,6 @@
import '../../admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-overlay/gr-overlay';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-identities_html';
@@ -39,9 +38,7 @@
}
@customElement('gr-identities')
-export class GrIdentities extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrIdentities extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts
index 805c9ca..34acaf9 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts
@@ -20,7 +20,6 @@
import '../../../styles/shared-styles';
import '../../../styles/gr-form-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-menu-editor_html';
@@ -28,9 +27,7 @@
import {TopMenuItemInfo} from '../../../types/common';
@customElement('gr-menu-editor')
-export class GrMenuEditor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrMenuEditor extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
index 85e692d..02ba85d 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
@@ -18,7 +18,6 @@
import '../../../styles/gr-form-styles';
import '../../shared/gr-button/gr-button';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-registration-dialog_html';
@@ -43,9 +42,7 @@
}
@customElement('gr-registration-dialog')
-export class GrRegistrationDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrRegistrationDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts
index 5d80a84d..887c441 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-settings-item_html';
@@ -27,9 +26,7 @@
}
@customElement('gr-settings-item')
-class GrSettingsItem extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrSettingsItem extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts
index e288d20..2f89b52 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../../styles/gr-page-nav-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-settings-menu-item_html';
@@ -28,9 +27,7 @@
}
@customElement('gr-settings-menu-item')
-class GrSettingsMenuItem extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrSettingsMenuItem extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 809139d..35448c4 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -42,7 +42,6 @@
import '../gr-menu-editor/gr-menu-editor';
import '../gr-ssh-editor/gr-ssh-editor';
import '../gr-watched-projects-editor/gr-watched-projects-editor';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-settings-view_html';
@@ -120,7 +119,7 @@
@customElement('gr-settings-view')
export class GrSettingsView extends ChangeTableMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -212,8 +211,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
// Polymer 2: anchor tag won't work on shadow DOM
// we need to manually calling scrollIntoView when hash changed
this.listen(window, 'location-change', '_handleLocationChange');
@@ -300,9 +299,9 @@
});
}
- detached() {
- super.detached();
+ disconnectedCallback() {
this.unlisten(window, 'location-change', '_handleLocationChange');
+ super.disconnectedCallback();
}
_handleLocationChange() {
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.js
index 98abb3fb..79789bb 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.js
@@ -329,7 +329,7 @@
test('emails are loaded without emailToken', () => {
sinon.stub(element.$.emailEditor, 'loadData');
element.params = {};
- element.attached();
+ element.connectedCallback();
assert.isTrue(element.$.emailEditor.loadData.calledOnce);
});
@@ -465,7 +465,7 @@
confirmEmailStub = stubRestApi('confirmEmail').returns(
new Promise(resolve => { resolveConfirm = resolve; }));
element.params = {view: GerritView.SETTINGS, emailToken: 'foo'};
- element.attached();
+ element.connectedCallback();
});
test('it is used to confirm email via rest API', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts
index b30b2cb..b2373ec 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts
@@ -21,7 +21,6 @@
import '../../shared/gr-overlay/gr-overlay';
import '../../../styles/shared-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-ssh-editor_html';
@@ -46,9 +45,7 @@
}
}
@customElement('gr-ssh-editor')
-export class GrSshEditor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrSshEditor extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.ts b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.ts
index a45e160..fe37795 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.ts
@@ -20,7 +20,6 @@
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-watched-projects-editor_html';
@@ -49,8 +48,8 @@
};
}
@customElement('gr-watched-projects-editor')
-export class GrWatchedProjectsEditor extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
+export class GrWatchedProjectsEditor extends LegacyElementMixin(
+ PolymerElement
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
index 8ff7a3a..e344a1b 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
@@ -18,7 +18,6 @@
import '../gr-button/gr-button';
import '../gr-icons/gr-icons';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-account-chip_html';
@@ -27,9 +26,7 @@
import {appContext} from '../../../services/app-context';
@customElement('gr-account-chip')
-export class GrAccountChip extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAccountChip extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
index 2562a1a..7c8c479 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
@@ -16,7 +16,6 @@
*/
import '../../../styles/shared-styles';
import '../gr-autocomplete/gr-autocomplete';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-account-entry_html';
@@ -33,9 +32,7 @@
* and/or group with autocomplete support.
*/
@customElement('gr-account-entry')
-export class GrAccountEntry extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAccountEntry extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
index 3f8fe6b..4702958 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
@@ -18,7 +18,6 @@
import '../../../styles/shared-styles';
import '../gr-avatar/gr-avatar';
import '../gr-hovercard-account/gr-hovercard-account';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-account-label_html';
@@ -34,9 +33,7 @@
import {ShowAlertEventDetail} from '../../../types/events';
@customElement('gr-account-label')
-export class GrAccountLabel extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAccountLabel extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.js b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.js
index e25bdea..f37aa01 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.js
@@ -29,7 +29,6 @@
}
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
element = basicFixture.instantiate();
element._config = {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
index be54f4e..5d72ca5 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
@@ -17,7 +17,6 @@
import '../gr-account-label/gr-account-label';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-account-link_html';
@@ -26,9 +25,7 @@
import {AccountInfo, ChangeInfo} from '../../../types/common';
@customElement('gr-account-link')
-class GrAccountLink extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrAccountLink extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.js b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.js
index af485c6..34fef2f 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.js
@@ -18,7 +18,6 @@
import '../../../test/common-test-setup-karma.js';
import './gr-account-link.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-account-link');
@@ -26,7 +25,6 @@
let element;
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
element = basicFixture.instantiate();
});
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
index 5cc1240..3b15233 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
@@ -17,7 +17,6 @@
import '../gr-account-chip/gr-account-chip';
import '../gr-account-entry/gr-account-entry';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-account-list_html';
@@ -115,9 +114,7 @@
}
@customElement('gr-account-list')
-export class GrAccountList extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAccountList extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.js b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.js
index 6430290..20e8672 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.js
@@ -17,7 +17,6 @@
import '../../../test/common-test-setup-karma.js';
import './gr-account-list.js';
-import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-account-list');
@@ -61,7 +60,6 @@
existingAccount1 = makeAccount();
existingAccount2 = makeAccount();
- stubRestApi('getConfig').returns(Promise.resolve({}));
element = basicFixture.instantiate();
element.accounts = [existingAccount1, existingAccount2];
suggestionsProvider = new MockSuggestionsProvider();
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
index a0fddcd..135abfc 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
@@ -16,7 +16,6 @@
*/
import '../gr-button/gr-button';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-alert_html';
@@ -31,9 +30,7 @@
}
@customElement('gr-alert')
-export class GrAlert extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAlert extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -75,21 +72,21 @@
_actionCallback?: () => void;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._boundTransitionEndHandler = () => this._handleTransitionEnd();
this.addEventListener('transitionend', this._boundTransitionEndHandler);
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
if (this._boundTransitionEndHandler) {
this.removeEventListener(
'transitionend',
this._boundTransitionEndHandler
);
}
+ super.disconnectedCallback();
}
show(text: string, actionText?: string, actionCallback?: () => void) {
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_html.ts b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_html.ts
index b66a1dd..bc517a8 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_html.ts
@@ -27,7 +27,7 @@
bottom: 1.25rem;
border-radius: var(--border-radius);
box-shadow: var(--elevation-level-2);
- color: var(--view-background-color);
+ color: var(--tooltip-text-color);
left: 1.25rem;
position: fixed;
transform: translateY(5rem);
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
index aff6d50..3e85ed7 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
@@ -18,7 +18,6 @@
import '../gr-cursor-manager/gr-cursor-manager';
import '../../../styles/shared-styles';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-autocomplete-dropdown_html';
@@ -55,9 +54,7 @@
*/
@customElement('gr-autocomplete-dropdown')
export class GrAutocompleteDropdown extends IronFitMixin(
- KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
- ),
+ KeyboardShortcutMixin(LegacyElementMixin(PolymerElement)),
IronFitBehavior as IronFitBehavior
) {
static get template() {
@@ -104,6 +101,12 @@
};
}
+ /** @override */
+ disconnectedCallback() {
+ this.$.cursor.unsetCursor();
+ super.disconnectedCallback();
+ }
+
close() {
this.isHidden = true;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_html.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_html.ts
index d3d2481..7e00e47 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_html.ts
@@ -93,7 +93,7 @@
</div>
<gr-cursor-manager
id="cursor"
- index="{{index}}"
+ index="[[index]]"
cursor-target-class="selected"
scroll-mode="never"
focus-on-move=""
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
index f4eb053..f34c3ad 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -20,7 +20,6 @@
import '../gr-icons/gr-icons';
import '../../../styles/shared-styles';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-autocomplete_html';
@@ -72,7 +71,7 @@
@customElement('gr-autocomplete')
export class GrAutocomplete extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -210,16 +209,16 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.listen(document.body, 'click', '_handleBodyClick');
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
this.unlisten(document.body, 'click', '_handleBodyClick');
this.cancelDebouncer(DEBOUNCER_UPDATE_SUGGESTIONS);
+ super.disconnectedCallback();
}
get focusStart() {
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts
index 9d7e19b..b628125 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-avatar_html';
@@ -26,9 +25,7 @@
import {appContext} from '../../../services/app-context';
@customElement('gr-avatar')
-export class GrAvatar extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrAvatar extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -45,8 +42,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
Promise.all([
this._getConfig(),
getPluginLoader().awaitPluginsLoaded(),
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
index 60b891e..72b2744 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
@@ -16,7 +16,6 @@
*/
import '@polymer/paper-button/paper-button';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {customElement, property, computed, observe} from '@polymer/decorators';
@@ -36,7 +35,7 @@
@customElement('gr-button')
export class GrButton extends LegacyElementMixin(
- KeyboardShortcutMixin(TooltipMixin(GestureEventListeners(PolymerElement)))
+ KeyboardShortcutMixin(TooltipMixin(PolymerElement))
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.js b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts
similarity index 72%
rename from polygerrit-ui/app/elements/shared/gr-button/gr-button_test.js
rename to polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts
index 242cb28..86ae2d1 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts
@@ -15,18 +15,21 @@
* limitations under the License.
*/
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
import '../../../test/common-test-setup-karma.js';
-import './gr-button.js';
import {addListener} from '@polymer/polymer/lib/utils/gestures.js';
import {appContext} from '../../../services/app-context.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
+import {GrButton} from './gr-button.js';
+import {queryAndAssert} from '../../../test/test-utils.js';
+import {PaperButtonElement} from '@polymer/paper-button';
const basicFixture = fixtureFromElement('gr-button');
const nestedFixture = fixtureFromTemplate(html`
-<div id="test">
- <gr-button class="testBtn"></gr-button>
-</div>
+ <div id="test">
+ <gr-button class="testBtn"></gr-button>
+ </div>
`);
const tabindexFixture = fixtureFromTemplate(html`
@@ -34,11 +37,11 @@
`);
suite('gr-button tests', () => {
- let element;
+ let element: GrButton;
- const addSpyOn = function(eventName) {
+ const addSpyOn = function (eventName: string) {
const spy = sinon.spy();
- if (eventName == 'tap') {
+ if (eventName === 'tap') {
addListener(element, eventName, spy);
} else {
element.addEventListener(eventName, spy);
@@ -51,7 +54,10 @@
});
test('disabled is set by disabled', () => {
- const paperBtn = element.shadowRoot.querySelector('paper-button');
+ const paperBtn = queryAndAssert<PaperButtonElement>(
+ element,
+ 'paper-button'
+ );
assert.isFalse(paperBtn.disabled);
element.disabled = true;
assert.isTrue(paperBtn.disabled);
@@ -60,17 +66,21 @@
});
test('loading set from listener', () => {
- let resolve;
+ let resolve: Function;
element.addEventListener('click', e => {
- e.target.loading = true;
- resolve = () => e.target.loading = false;
+ const target = e.target as HTMLElement;
+ target.setAttribute('loading', 'true');
+ resolve = () => target.removeAttribute('loading');
});
- const paperBtn = element.shadowRoot.querySelector('paper-button');
+ const paperBtn = queryAndAssert<PaperButtonElement>(
+ element,
+ 'paper-button'
+ );
assert.isFalse(paperBtn.disabled);
MockInteractions.tap(element);
assert.isTrue(paperBtn.disabled);
assert.isTrue(element.hasAttribute('loading'));
- resolve();
+ resolve!();
flush();
assert.isFalse(paperBtn.disabled);
assert.isFalse(element.hasAttribute('loading'));
@@ -92,13 +102,13 @@
});
test('tabindex should be preserved', () => {
- element = tabindexFixture.instantiate();
- element.disabled = false;
- assert.equal(element.getAttribute('tabindex'), '3');
- element.disabled = true;
- assert.equal(element.getAttribute('tabindex'), '-1');
- element.disabled = false;
- assert.equal(element.getAttribute('tabindex'), '3');
+ const tabIndexElement = tabindexFixture.instantiate() as GrButton;
+ tabIndexElement.disabled = false;
+ assert.equal(tabIndexElement.getAttribute('tabindex'), '3');
+ tabIndexElement.disabled = true;
+ assert.equal(tabIndexElement.getAttribute('tabindex'), '-1');
+ tabIndexElement.disabled = false;
+ assert.equal(tabIndexElement.getAttribute('tabindex'), '3');
});
// 'tap' event is tested so we don't loose backward compatibility with older
@@ -123,14 +133,14 @@
// Keycodes: 32 for Space, 13 for Enter.
for (const key of [32, 13]) {
- test('dispatches click event on keycode ' + key, () => {
+ test(`dispatches click event on keycode ${key}`, () => {
const tapSpy = sinon.spy();
element.addEventListener('click', tapSpy);
MockInteractions.pressAndReleaseKeyOn(element, key);
assert.isTrue(tapSpy.calledOnce);
});
- test('dispatches no click event with modifier on keycode ' + key, () => {
+ test(`dispatches no click event with modifier on keycode ${key}`, () => {
const tapSpy = sinon.spy();
element.addEventListener('click', tapSpy);
MockInteractions.pressAndReleaseKeyOn(element, key, 'shift');
@@ -156,7 +166,7 @@
// Keycodes: 32 for Space, 13 for Enter.
for (const key of [32, 13]) {
- test('stops click event on keycode ' + key, () => {
+ test(`stops click event on keycode ${key}`, () => {
const tapSpy = sinon.spy();
element.addEventListener('click', tapSpy);
MockInteractions.pressAndReleaseKeyOn(element, key);
@@ -166,10 +176,9 @@
});
suite('reporting', () => {
- let reportStub;
+ let reportStub: sinon.SinonStub;
setup(() => {
- reportStub = sinon.stub(appContext.reportingService,
- 'reportInteraction');
+ reportStub = sinon.stub(appContext.reportingService, 'reportInteraction');
reportStub.reset();
});
@@ -178,20 +187,20 @@
assert.isTrue(reportStub.calledOnce);
assert.equal(reportStub.lastCall.args[0], 'button-click');
assert.deepEqual(reportStub.lastCall.args[1], {
- path: `html>body>test-fixture#${basicFixture.fixtureId}>gr-button`,
+ path: `html>body>test-fixture#${element.parentElement!.id}>gr-button`,
});
});
test('report event after click on nested', () => {
- element = nestedFixture.instantiate();
- MockInteractions.click(element.querySelector('gr-button'));
+ const nestedElement = nestedFixture.instantiate() as HTMLDivElement;
+ MockInteractions.click(queryAndAssert(nestedElement, 'gr-button'));
assert.isTrue(reportStub.calledOnce);
assert.equal(reportStub.lastCall.args[0], 'button-click');
assert.deepEqual(reportStub.lastCall.args[1], {
- path: `html>body>test-fixture#${nestedFixture.fixtureId}` +
- `>div#test>gr-button.testBtn`,
+ path:
+ `html>body>test-fixture#${nestedElement.parentElement!.id}` +
+ '>div#test>gr-button.testBtn',
});
});
});
});
-
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts
index 1ecaf7f..bafc00f 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts
@@ -16,13 +16,13 @@
*/
import '../gr-icons/gr-icons';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-star_html';
import {customElement, property} from '@polymer/decorators';
import {ChangeInfo} from '../../../types/common';
import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+import {fireAlert} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
@@ -37,7 +37,7 @@
@customElement('gr-change-star')
export class GrChangeStar extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -78,6 +78,7 @@
change: this.change,
starred: newVal,
};
+ if (newVal) fireAlert(this, 'Starring change...');
this.dispatchEvent(
new CustomEvent('toggle-star', {
bubbles: true,
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
index 7cf9bb1..c84344e 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
@@ -16,7 +16,6 @@
*/
import '../gr-tooltip-content/gr-tooltip-content';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-status_html';
@@ -46,9 +45,7 @@
/** @extends PolymerElement */
@customElement('gr-change-status')
-class GrChangeStatus extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrChangeStatus extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index a5b7df7..06144dc 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -19,7 +19,6 @@
import '../gr-comment/gr-comment';
import '../../diff/gr-diff/gr-diff';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-comment-thread_html';
@@ -61,6 +60,8 @@
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
import {RenderPreferences} from '../../../api/diff';
import {check, assertIsDefined} from '../../../utils/common-util';
+import {waitForEventOnce} from '../../../utils/event-util';
+import {GrSyntaxLayer} from '../../diff/gr-syntax-layer/gr-syntax-layer';
const UNRESOLVED_EXPAND_COUNT = 5;
const NEWLINE_PATTERN = /\n/g;
@@ -74,7 +75,7 @@
@customElement('gr-comment-thread')
export class GrCommentThread extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
// KeyboardShortcutMixin Not used in this element rather other elements tests
@@ -182,6 +183,7 @@
_renderPrefs: RenderPreferences = {
hide_left_side: true,
disable_context_control_buttons: true,
+ show_file_comment_button: false,
};
@property({type: Boolean, reflectToAttribute: true})
@@ -211,6 +213,8 @@
readonly storage = new GrStorage();
+ private readonly syntaxLayer = new GrSyntaxLayer();
+
private isCommentContextExperimentEnabled = this.flagsService.isEnabled(
KnownExperimentId.COMMENT_CONTEXT
);
@@ -226,8 +230,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._getLoggedIn().then(loggedIn => {
this._showActions = loggedIn;
});
@@ -235,7 +239,6 @@
if (!prefs) return;
this._prefs = {
...prefs,
- show_file_comment_button: false,
// override explicitly so that diff doesn't take too much width
// compared to the context
line_wrapping: false,
@@ -248,7 +251,16 @@
get _diff() {
if (this.comments === undefined || this.path === undefined) return;
if (!this.comments[0]?.context_lines?.length) return;
- return computeDiffFromContext(this.comments[0].context_lines, this.path);
+ waitForEventOnce(this, 'render').then(() => {
+ this.syntaxLayer.process();
+ });
+ const diff = computeDiffFromContext(
+ this.comments[0].context_lines,
+ this.path,
+ this.comments[0].source_content_type
+ );
+ this.syntaxLayer.init(diff);
+ return diff;
}
_shouldShowCommentContext(diff?: DiffInfo) {
@@ -333,6 +345,11 @@
return undefined;
}
+ _getLayers(diff?: DiffInfo) {
+ if (!diff) return [];
+ return [this.syntaxLayer];
+ }
+
_getUrlForViewDiff(comments: UIComment[]) {
assertIsDefined(this.changeNum, 'changeNum');
assertIsDefined(this.projectName, 'projectName');
@@ -504,7 +521,7 @@
if (!isEditing) {
// Allow the reply to render in the dom-repeat.
- this.async(() => {
+ setTimeout(() => {
const commentEl = this._commentElWithDraftID(reply.__draftID);
if (commentEl) commentEl.save();
}, 1);
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
index 55408c0..60df799 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
@@ -86,15 +86,21 @@
.fileName {
padding: var(--spacing-m) var(--spacing-s) var(--spacing-m);
}
+ @media only screen and (max-width: 1200px) {
+ .diff-container {
+ display: none;
+ }
+ }
.diff-container {
margin-left: var(--spacing-l);
border: 1px solid var(--border-color);
}
- .view-diff-container {
- text-align: end;
- }
.view-diff-button {
- margin: var(--spacing-m);
+ margin: var(--spacing-s) var(--spacing-m);
+ }
+ .view-diff-container {
+ border-top: 1px solid var(--border-color);
+ background-color: var(--background-color-primary);
}
</style>
@@ -193,10 +199,12 @@
id="diff"
change-num="[[changeNum]]"
diff="[[_diff]]"
+ layers="[[_getLayers(_diff)]]"
path="[[path]]"
prefs="[[_prefs]]"
render-prefs="[[_renderPrefs]]"
highlight-range="[[getHighlightRange(comments)]]"
+ on-render="_handleDiffRender"
>
</gr-diff>
<div class="view-diff-container">
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index bf376f6..2807730 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -30,7 +30,6 @@
import '../gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog';
import '../gr-account-label/gr-account-label';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-comment_html';
@@ -48,6 +47,7 @@
ConfigInfo,
PatchSetNum,
RepoName,
+ BasePatchSetNum,
} from '../../../types/common';
import {GrButton} from '../gr-button/gr-button';
import {GrConfirmDeleteCommentDialog} from '../gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog';
@@ -109,7 +109,7 @@
@customElement('gr-comment')
export class GrComment extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -282,8 +282,8 @@
reporting = appContext.reportingService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.restApiService.getAccount().then(account => {
this._selfAccount = account;
});
@@ -298,14 +298,14 @@
}
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
this.cancelDebouncer(DEBOUNCER_FIRE_UPDATE);
this.cancelDebouncer(DEBOUNCER_STORE);
this.cancelDebouncer(DEBOUNCER_DRAFT_TOAST);
if (this.textarea) {
this.textarea.closeDropdown();
}
+ super.disconnectedCallback();
}
_getAuthor(comment: UIComment) {
@@ -587,7 +587,7 @@
this._fireUpdate();
}
if (editing) {
- this.async(() => {
+ setTimeout(() => {
flush();
this.textarea && this.textarea.putCursorAtEnd();
}, 1);
@@ -930,7 +930,7 @@
_getPatchNum(): PatchSetNum {
const patchNum = this.isOnParent()
- ? ('PARENT' as PatchSetNum)
+ ? ('PARENT' as BasePatchSetNum)
: this.patchNum;
if (patchNum === undefined) throw new Error('patchNum undefined');
return patchNum;
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js
index 6c925b0..f28a7fe 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js
@@ -442,7 +442,6 @@
setup(() => {
stubRestApi('getAccount').returns(Promise.resolve(null));
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('saveDiffDraft').returns(Promise.resolve({
ok: true,
text() {
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
index a636a07..6a350b0 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
@@ -16,7 +16,6 @@
*/
import '../gr-dialog/gr-dialog';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-confirm-delete-comment-dialog_html';
@@ -35,8 +34,8 @@
}
@customElement('gr-confirm-delete-comment-dialog')
-export class GrConfirmDeleteCommentDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
+export class GrConfirmDeleteCommentDialog extends LegacyElementMixin(
+ PolymerElement
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
index 47dacc3..b47925b 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
@@ -19,7 +19,6 @@
import '../gr-button/gr-button';
import '../gr-icons/gr-icons';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-copy-clipboard_html';
@@ -41,9 +40,7 @@
/** @extends PolymerElement */
@customElement('gr-copy-clipboard')
-export class GrCopyClipboard extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrCopyClipboard extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -87,7 +84,7 @@
this.$.input.style.display = 'none';
}
this.$.icon.icon = 'gr-icons:check';
- this.async(
+ setTimeout(
() => (this.$.icon.icon = 'gr-icons:content-copy'),
COPY_TIMEOUT_MS
);
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.ts b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.ts
index 119ed20..411dac4 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.ts
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-cursor-manager_html';
@@ -62,9 +61,7 @@
}
@customElement('gr-cursor-manager')
-export class GrCursorManager extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrCursorManager extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -81,7 +78,7 @@
/**
* The index of the current target (if any). -1 otherwise.
*/
- @property({type: Number, notify: true})
+ @property({type: Number})
index = -1;
/**
@@ -115,12 +112,6 @@
return this.stops.filter(isTargetable);
}
- /** @override */
- detached() {
- super.detached();
- this.unsetCursor();
- }
-
/**
* Move the cursor forward. Clipped to the ends of the stop list.
*
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
index 5bb4f4c..f53d87f 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-date-formatter_html';
@@ -79,7 +78,7 @@
@customElement('gr-date-formatter')
export class GrDateFormatter extends TooltipMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -134,8 +133,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._loadPreferences();
}
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
index fa6403a..62a2b82 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
@@ -16,7 +16,6 @@
*/
import '../gr-button/gr-button';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-dialog_html';
@@ -36,9 +35,7 @@
}
@customElement('gr-dialog')
-export class GrDialog extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDialog extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.js b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts
similarity index 74%
rename from polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.js
rename to polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts
index 1238168..e7b7130 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts
@@ -15,14 +15,15 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-dialog.js';
-import {isHidden} from '../../../test/test-utils.js';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
+import '../../../test/common-test-setup-karma';
+import {GrDialog} from './gr-dialog';
+import {isHidden, queryAndAssert} from '../../../test/test-utils';
const basicFixture = fixtureFromElement('gr-dialog');
suite('gr-dialog tests', () => {
- let element;
+ let element: GrDialog;
setup(() => {
element = basicFixture.instantiate();
@@ -34,12 +35,10 @@
element.addEventListener('confirm', confirm);
element.addEventListener('cancel', cancel);
- MockInteractions.tap(
- element.shadowRoot.querySelector('gr-button[primary]'));
+ MockInteractions.tap(queryAndAssert(element, 'gr-button[primary]'));
assert.equal(confirm.callCount, 1);
- MockInteractions.tap(
- element.shadowRoot.querySelector('gr-button:not([primary])'));
+ MockInteractions.tap(queryAndAssert(element, 'gr-button:not([primary])'));
assert.equal(cancel.callCount, 1);
});
@@ -48,7 +47,11 @@
const handleConfirmStub = sinon.stub(element, '_handleConfirm');
const handleKeydownSpy = sinon.spy(element, '_handleKeydown');
MockInteractions.pressAndReleaseKeyOn(
- element.shadowRoot.querySelector('main'), 13, null, 'enter');
+ queryAndAssert(element, 'main'),
+ 13,
+ null,
+ 'enter'
+ );
flush();
assert.isTrue(handleKeydownSpy.called);
@@ -56,7 +59,11 @@
element.confirmOnEnter = true;
MockInteractions.pressAndReleaseKeyOn(
- element.shadowRoot.querySelector('main'), 13, null, 'enter');
+ queryAndAssert(element, 'main'),
+ 13,
+ null,
+ 'enter'
+ );
flush();
assert.isTrue(handleConfirmStub.called);
@@ -81,11 +88,11 @@
});
test('empty cancel label hides cancel btn', () => {
- assert.isFalse(isHidden(element.$.cancel));
+ const cancelButton = queryAndAssert(element, '#cancel');
+ assert.isFalse(isHidden(cancelButton));
element.cancelLabel = '';
flush();
- assert.isTrue(isHidden(element.$.cancel));
+ assert.isTrue(isHidden(cancelButton));
});
});
-
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
index 5810222..5664e22 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
@@ -18,7 +18,6 @@
import '../../../styles/shared-styles';
import '../gr-button/gr-button';
import '../gr-select/gr-select';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-preferences_html';
@@ -40,9 +39,7 @@
}
@customElement('gr-diff-preferences')
-export class GrDiffPreferences extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDiffPreferences extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
index 4c2a417..f00d3bb 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
@@ -17,7 +17,6 @@
import '@polymer/paper-tabs/paper-tabs';
import '../gr-shell-command/gr-shell-command';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-download-commands_html';
@@ -43,9 +42,7 @@
}
@customElement('gr-download-commands')
-export class GrDownloadCommands extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDownloadCommands extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -66,8 +63,8 @@
private readonly restApiService = appContext.restApiService;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this._getLoggedIn().then(loggedIn => {
this._loggedIn = loggedIn;
});
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
index 888f34f..28336cf 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
@@ -22,7 +22,6 @@
import '../gr-date-formatter/gr-date-formatter';
import '../gr-select/gr-select';
import '../gr-file-status-chip/gr-file-status-chip';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-dropdown-list_html';
@@ -64,9 +63,7 @@
export type DropDownValueChangeEvent = CustomEvent<ValueChangeDetail>;
@customElement('gr-dropdown-list')
-export class GrDropdownList extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrDropdownList extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -103,7 +100,7 @@
_handleDropdownClick() {
// async is needed so that that the click event is fired before the
// dropdown closes (This was a bug for touch devices).
- this.async(() => {
+ setTimeout(() => {
this.$.dropdown.close();
}, 1);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.js b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.js
index c2c8d68..909a3bbf 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.js
@@ -17,7 +17,6 @@
import '../../../test/common-test-setup-karma.js';
import './gr-dropdown-list.js';
-import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-dropdown-list');
@@ -25,7 +24,6 @@
let element;
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
element = basicFixture.instantiate();
});
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
index ae9bfd7..29c6bd8 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
@@ -20,7 +20,6 @@
import '../gr-tooltip-content/gr-tooltip-content';
import '../../../styles/shared-styles';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-dropdown_html';
@@ -67,7 +66,7 @@
@customElement('gr-dropdown')
export class GrDropdown extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -129,6 +128,12 @@
};
}
+ /** @override */
+ disconnectedCallback() {
+ this.$.cursor.unsetCursor();
+ super.disconnectedCallback();
+ }
+
/**
* Handle the up key.
*/
@@ -220,7 +225,7 @@
_close() {
// async is needed so that that the click event is fired before the
// dropdown closes (This was a bug for touch devices).
- this.async(() => {
+ setTimeout(() => {
this.$.dropdown.close();
}, 1);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.js b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.js
index 02bffe4..fea4a08 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.js
@@ -18,7 +18,6 @@
import '../../../test/common-test-setup-karma.js';
import './gr-dropdown.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-dropdown');
@@ -26,7 +25,6 @@
let element;
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
element = basicFixture.instantiate();
});
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
index c744eab..c849fac 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
@@ -19,7 +19,6 @@
import '../gr-storage/gr-storage';
import '../gr-button/gr-button';
import {GrStorage} from '../gr-storage/gr-storage';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {customElement, property} from '@polymer/decorators';
@@ -40,9 +39,7 @@
const DEBOUNCER_STORE = 'store';
@customElement('gr-editable-content')
-export class GrEditableContent extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrEditableContent extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -120,6 +117,8 @@
private readonly flagsService = appContext.flagsService;
+ private readonly reporting = appContext.reportingService;
+
/** @override */
ready() {
super.ready();
@@ -129,8 +128,9 @@
}
/** @override */
- detached() {
+ disconnectedCallback() {
this.cancelDebouncer(DEBOUNCER_STORE);
+ super.disconnectedCallback();
}
_contentChanged() {
@@ -238,6 +238,10 @@
_toggleCommitCollapsed() {
this._commitCollapsed = !this._commitCollapsed;
+ this.reporting.reportInteraction('toggle show all button', {
+ sectionName: 'Commit message',
+ toState: !this._commitCollapsed ? 'Show all' : 'Show less',
+ });
if (this._commitCollapsed) {
window.scrollTo(0, 0);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_html.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_html.ts
index 0f530bf..68b8121 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_html.ts
@@ -62,7 +62,6 @@
background-color: var(--view-background-color);
display: flex;
justify-content: flex-end;
- margin-bottom: 8px;
border-top-width: 1px;
border-top-style: solid;
border-radius: 0 0 4px 4px;
@@ -88,6 +87,11 @@
.save-button {
margin-right: var(--spacing-xs);
}
+ gr-button {
+ font-family: var(--font-family);
+ line-height: var(--line-height-normal);
+ padding: var(--spacing-xs);
+ }
</style>
<div
class$="viewer new-change-summary-[[_isNewChangeSummaryUiEnabled]]"
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
index 6f0e84a..db70e3c 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
@@ -18,7 +18,7 @@
import '@polymer/paper-input/paper-input';
import '../../../styles/shared-styles';
import '../gr-button/gr-button';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
+import '../../shared/gr-autocomplete/gr-autocomplete';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
@@ -28,6 +28,10 @@
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {PaperInputElementExt} from '../../../types/types';
import {CustomKeyboardEvent} from '../../../types/events';
+import {
+ AutocompleteQuery,
+ GrAutocomplete,
+} from '../gr-autocomplete/gr-autocomplete';
const AWAIT_MAX_ITERS = 10;
const AWAIT_STEP = 5;
@@ -40,14 +44,13 @@
export interface GrEditableLabel {
$: {
- input: PaperInputElementExt;
dropdown: IronDropdownElement;
};
}
@customElement('gr-editable-label')
export class GrEditableLabel extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -92,6 +95,12 @@
@property({type: Boolean})
showAsEditPencil = false;
+ @property({type: Boolean})
+ autocomplete = false;
+
+ @property({type: Object})
+ query?: AutocompleteQuery;
+
/** @override */
ready() {
super.ready();
@@ -120,8 +129,9 @@
if (this.readOnly || this.editing) return;
return this._open().then(() => {
this._nativeInput.focus();
- if (!this.$.input.value) return;
- this._nativeInput.setSelectionRange(0, this.$.input.value.length);
+ const input = this.getInput();
+ if (!input?.value) return;
+ this._nativeInput.setSelectionRange(0, input.value.length);
});
}
@@ -133,7 +143,7 @@
_open() {
this.$.dropdown.open();
- this._inputText = this.value;
+ this._inputText = this.value || '';
this.editing = true;
return new Promise<void>(resolve => {
@@ -148,7 +158,7 @@
_awaitOpen(fn: () => void) {
let iters = 0;
const step = () => {
- this.async(() => {
+ setTimeout(() => {
if (this.$.dropdown.style.display !== 'none') {
fn.call(this);
} else if (iters++ < AWAIT_MAX_ITERS) {
@@ -190,8 +200,9 @@
get _nativeInput(): HTMLInputElement {
// In Polymer 2 inputElement isn't nativeInput anymore
- return (this.$.input.$.nativeInput ||
- this.$.input.inputElement) as HTMLInputElement;
+ return (this.getInput()?.$.nativeInput ||
+ this.getInput()?.inputElement ||
+ this.getGrAutocomplete()) as HTMLInputElement;
}
_handleEnter(e: CustomKeyboardEvent) {
@@ -212,6 +223,10 @@
}
}
+ _handleCommit() {
+ this._save();
+ }
+
_computeLabelClass(readOnly?: boolean, value?: string, placeholder?: string) {
const classes = [];
if (!readOnly) {
@@ -226,4 +241,12 @@
_updateTitle(value?: string) {
this.setAttribute('title', this._computeLabel(value, this.placeholder));
}
+
+ getInput(): PaperInputElementExt | null {
+ return this.shadowRoot!.querySelector<PaperInputElementExt>('#input');
+ }
+
+ getGrAutocomplete(): GrAutocomplete | null {
+ return this.shadowRoot!.querySelector<GrAutocomplete>('#autocomplete');
+ }
}
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_html.ts b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_html.ts
index ad4fead..d7b6df8 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_html.ts
@@ -105,12 +105,24 @@
>
<div class="dropdown-content" slot="dropdown-content">
<div class="inputContainer">
- <paper-input
- id="input"
- label="[[labelText]]"
- maxlength="[[maxLength]]"
- value="{{_inputText}}"
- ></paper-input>
+ <template is="dom-if" if="[[!autocomplete]]">
+ <paper-input
+ id="input"
+ label="[[labelText]]"
+ maxlength="[[maxLength]]"
+ value="{{_inputText}}"
+ ></paper-input>
+ </template>
+ <template is="dom-if" if="[[autocomplete]]">
+ <gr-autocomplete
+ label="[[labelText]]"
+ id="autocomplete"
+ text="{{_inputText}}"
+ query="[[query]]"
+ on-commit="_handleCommit"
+ >
+ </gr-autocomplete>
+ </template>
<div class="buttons">
<gr-button link="" id="cancelBtn" on-click="_cancel"
>cancel</gr-button
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.js b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.js
index 2bdc570..3e217f0 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.js
@@ -50,7 +50,8 @@
await flush();
// In Polymer 2 inputElement isn't nativeInput anymore
- input = element.$.input.$.nativeInput || element.$.input.inputElement;
+ const paperInput = element.shadowRoot.querySelector('#input');
+ input = paperInput.$.nativeInput || paperInput.inputElement;
});
test('element render', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts
index 9298fa3..0e9c0fa 100644
--- a/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {htmlTemplate} from './gr-file-status-chip_html';
import {customElement, property} from '@polymer/decorators';
@@ -35,9 +34,7 @@
};
@customElement('gr-file-status-chip')
-export class GrFileStatusChip extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrFileStatusChip extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index a6c7b92..82ed264 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -16,7 +16,6 @@
*/
import '../gr-linked-text/gr-linked-text';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {customElement, property} from '@polymer/decorators';
@@ -45,9 +44,7 @@
}
@customElement('gr-formatted-text')
-export class GrFormattedText extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrFormattedText extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts
index ddae8ea..9cbd11c 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts
@@ -20,7 +20,6 @@
import '../gr-avatar/gr-avatar';
import '../gr-button/gr-button';
import {hovercardBehaviorMixin} from '../gr-hovercard/gr-hovercard-behavior';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-hovercard-account_html';
@@ -48,8 +47,8 @@
import {assertIsDefined} from '../../../utils/common-util';
@customElement('gr-hovercard-account')
-export class GrHovercardAccount extends GestureEventListeners(
- hovercardBehaviorMixin(LegacyElementMixin(PolymerElement))
+export class GrHovercardAccount extends hovercardBehaviorMixin(
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -96,8 +95,8 @@
this.reporting = appContext.reportingService;
}
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
this.restApiService.getConfig().then(config => {
this._config = config;
});
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
index b8f0161..74c47d20 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
@@ -119,8 +119,8 @@
private isScheduledToHide?: boolean;
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
if (!this._target) {
this._target = this.target;
}
@@ -141,11 +141,11 @@
this.listen(this, 'mouseleave', 'unlock');
}
- detached() {
- super.detached();
+ disconnectedCallback() {
this.cancelShowDebouncer();
this.cancelHideDebouncer();
this.unlock();
+ super.disconnectedCallback();
}
/** @override */
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.ts b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.ts
index c56bc8f..a5f23ba 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.ts
@@ -16,7 +16,6 @@
*/
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-hovercard_html';
import {hovercardBehaviorMixin} from './gr-hovercard-behavior';
@@ -25,8 +24,8 @@
import {customElement} from '@polymer/decorators';
@customElement('gr-hovercard')
-export class GrHovercard extends GestureEventListeners(
- hovercardBehaviorMixin(LegacyElementMixin(PolymerElement))
+export class GrHovercard extends hovercardBehaviorMixin(
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
index 7745da8..8803557 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
@@ -63,8 +63,6 @@
<g id="hourglass"><path d="M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6z"></path><path d="M0 0h24v24H0V0z" fill="none"></path></g>
<!-- This SVG is a copy from material.io https://material.io/icons/#mode_comment-->
<g id="comment"><path d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18z"></path><path d="M0 0h24v24H0z" fill="none"></path></g>
- <!-- This SVG is a copy from material.io https://material.io/resources/icons/?icon=mode_comment&style=outline-->
- <g id="comment-outline"><path d="M0 0h24v24H0V0z" fill="none"></path><path d="M20 17.17L18.83 16H4V4h16v13.17zM20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4V4c0-1.1-.9-2-2-2z"></path></g>
<!-- This SVG is a copy from material.io https://material.io/icons/#calendar_today-->
<g id="calendar"><path d="M20 3h-1V1h-2v2H7V1H5v2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 18H4V8h16v13z"></path><path d="M0 0h24v24H0z" fill="none"></path></g>
<!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
@@ -130,6 +128,10 @@
<g id="launch"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></g>
<!-- This SVG is a copy from material.io https://material.io/icons/#filter-->
<g id="filter"><path d="M0,0h24 M24,24H0" fill="none"/><path d="M4.25,5.61C6.27,8.2,10,13,10,13v6c0,0.55,0.45,1,1,1h2c0.55,0,1-0.45,1-1v-6c0,0,3.72-4.8,5.74-7.39 C20.25,4.95,19.78,4,18.95,4H5.04C4.21,4,3.74,4.95,4.25,5.61z"/><path d="M0,0h24v24H0V0z" fill="none"/></g>
+ <!-- This SVG is a copy from material.io https://material.io/icons/#arrow_drop_down-->
+ <g id="arrowDropDown"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 10l5 5 5-5z"/></g>
+ <!-- This SVG is a copy from material.io https://material.io/icons/#arrow_drop_up-->
+ <g id="arrowDropUp"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 14l5-5 5 5z"/></g>
<!-- This is just a placeholder, i.e. an empty icon that has the same size as a normal icon. -->
<g id="placeholder"><path d="M0 0h24v24H0z" fill="none"/></g>
</defs>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
index a3d038d..857d079 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
@@ -41,10 +41,12 @@
private readonly reporting = appContext.reportingService;
constructor(private readonly plugin: PluginApi) {
+ this.reporting.trackApi(this.plugin, 'annotation', 'constructor');
plugin.on(EventType.ANNOTATE_DIFF, this);
}
setLayer(annotationCallback: AnnotationCallback) {
+ this.reporting.trackApi(this.plugin, 'annotation', 'setLayer');
if (this.annotationCallback) {
console.warn('Overwriting an existing plugin annotation layer.');
}
@@ -55,6 +57,7 @@
setCoverageProvider(
coverageProvider: CoverageProvider
): GrAnnotationActionsInterface {
+ this.reporting.trackApi(this.plugin, 'annotation', 'setCoverageProvider');
if (this.coverageProvider) {
console.warn('Overwriting an existing coverage provider.');
}
@@ -74,6 +77,7 @@
checkboxLabel: string,
onAttached: (checkboxEl: Element | null) => void
) {
+ this.reporting.trackApi(this.plugin, 'annotation', 'enableToggleCheckbox');
this.plugin.hook('annotation-toggler').onAttached(element => {
if (!element.content) {
this.reporting.error(new Error('plugin endpoint without content.'));
@@ -104,6 +108,7 @@
}
notify(path: string, start: number, end: number, side: Side) {
+ this.reporting.trackApi(this.plugin, 'annotation', 'notify');
for (const annotationLayer of this.annotationLayers) {
// Notify only the annotation layer that is associated with the specified
// path.
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
index a4c6974..15d4680 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
@@ -68,7 +68,10 @@
ActionType = ActionType;
+ private readonly reporting = appContext.reportingService;
+
constructor(public plugin: PluginApi, el?: GrChangeActionsElement) {
+ this.reporting.trackApi(this.plugin, 'actions', 'constructor');
this.setEl(el);
}
@@ -100,6 +103,7 @@
}
addPrimaryActionKey(key: PrimaryActionKey) {
+ this.reporting.trackApi(this.plugin, 'actions', 'addPrimaryActionKey');
const el = this.ensureEl();
if (el.primaryActionKeys.includes(key)) {
return;
@@ -109,63 +113,77 @@
}
removePrimaryActionKey(key: string) {
+ this.reporting.trackApi(this.plugin, 'actions', 'removePrimaryActionKey');
const el = this.ensureEl();
el.primaryActionKeys = el.primaryActionKeys.filter(k => k !== key);
}
hideQuickApproveAction() {
+ this.reporting.trackApi(this.plugin, 'actions', 'hideQuickApproveAction');
this.ensureEl().hideQuickApproveAction();
}
setActionOverflow(type: ActionType, key: string, overflow: boolean) {
+ this.reporting.trackApi(this.plugin, 'actions', 'setActionOverflow');
// TODO(TS): remove return, unclear why it was written
return this.ensureEl().setActionOverflow(type, key, overflow);
}
setActionPriority(type: ActionType, key: string, priority: ActionPriority) {
+ this.reporting.trackApi(this.plugin, 'actions', 'setActionPriority');
// TODO(TS): remove return, unclear why it was written
return this.ensureEl().setActionPriority(type, key, priority);
}
setActionHidden(type: ActionType, key: string, hidden: boolean) {
+ this.reporting.trackApi(this.plugin, 'actions', 'setActionHidden');
// TODO(TS): remove return, unclear why it was written
return this.ensureEl().setActionHidden(type, key, hidden);
}
add(type: ActionType, label: string): string {
+ this.reporting.trackApi(this.plugin, 'actions', 'add');
return this.ensureEl().addActionButton(type, label);
}
remove(key: string) {
+ this.reporting.trackApi(this.plugin, 'actions', 'remove');
// TODO(TS): remove return, unclear why it was written
return this.ensureEl().removeActionButton(key);
}
addTapListener(key: string, handler: EventListenerOrEventListenerObject) {
+ this.reporting.trackApi(this.plugin, 'actions', 'addTapListener');
this.ensureEl().addEventListener(key + '-tap', handler);
}
removeTapListener(key: string, handler: EventListenerOrEventListenerObject) {
+ this.reporting.trackApi(this.plugin, 'actions', 'removeTapListener');
this.ensureEl().removeEventListener(key + '-tap', handler);
}
setLabel(key: string, text: string) {
+ this.reporting.trackApi(this.plugin, 'actions', 'setLabel');
this.ensureEl().setActionButtonProp(key, 'label', text);
}
setTitle(key: string, text: string) {
+ this.reporting.trackApi(this.plugin, 'actions', 'setTitle');
this.ensureEl().setActionButtonProp(key, 'title', text);
}
setEnabled(key: string, enabled: boolean) {
+ this.reporting.trackApi(this.plugin, 'actions', 'setEnabled');
this.ensureEl().setActionButtonProp(key, 'enabled', enabled);
}
setIcon(key: string, icon: string) {
+ this.reporting.trackApi(this.plugin, 'actions', 'setIcon');
this.ensureEl().setActionButtonProp(key, 'icon', icon);
}
getActionDetails(action: string) {
+ this.reporting.trackApi(this.plugin, 'actions', 'getActionDetails');
const el = this.ensureEl();
return (
el.getActionDetails(action) ||
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts
index effebe1..de57794 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts
@@ -25,15 +25,20 @@
ReplyChangedCallback,
ValueChangedDetail,
} from '../../../api/change-reply';
+import {appContext} from '../../../services/app-context';
/**
* GrChangeReplyInterface, provides a set of handy methods on reply dialog.
*/
export class GrChangeReplyInterface implements ChangeReplyPluginApi {
+ private readonly reporting = appContext.reportingService;
+
constructor(
readonly plugin: PluginApi,
readonly sharedApiElement: JsApiService
- ) {}
+ ) {
+ this.reporting.trackApi(this.plugin, 'reply', 'constructor');
+ }
get _el(): GrReplyDialog {
return (this.sharedApiElement.getElement(
@@ -42,18 +47,22 @@
}
getLabelValue(label: string): string {
+ this.reporting.trackApi(this.plugin, 'reply', 'getLabelValue');
return this._el.getLabelValue(label);
}
setLabelValue(label: string, value: string) {
+ this.reporting.trackApi(this.plugin, 'reply', 'setLabelValue');
this._el.setLabelValue(label, value);
}
send(includeComments?: boolean) {
+ this.reporting.trackApi(this.plugin, 'reply', 'send');
this._el.send(includeComments);
}
addReplyTextChangedCallback(handler: ReplyChangedCallback) {
+ this.reporting.trackApi(this.plugin, 'reply', 'addReplyTextChangedCb');
const hookApi = this.plugin.hook('reply-text');
const registeredHandler = (e: Event) => {
const ce = e as CustomEvent<ValueChangedDetail>;
@@ -74,6 +83,7 @@
}
addLabelValuesChangedCallback(handler: LabelsChangedCallback) {
+ this.reporting.trackApi(this.plugin, 'reply', 'addLabelValuesChangedCb');
const hookApi = this.plugin.hook('reply-label-scores');
const registeredHandler = (e: Event) => {
const ce = e as CustomEvent<LabelsChangedDetail>;
@@ -95,6 +105,7 @@
}
showMessage(message: string) {
+ this.reporting.trackApi(this.plugin, 'reply', 'showMessage');
this._el.setPluginMessage(message);
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.js
index 52efc56..d6d4ce5 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.js
@@ -31,7 +31,6 @@
let plugin;
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('getAccount').returns(Promise.resolve(null));
});
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
index 82df2fa..46e91d0 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
@@ -19,6 +19,8 @@
import {PluginApi} from '../../../api/plugin';
import {notUndefined} from '../../../types/types';
import {HookApi} from '../../../api/hook';
+import {appContext} from '../../../services/app-context';
+import {Execution} from '../../../constants/reporting';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Callback = (value: any) => void;
@@ -51,6 +53,8 @@
private readonly _importedUrls = new Set<string>();
+ private readonly reporting = appContext.reportingService;
+
private pluginLoaded = false;
setPluginsReady() {
@@ -181,6 +185,10 @@
}
importUrl(pluginUrl: URL) {
+ this.reporting.reportExecution(Execution.METHOD_USED, {
+ id: 'import-href-endpoints',
+ pluginUrl,
+ });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let timerId: any;
return Promise.race([
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
index db34e5a..550a35f 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
@@ -28,6 +28,7 @@
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
import {hasOwnProperty} from '../../../utils/common-util';
import {ShowAlertEventDetail} from '../../../types/events';
+import {Execution} from '../../../constants/reporting';
enum PluginState {
/** State that indicates the plugin is pending to be loaded. */
@@ -340,7 +341,10 @@
const urlWithAP = this._urlFor(pluginUrl, window.ASSETS_PATH);
const urlWithoutAP = this._urlFor(pluginUrl);
let onerror = undefined;
- this._getReporting().reportExecution('html-plugin', {pluginUrl});
+ this._getReporting().reportExecution(Execution.METHOD_USED, {
+ id: 'html-plugin',
+ pluginUrl,
+ });
if (urlWithAP !== urlWithoutAP) {
onerror = () => this._loadHtmlPlugin(urlWithoutAP, opts.sync);
}
@@ -354,6 +358,10 @@
};
}
+ this._getReporting().reportExecution(Execution.METHOD_USED, {
+ id: 'import-href-loader',
+ url,
+ });
importHref(url, () => {}, onerror, !sync);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
index cd35d4e..150c45a 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
@@ -18,6 +18,7 @@
import {RequestPayload} from '../../../types/common';
import {appContext} from '../../../services/app-context';
import {ErrorCallback, RestPluginApi} from '../../../api/rest';
+import {PluginApi} from '../../../api/plugin';
async function getErrorMessage(response: Response): Promise<string> {
const text = await response.text();
@@ -36,33 +37,44 @@
export class GrPluginRestApi implements RestPluginApi {
private readonly restApi = appContext.restApiService;
- constructor(private readonly prefix = '') {}
+ private readonly reporting = appContext.reportingService;
+
+ constructor(readonly plugin: PluginApi, private readonly prefix = '') {
+ this.reporting.trackApi(this.plugin, 'rest', 'constructor');
+ }
getLoggedIn() {
+ this.reporting.trackApi(this.plugin, 'rest', 'getLoggedIn');
return this.restApi.getLoggedIn();
}
getVersion() {
+ this.reporting.trackApi(this.plugin, 'rest', 'getVersion');
return this.restApi.getVersion();
}
getConfig() {
+ this.reporting.trackApi(this.plugin, 'rest', 'getConfig');
return this.restApi.getConfig();
}
invalidateReposCache() {
+ this.reporting.trackApi(this.plugin, 'rest', 'invalidateReposCache');
this.restApi.invalidateReposCache();
}
getAccount() {
+ this.reporting.trackApi(this.plugin, 'rest', 'getAccount');
return this.restApi.getAccount();
}
getAccountCapabilities(capabilities: string[]) {
+ this.reporting.trackApi(this.plugin, 'rest', 'getAccountCapabilities');
return this.restApi.getAccountCapabilities(capabilities);
}
getRepos(filter: string, reposPerPage: number, offset?: number) {
+ this.reporting.trackApi(this.plugin, 'rest', 'getRepos');
return this.restApi.getRepos(filter, reposPerPage, offset);
}
@@ -100,6 +112,7 @@
errFn?: ErrorCallback,
contentType?: string
): Promise<Response | void> {
+ this.reporting.trackApi(this.plugin, 'rest', 'fetch');
return this.restApi.send(
method,
this.prefix + url,
@@ -119,6 +132,7 @@
errFn?: ErrorCallback,
contentType?: string
) {
+ this.reporting.trackApi(this.plugin, 'rest', 'send');
// Plugins typically don't want Gerrit to show error dialogs for failed
// requests. So we are defining a default errFn here, even if it is not
// explicitly set by the caller.
@@ -167,6 +181,7 @@
}
get(url: string) {
+ this.reporting.trackApi(this.plugin, 'rest', 'get');
return this.send(HttpMethod.GET, url);
}
@@ -176,6 +191,7 @@
errFn?: ErrorCallback,
contentType?: string
) {
+ this.reporting.trackApi(this.plugin, 'rest', 'post');
return this.send(HttpMethod.POST, url, payload, errFn, contentType);
}
@@ -185,10 +201,12 @@
errFn?: ErrorCallback,
contentType?: string
) {
+ this.reporting.trackApi(this.plugin, 'rest', 'put');
return this.send(HttpMethod.PUT, url, payload, errFn, contentType);
}
delete(url: string) {
+ this.reporting.trackApi(this.plugin, 'rest', 'delete');
return this.fetch(HttpMethod.DELETE, url).then(response => {
if (response.status !== 204) {
return response.text().then(text => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
index e7843af..68fb96b 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
@@ -54,6 +54,7 @@
import {ChangeReplyPluginApi} from '../../../api/change-reply';
import {RestPluginApi} from '../../../api/rest';
import {HookApi, RegisterOptions} from '../../../api/hook';
+import {AttributeHelperPluginApi} from '../../../api/attribute-helper';
/**
* Plugin-provided custom components can affect content in extension
@@ -84,6 +85,8 @@
private readonly jsApi = appContext.jsApiService;
+ private readonly report = appContext.reportingService;
+
constructor(url?: string) {
this.domHooks = new GrDomHooksManager(this);
@@ -97,6 +100,7 @@
this._url = new URL(url);
this._name = getPluginNameFromUrl(this._url) ?? 'NULL';
+ this.report.trackApi(this, 'plugin', 'constructor');
}
getPluginName() {
@@ -104,6 +108,7 @@
}
registerStyleModule(endpoint: string, moduleName: string) {
+ this.report.trackApi(this, 'plugin', 'registerStyleModule');
getPluginEndpoints().registerModule(this, {
endpoint,
type: EndpointType.STYLE,
@@ -119,6 +124,7 @@
moduleName?: string,
options?: RegisterOptions
): HookApi {
+ this.report.trackApi(this, 'plugin', 'registerCustomComponent');
return this._registerCustomComponent(endpointName, moduleName, options);
}
@@ -133,6 +139,7 @@
moduleName?: string,
options?: RegisterOptions
): HookApi {
+ this.report.trackApi(this, 'plugin', 'registerDynamicCustomComponent');
const fullEndpointName = `${endpointName}-${this.getPluginName()}`;
return this._registerCustomComponent(
fullEndpointName,
@@ -169,19 +176,23 @@
* element for the first call.
*/
hook(endpointName: string, options?: RegisterOptions) {
+ this.report.trackApi(this, 'plugin', 'hook');
return this.registerCustomComponent(endpointName, undefined, options);
}
getServerInfo() {
+ this.report.trackApi(this, 'plugin', 'getServerInfo');
return appContext.restApiService.getConfig();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(eventName: EventType, callback: (...args: any[]) => any) {
+ this.report.trackApi(this, 'plugin', 'on');
this.jsApi.addEventCallback(eventName, callback);
}
url(path?: string) {
+ this.report.trackApi(this, 'plugin', 'url');
if (!this._url) throw new Error('plugin url not set');
const relPath = '/plugins/' + this._name + (path || '/');
const sameOriginPath = window.location.origin + `${getBaseUrl()}${relPath}`;
@@ -200,6 +211,7 @@
}
screenUrl(screenName?: string) {
+ this.report.trackApi(this, 'plugin', 'screenUrl');
const origin = location.origin;
const base = getBaseUrl();
const tokenPart = screenName ? '/' + screenName : '';
@@ -216,21 +228,25 @@
}
get(url: string, callback?: SendCallback) {
+ this.report.trackApi(this, 'plugin', 'get');
console.warn('.get() is deprecated! Use .restApi().get()');
return this._send(HttpMethod.GET, url, callback);
}
post(url: string, payload: RequestPayload, callback?: SendCallback) {
+ this.report.trackApi(this, 'plugin', 'post');
console.warn('.post() is deprecated! Use .restApi().post()');
return this._send(HttpMethod.POST, url, callback, payload);
}
put(url: string, payload: RequestPayload, callback?: SendCallback) {
+ this.report.trackApi(this, 'plugin', 'put');
console.warn('.put() is deprecated! Use .restApi().put()');
return this._send(HttpMethod.PUT, url, callback, payload);
}
delete(url: string, callback?: SendCallback) {
+ this.report.trackApi(this, 'plugin', 'delete');
console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
return this.restApi()
.delete(this.url(url))
@@ -286,19 +302,19 @@
}
styles(): StylesPluginApi {
- return new GrStylesApi();
+ return new GrStylesApi(this);
}
restApi(prefix?: string): RestPluginApi {
- return new GrPluginRestApi(prefix);
+ return new GrPluginRestApi(this, prefix);
}
- attributeHelper(element: HTMLElement) {
- return new GrAttributeHelper(element);
+ attributeHelper(element: HTMLElement): AttributeHelperPluginApi {
+ return new GrAttributeHelper(this, element);
}
eventHelper(element: HTMLElement): EventHelperPluginApi {
- return new GrEventHelper(element);
+ return new GrEventHelper(this, element);
}
popup(): Promise<PopupPluginApi>;
@@ -314,6 +330,7 @@
}
screen(screenName: string, moduleName?: string) {
+ this.report.trackApi(this, 'plugin', 'screen');
if (moduleName && typeof moduleName !== 'string') {
console.error(
'.screen(pattern, callback) deprecated, use ' +
@@ -328,6 +345,7 @@
}
_getScreenName(screenName: string) {
+ this.report.trackApi(this, 'plugin', '_getScreenName');
return `${this.getPluginName()}-screen-${screenName}`;
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api.ts
index d4b51a8..595fb4f 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api.ts
@@ -18,6 +18,7 @@
import {appContext} from '../../../services/app-context';
import {PluginApi} from '../../../api/plugin';
import {EventDetails, ReportingPluginApi} from '../../../api/reporting';
+import {LifeCycle} from '../../../constants/reporting';
/**
* Defines all methods that will be exported to plugin from reporting service.
@@ -25,9 +26,12 @@
export class GrReportingJsApi implements ReportingPluginApi {
private readonly reporting = appContext.reportingService;
- constructor(private readonly plugin: PluginApi) {}
+ constructor(private readonly plugin: PluginApi) {
+ this.reporting.trackApi(this.plugin, 'reporting', 'constructor');
+ }
reportInteraction(eventName: string, details?: EventDetails) {
+ this.reporting.trackApi(this.plugin, 'reporting', 'reportInteraction');
this.reporting.reportInteraction(
`${this.plugin.getPluginName()}-${eventName}`,
details
@@ -35,9 +39,11 @@
}
reportLifeCycle(eventName: string, details?: EventDetails) {
- this.reporting.reportLifeCycle(
- `${this.plugin.getPluginName()}-${eventName}`,
- details
- );
+ this.reporting.trackApi(this.plugin, 'reporting', 'reportLifeCycle');
+ this.reporting.reportLifeCycle(LifeCycle.PLUGIN_LIFE_CYCLE, {
+ ...details,
+ pluginName: this.plugin.getPluginName(),
+ eventName,
+ });
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api_test.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api_test.js
index 9e3876b..ffc2fbb 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api_test.js
@@ -28,7 +28,6 @@
let plugin;
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
stubRestApi('getAccount').returns(Promise.resolve(null));
});
@@ -63,11 +62,11 @@
assert.isTrue(appContext.reportingService.reportLifeCycle.called);
assert.equal(
appContext.reportingService.reportLifeCycle.lastCall.args[0],
- 'testplugin-test'
+ 'Plugin life cycle'
);
assert.deepEqual(
appContext.reportingService.reportLifeCycle.lastCall.args[1],
- {}
+ {pluginName: 'testplugin', eventName: 'test'}
);
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
index 60a07a4..8dc42fc 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
@@ -22,7 +22,6 @@
import '../gr-icons/gr-icons';
import '../gr-label/gr-label';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-label-info_html';
@@ -61,9 +60,7 @@
}
@customElement('gr-label-info')
-export class GrLabelInfo extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrLabelInfo extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts
index c640d23..0efe73d 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts
@@ -101,7 +101,10 @@
</gr-label>
</td>
<td>
- <gr-account-link account="[[mappedLabel.account]]"></gr-account-link>
+ <gr-account-link
+ account="[[mappedLabel.account]]"
+ change="[[change]]"
+ ></gr-account-link>
</td>
<td>
<gr-button
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.js b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.js
index b2365aa..e8a38ec 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.js
@@ -17,8 +17,7 @@
import '../../../test/common-test-setup-karma.js';
import './gr-label-info.js';
-import {isHidden} from '../../../test/test-utils.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import {isHidden, stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-label-info');
diff --git a/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts b/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
index 46b10cc..d3c08f4 100644
--- a/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
@@ -21,7 +21,6 @@
* used in gr-label-info.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {customElement} from '@polymer/decorators';
@@ -35,9 +34,7 @@
}
@customElement('gr-label')
-export class GrLabel extends TooltipMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
-) {
+export class GrLabel extends TooltipMixin(LegacyElementMixin(PolymerElement)) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.ts
index 4240c77..84467ee 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.ts
@@ -16,7 +16,6 @@
*/
import '../gr-autocomplete/gr-autocomplete';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-labeled-autocomplete_html';
@@ -32,9 +31,7 @@
};
}
@customElement('gr-labeled-autocomplete')
-export class GrLabeledAutocomplete extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrLabeledAutocomplete extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
index 4d65874..607f75d 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-limited-text_html';
@@ -35,7 +34,7 @@
*/
@customElement('gr-limited-text')
export class GrLimitedText extends TooltipMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
index fa244f6..36a360b 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
@@ -19,7 +19,6 @@
import '../gr-icons/gr-icons';
import '../gr-limited-text/gr-limited-text';
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {customElement, property} from '@polymer/decorators';
@@ -33,9 +32,7 @@
}
@customElement('gr-linked-chip')
-export class GrLinkedChip extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrLinkedChip extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
index e2c2d7f..44e740d 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-linked-text_html';
@@ -36,9 +35,7 @@
}
@customElement('gr-linked-text')
-export class GrLinkedText extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrLinkedText extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
index bf532ef..71b8bc7 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
@@ -18,7 +18,6 @@
import '@polymer/iron-icon/iron-icon';
import '../../../styles/shared-styles';
import '../gr-button/gr-button';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-list-view_html';
@@ -38,9 +37,7 @@
const DEBOUNCER_RELOAD = 'reload';
@customElement('gr-list-view')
-class GrListView extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrListView extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -67,9 +64,9 @@
path?: string;
/** @override */
- detached() {
- super.detached();
+ disconnectedCallback() {
this.cancelDebouncer(DEBOUNCER_RELOAD);
+ super.disconnectedCallback();
}
_filterChanged(newFilter?: string, oldFilter?: string) {
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
index 20e5296..a89e051 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-overlay_html';
@@ -37,7 +36,7 @@
@customElement('gr-overlay')
export class GrOverlay extends IronOverlayMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement)),
+ LegacyElementMixin(PolymerElement),
IronOverlayBehavior as IronOverlayBehavior
) {
static get template() {
@@ -136,7 +135,7 @@
_awaitOpen(fn: (this: GrOverlay) => void, reject: (error: Error) => void) {
let iters = 0;
const step = () => {
- this.async(() => {
+ setTimeout(() => {
if (this.style.display !== 'none') {
fn.call(this);
} else if (iters++ < AWAIT_MAX_ITERS) {
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.ts b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.ts
index e009499..623e6a3 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.ts
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-page-nav_html';
@@ -34,9 +33,7 @@
}
@customElement('gr-page-nav')
-export class GrPageNav extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrPageNav extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -51,14 +48,14 @@
this.bodyScrollHandler = () => this._handleBodyScroll();
}
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
window.addEventListener('scroll', this.bodyScrollHandler);
}
- detached() {
- super.detached();
+ disconnectedCallback() {
window.removeEventListener('scroll', this.bodyScrollHandler);
+ super.disconnectedCallback();
}
_handleBodyScroll() {
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts
index 6db1925..9dee18f 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts
@@ -18,7 +18,6 @@
import '../../../styles/shared-styles';
import '../gr-icons/gr-icons';
import '../gr-labeled-autocomplete/gr-labeled-autocomplete';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-branch-picker_html';
@@ -44,9 +43,7 @@
};
}
@customElement('gr-repo-branch-picker')
-export class GrRepoBranchPicker extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrRepoBranchPicker extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
@@ -75,8 +72,8 @@
}
/** @override */
- attached() {
- super.attached();
+ connectedCallback() {
+ super.connectedCallback();
if (this.repo) {
this.$.repoInput.setText(this.repo);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
index ec59ddc..145883b 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
@@ -52,6 +52,7 @@
Base64File,
Base64FileContent,
Base64ImageFile,
+ BasePatchSetNum,
BlameInfo,
BranchInfo,
BranchInput,
@@ -1774,6 +1775,15 @@
}) as Promise<ChangeInfo[] | undefined>;
}
+ getChangesWithSimilarTopic(topic: string): Promise<ChangeInfo[] | undefined> {
+ const query = [`intopic:"${topic}"`].join(' ');
+ return this._restApiHelper.fetchJSON({
+ url: '/changes/',
+ params: {q: query},
+ anonymizedUrl: '/changes/intopic:*',
+ }) as Promise<ChangeInfo[] | undefined>;
+ }
+
getReviewedFiles(
changeNum: NumericChangeId,
patchNum: PatchSetNum
@@ -2220,14 +2230,14 @@
getDiffComments(
changeNum: NumericChangeId,
- basePatchNum: PatchSetNum,
+ basePatchNum: BasePatchSetNum,
patchNum: PatchSetNum,
path: string
): Promise<GetDiffCommentsOutput>;
getDiffComments(
changeNum: NumericChangeId,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
patchNum?: PatchSetNum,
path?: string
) {
@@ -2253,14 +2263,14 @@
getDiffRobotComments(
changeNum: NumericChangeId,
- basePatchNum: PatchSetNum,
+ basePatchNum: BasePatchSetNum,
patchNum: PatchSetNum,
path: string
): Promise<GetDiffRobotCommentsOutput>;
getDiffRobotComments(
changeNum: NumericChangeId,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
patchNum?: PatchSetNum,
path?: string
) {
@@ -2289,14 +2299,14 @@
getDiffDrafts(
changeNum: NumericChangeId,
- basePatchNum: PatchSetNum,
+ basePatchNum: BasePatchSetNum,
patchNum: PatchSetNum,
path: string
): Promise<GetDiffCommentsOutput>;
getDiffDrafts(
changeNum: NumericChangeId,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
patchNum?: PatchSetNum,
path?: string
) {
@@ -2356,7 +2366,7 @@
changeNum: NumericChangeId,
endpoint: '/comments' | '/drafts',
params?: FetchParams,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
patchNum?: PatchSetNum,
path?: string
): Promise<GetDiffCommentsOutput>;
@@ -2365,7 +2375,7 @@
changeNum: NumericChangeId,
endpoint: '/robotcomments',
params?: FetchParams,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
patchNum?: PatchSetNum,
path?: string
): Promise<GetDiffRobotCommentsOutput>;
@@ -2374,7 +2384,7 @@
changeNum: NumericChangeId,
endpoint: string,
params?: FetchParams,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
patchNum?: PatchSetNum,
path?: string
): Promise<
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.js
index 4eef8a2f..3a5a587 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.js
@@ -16,8 +16,7 @@
*/
import '../../../../test/common-test-setup-karma.js';
-import {SiteBasedCache} from './gr-rest-api-helper.js';
-import {FetchPromisesCache, GrRestApiHelper} from './gr-rest-api-helper.js';
+import {SiteBasedCache, FetchPromisesCache, GrRestApiHelper} from './gr-rest-api-helper.js';
import {appContext} from '../../../../services/app-context.js';
suite('gr-rest-api-helper tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select.ts b/polygerrit-ui/app/elements/shared/gr-select/gr-select.ts
index a2c1253..27f6cb1 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select.ts
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {html} from '@polymer/polymer/lib/utils/html-tag';
@@ -30,9 +29,7 @@
* GrSelect `gr-select` component.
*/
@customElement('gr-select')
-export class GrSelect extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrSelect extends LegacyElementMixin(PolymerElement) {
static get template() {
return html` <slot></slot> `;
}
@@ -57,7 +54,7 @@
// Async needed for firefox to populate value. It was trying to do it
// before options from a dom-repeat were rendered previously.
// See https://bugs.chromium.org/p/gerrit/issues/detail?id=7735
- this.async(() => {
+ setTimeout(() => {
// TODO(TS): maybe should check for undefined before assigning
// or fallback to ''
this.nativeSelect.value = this.bindValue!;
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts
index 27f4069..133c6d5 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts
@@ -16,7 +16,6 @@
*/
import '../../../styles/shared-styles';
import '../gr-copy-clipboard/gr-copy-clipboard';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-shell-command_html';
@@ -29,9 +28,7 @@
}
@customElement('gr-shell-command')
-class GrShellCommand extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+class GrShellCommand extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index 885db2a..3bc4c84 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -21,7 +21,6 @@
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
import '../../../styles/shared-styles';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-textarea_html';
@@ -82,7 +81,7 @@
*/
@customElement('gr-textarea')
export class GrTextarea extends KeyboardShortcutMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
@@ -186,7 +185,7 @@
// Put the cursor at the end always.
textarea.selectionStart = textarea.value.length;
textarea.selectionEnd = textarea.selectionStart;
- this.async(() => {
+ setTimeout(() => {
textarea.focus();
});
}
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
index cfd9e81..ab3b5c5 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../gr-icons/gr-icons';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-tooltip-content_html';
@@ -33,7 +32,7 @@
*/
@customElement('gr-tooltip-content')
export class GrTooltipContent extends TooltipMixin(
- GestureEventListeners(LegacyElementMixin(PolymerElement))
+ LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.ts b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.ts
index c1a8eb2..8652df0 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-tooltip_html';
@@ -32,9 +31,7 @@
}
@customElement('gr-tooltip')
-export class GrTooltip extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)
-) {
+export class GrTooltip extends LegacyElementMixin(PolymerElement) {
static get template() {
return htmlTemplate;
}
diff --git a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts b/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts
index 75ad608..e60c614 100644
--- a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts
+++ b/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts
@@ -88,7 +88,6 @@
/** @override */
disconnectedCallback() {
- super.disconnectedCallback();
// NOTE: if you define your own `detached` in your component
// then this won't take affect (as its not a class yet)
this._handleHideTooltip();
@@ -96,6 +95,7 @@
this.removeEventListener('mouseenter', this.mouseenterHandler);
}
window.removeEventListener('scroll', this.windowScrollHandler);
+ super.disconnectedCallback();
}
@observe('hasTooltip')
diff --git a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
index ab85b87..85931b4 100644
--- a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
+++ b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
@@ -960,10 +960,10 @@
/** @override */
disconnectedCallback() {
- super.disconnectedCallback();
if (shortcutManager.detachHost(this)) {
this.removeOwnKeyBindings();
}
+ super.disconnectedCallback();
}
keyboardShortcuts() {
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses.ts b/polygerrit-ui/app/node_modules_licenses/licenses.ts
index f03c7e6..36b2eb5 100644
--- a/polygerrit-ui/app/node_modules_licenses/licenses.ts
+++ b/polygerrit-ui/app/node_modules_licenses/licenses.ts
@@ -250,6 +250,10 @@
license: SharedLicenses.Polymer2015
},
{
+ name: "@polymer/paper-tooltip",
+ license: SharedLicenses.Polymer2015
+ },
+ {
name: "@polymer/polymer",
license: SharedLicenses.Polymer2017
},
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index 5e15990..2f6aa05 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -24,6 +24,7 @@
"@polymer/paper-listbox": "^3.0.1",
"@polymer/paper-tabs": "^3.1.0",
"@polymer/paper-toggle-button": "^3.0.1",
+ "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "^3.4.1",
"@types/resize-observer-browser": "^0.1.5",
"@webcomponents/shadycss": "^1.9.2",
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.js b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.js
index 85c92d3..66681ee 100644
--- a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.js
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.js
@@ -32,7 +32,6 @@
};
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
provider = new GrEmailSuggestionsProvider(appContext.restApiService);
});
diff --git a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.js b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.js
index 3ce9d9d..1a14abf 100644
--- a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.js
+++ b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.js
@@ -33,7 +33,6 @@
};
setup(() => {
- stubRestApi('getConfig').returns(Promise.resolve({}));
provider = new GrGroupSuggestionsProvider(appContext.restApiService);
});
diff --git a/polygerrit-ui/app/services/change/change-model.ts b/polygerrit-ui/app/services/change/change-model.ts
index b2bdcfe..e7472de 100644
--- a/polygerrit-ui/app/services/change/change-model.ts
+++ b/polygerrit-ui/app/services/change/change-model.ts
@@ -46,10 +46,18 @@
// Must only be used by the change service or whatever is in control of this
// model.
export function updateState(change?: ParsedChangeInfo) {
- privateState$.next({
- ...privateState$.getValue(),
- change,
- });
+ const current = privateState$.getValue();
+ // We want to make it easy for subscribers to react to change changes, so we
+ // are explicitly emitting and additional `undefined` when the change number
+ // changes. So if you are subscribed to the latestPatchsetNumber for example,
+ // then you can rely on emissions even if the old and the new change have the
+ // same latestPatchsetNumber.
+ if (change !== undefined && current.change !== undefined) {
+ if (change._number !== current.change._number) {
+ privateState$.next({...current, change: undefined});
+ }
+ }
+ privateState$.next({...current, change});
}
/**
@@ -91,9 +99,6 @@
*
* Note that this selector can emit a patchNum without the change being
* available!
- *
- * TODO: It would be good to assert/enforce somehow that currentPatchNum$ cannot
- * emit 'PARENT'.
*/
export const currentPatchNum$: Observable<
PatchSetNum | undefined
diff --git a/polygerrit-ui/app/services/change/change-service.ts b/polygerrit-ui/app/services/change/change-service.ts
index 4524813..c292fb5 100644
--- a/polygerrit-ui/app/services/change/change-service.ts
+++ b/polygerrit-ui/app/services/change/change-service.ts
@@ -16,21 +16,16 @@
*/
import {routerChangeNum$} from '../router/router-model';
import {updateState} from './change-model';
-import {tap} from 'rxjs/operators';
import {ParsedChangeInfo} from '../../types/types';
export class ChangeService {
- // TODO: In the future we will want to make restApiService.getChangeDetail()
- // calls from a switchMap() here. For now just make sure to invalidate the
- // change when no changeNum is set.
- private routerChangeNumEffect = routerChangeNum$.pipe(
- tap(changeNum => {
- if (!changeNum) updateState(undefined);
- })
- );
-
constructor() {
- this.routerChangeNumEffect.subscribe();
+ // TODO: In the future we will want to make restApiService.getChangeDetail()
+ // calls from a switchMap() here. For now just make sure to invalidate the
+ // change when no changeNum is set.
+ routerChangeNum$.subscribe(changeNum => {
+ if (!changeNum) updateState(undefined);
+ });
}
/**
diff --git a/polygerrit-ui/app/services/checks/checks-model.ts b/polygerrit-ui/app/services/checks/checks-model.ts
index 28aca73..1c5b862 100644
--- a/polygerrit-ui/app/services/checks/checks-model.ts
+++ b/polygerrit-ui/app/services/checks/checks-model.ts
@@ -26,6 +26,7 @@
RunStatus,
} from '../../api/checks';
import {distinctUntilChanged, map} from 'rxjs/operators';
+import {PatchSetNumber} from '../../types/common';
// This is a convenience type for working with results, because when working
// with a bunch of results you will typically also want to know about the run
@@ -41,29 +42,44 @@
}
interface ChecksState {
- [name: string]: ChecksProviderState;
+ patchsetNumber?: PatchSetNumber;
+ providerNameToState: {
+ [name: string]: ChecksProviderState;
+ };
}
-const initialState: ChecksState = {};
+const initialState: ChecksState = {
+ providerNameToState: {},
+};
const privateState$ = new BehaviorSubject(initialState);
// Re-exporting as Observable so that you can only subscribe, but not emit.
export const checksState$: Observable<ChecksState> = privateState$;
-export const aPluginHasRegistered$ = checksState$.pipe(
+export const checksPatchsetNumber$ = checksState$.pipe(
+ map(state => state.patchsetNumber),
+ distinctUntilChanged()
+);
+
+export const checksProviderState$ = checksState$.pipe(
+ map(state => state.providerNameToState),
+ distinctUntilChanged()
+);
+
+export const aPluginHasRegistered$ = checksProviderState$.pipe(
map(state => Object.keys(state).length > 0),
distinctUntilChanged()
);
-export const someProvidersAreLoading$ = checksState$.pipe(
+export const someProvidersAreLoading$ = checksProviderState$.pipe(
map(state => {
return Object.values(state).some(providerState => providerState.loading);
}),
distinctUntilChanged()
);
-export const allActions$ = checksState$.pipe(
+export const allActions$ = checksProviderState$.pipe(
map(state => {
return Object.values(state).reduce(
(allActions: Action[], providerState: ChecksProviderState) => [
@@ -75,7 +91,7 @@
})
);
-export const allRuns$ = checksState$.pipe(
+export const allRuns$ = checksProviderState$.pipe(
map(state => {
return Object.values(state).reduce(
(allRuns: CheckRun[], providerState: ChecksProviderState) => [
@@ -87,7 +103,19 @@
})
);
-export const allResults$ = checksState$.pipe(
+export const checkToPluginMap$ = checksProviderState$.pipe(
+ map(state => {
+ const map = new Map<string, string>();
+ for (const [pluginName, providerState] of Object.entries(state)) {
+ for (const run of providerState.runs) {
+ map.set(run.checkName, pluginName);
+ }
+ }
+ return map;
+ })
+);
+
+export const allResults$ = checksProviderState$.pipe(
map(state => {
return Object.values(state)
.reduce(
@@ -112,7 +140,8 @@
config?: ChecksApiConfig
) {
const nextState = {...privateState$.getValue()};
- nextState[pluginName] = {
+ nextState.providerNameToState = {...nextState.providerNameToState};
+ nextState.providerNameToState[pluginName] = {
pluginName,
loading: false,
config,
@@ -188,8 +217,9 @@
export function updateStateSetLoading(pluginName: string) {
const nextState = {...privateState$.getValue()};
- nextState[pluginName] = {
- ...nextState[pluginName],
+ nextState.providerNameToState = {...nextState.providerNameToState};
+ nextState.providerNameToState[pluginName] = {
+ ...nextState.providerNameToState[pluginName],
loading: true,
};
privateState$.next(nextState);
@@ -201,11 +231,18 @@
actions: Action[] = []
) {
const nextState = {...privateState$.getValue()};
- nextState[pluginName] = {
- ...nextState[pluginName],
+ nextState.providerNameToState = {...nextState.providerNameToState};
+ nextState.providerNameToState[pluginName] = {
+ ...nextState.providerNameToState[pluginName],
loading: false,
runs: [...runs],
actions: [...actions],
};
privateState$.next(nextState);
}
+
+export function updateStateSetPatchset(patchsetNumber?: PatchSetNumber) {
+ const nextState = {...privateState$.getValue()};
+ nextState.patchsetNumber = patchsetNumber;
+ privateState$.next(nextState);
+}
diff --git a/polygerrit-ui/app/services/checks/checks-service.ts b/polygerrit-ui/app/services/checks/checks-service.ts
index b11eada..f0c566b 100644
--- a/polygerrit-ui/app/services/checks/checks-service.ts
+++ b/polygerrit-ui/app/services/checks/checks-service.ts
@@ -16,9 +16,9 @@
*/
import {
+ filter,
switchMap,
takeWhile,
- tap,
throttleTime,
withLatestFrom,
} from 'rxjs/operators';
@@ -29,11 +29,14 @@
FetchResponse,
ResponseCode,
} from '../../api/checks';
-import {change$, currentPatchNum$} from '../change/change-model';
+import {change$, changeNum$, latestPatchNum$} from '../change/change-model';
import {
updateStateSetLoading,
+ checkToPluginMap$,
updateStateSetProvider,
updateStateSetResults,
+ checksPatchsetNumber$,
+ updateStateSetPatchset,
} from './checks-model';
import {
BehaviorSubject,
@@ -42,14 +45,35 @@
Observable,
of,
Subject,
+ timer,
} from 'rxjs';
+import {PatchSetNumber} from '../../types/common';
+import {getCurrentRevision} from '../../utils/change-util';
export class ChecksService {
private readonly providers: {[name: string]: ChecksProvider} = {};
private readonly reloadSubjects: {[name: string]: Subject<void>} = {};
- private changeAndPatchNum$ = change$.pipe(withLatestFrom(currentPatchNum$));
+ private checkToPluginMap = new Map<string, string>();
+
+ private readonly documentVisibilityChange$ = new BehaviorSubject(undefined);
+
+ constructor() {
+ checkToPluginMap$.subscribe(map => {
+ this.checkToPluginMap = map;
+ });
+ latestPatchNum$.subscribe(num => {
+ updateStateSetPatchset(num);
+ });
+ document.addEventListener('visibilitychange', () => {
+ this.documentVisibilityChange$.next(undefined);
+ });
+ }
+
+ setPatchset(num: PatchSetNumber) {
+ updateStateSetPatchset(num);
+ }
reload(pluginName: string) {
this.reloadSubjects[pluginName].next();
@@ -59,6 +83,12 @@
Object.keys(this.providers).forEach(key => this.reload(key));
}
+ reloadForCheck(checkName?: string) {
+ if (!checkName) return;
+ const plugin = this.checkToPluginMap.get(checkName);
+ if (plugin) this.reload(plugin);
+ }
+
register(
pluginName: string,
provider: ChecksProvider,
@@ -67,39 +97,53 @@
this.providers[pluginName] = provider;
this.reloadSubjects[pluginName] = new BehaviorSubject<void>(undefined);
updateStateSetProvider(pluginName, config);
- // Both, changed numbers and and announceUpdate request should trigger.
+ const pollIntervalMs = (config?.fetchPollingIntervalSeconds ?? 60) * 1000;
+ // Various events should trigger fetching checks from the provider:
+ // 1. Change number and patchset number changes.
+ // 2. Specific reload requests.
+ // 3. Regular polling starting with an initial fetch right now.
+ // 4. A hidden Gerrit tab becoming visible.
combineLatest([
- this.changeAndPatchNum$,
+ changeNum$,
+ checksPatchsetNumber$,
this.reloadSubjects[pluginName].pipe(throttleTime(1000)),
+ timer(0, pollIntervalMs),
+ this.documentVisibilityChange$,
])
.pipe(
takeWhile(_ => !!this.providers[pluginName]),
+ filter(_ => document.visibilityState !== 'hidden'),
+ withLatestFrom(change$),
switchMap(
- ([[change, patchNum], _]): Observable<FetchResponse> => {
- if (!change || !patchNum || typeof patchNum !== 'number') {
+ ([[changeNum, patchNum], change]): Observable<FetchResponse> => {
+ if (
+ !change ||
+ !changeNum ||
+ !patchNum ||
+ typeof patchNum !== 'number'
+ ) {
return of({
responseCode: ResponseCode.OK,
runs: [],
});
}
const data: ChangeData = {
- changeNumber: change._number,
+ changeNumber: changeNum,
patchsetNumber: patchNum,
repo: change.project,
+ commmitMessage: getCurrentRevision(change)?.commit?.message,
};
updateStateSetLoading(pluginName);
return from(this.providers[pluginName].fetch(data));
}
- ),
- tap(response => {
- updateStateSetResults(
- pluginName,
- response.runs ?? [],
- response.actions
- );
- })
+ )
)
- .subscribe(() => {});
- this.reload(pluginName);
+ .subscribe(response => {
+ updateStateSetResults(
+ pluginName,
+ response.runs ?? [],
+ response.actions
+ );
+ });
}
}
diff --git a/polygerrit-ui/app/services/checks/checks-util.ts b/polygerrit-ui/app/services/checks/checks-util.ts
index ea532ea..9b75bd9 100644
--- a/polygerrit-ui/app/services/checks/checks-util.ts
+++ b/polygerrit-ui/app/services/checks/checks-util.ts
@@ -14,7 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Action, Category, CheckRun, RunStatus} from '../../api/checks';
+import {
+ Action,
+ Category,
+ CheckResult,
+ CheckRun,
+ RunStatus,
+} from '../../api/checks';
import {assertNever} from '../../utils/common-util';
export function worstCategory(run: CheckRun) {
@@ -118,6 +124,16 @@
return hasCompleted(run) && hasResultsOf(run, category);
}
+export function hasResults(run: CheckRun): boolean {
+ return (run.results ?? []).length > 0;
+}
+
+export function allResults(runs: CheckRun[]): CheckResult[] {
+ return runs.reduce((results: CheckResult[], run: CheckRun) => {
+ return [...results, ...(run.results ?? [])];
+ }, []);
+}
+
export function hasResultsOf(run: CheckRun, category: Category) {
return getResultsOf(run, category).length > 0;
}
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index 878b8f7..ff950b8 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -29,7 +29,6 @@
// with the new Checks plugin API.
CI_REBOOT_CHECKS = 'UiFeature__ci_reboot_checks',
NEW_CHANGE_SUMMARY_UI = 'UiFeature__new_change_summary_ui',
- PORTING_COMMENTS = 'UiFeature__porting_comments',
NEW_IMAGE_DIFF_UI = 'UiFeature__new_image_diff_ui',
COMMENT_CONTEXT = 'UiFeature__comment_context',
}
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
index 4196513..c11896f 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
@@ -17,6 +17,8 @@
import {NumericChangeId} from '../../types/common';
import {EventDetails} from '../../api/reporting';
+import {PluginApi} from '../../api/plugin';
+import {Execution, LifeCycle} from '../../constants/reporting';
export type EventValue = string | number | {error?: Error};
@@ -86,17 +88,18 @@
* @param elapsed The time elapsed of the RPC.
*/
reportRpcTiming(anonymizedUrl: string, elapsed: number): void;
- reportLifeCycle(eventName: string, details?: EventDetails): void;
+ reportLifeCycle(eventName: LifeCycle, details?: EventDetails): void;
/**
* Use this method, if you want to check/count how often a certain code path
* is executed. For example you can use this method to prove that certain code
* paths are dead: Add reportExecution(), check the logs a week later, then
- * safely remove the coe.
+ * safely remove the code.
*
* Every execution is only reported once per session.
*/
- reportExecution(id: string, details: EventDetails): void;
+ reportExecution(id: Execution, details?: EventDetails): void;
+ trackApi(plugin: PluginApi, object: string, method: string): void;
reportInteraction(eventName: string, details?: EventDetails): void;
/**
* A draft interaction was started. Update the time-between-draft-actions
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
index 631a4e0..bd564c5 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -20,6 +20,8 @@
import {hasOwnProperty} from '../../utils/common-util';
import {NumericChangeId} from '../../types/common';
import {EventDetails} from '../../api/reporting';
+import {PluginApi} from '../../api/plugin';
+import {Execution, LifeCycle} from '../../constants/reporting';
// Latency reporting constants.
@@ -448,17 +450,23 @@
onVisibilityChange() {
this.hiddenDurationTimer.onVisibilityChange();
- const eventName = `Visibility changed to ${document.visibilityState}`;
- this.reporter(
- LIFECYCLE.TYPE,
- LIFECYCLE.CATEGORY.VISIBILITY,
- eventName,
- undefined,
- {
- hiddenDurationMs: this.hiddenDurationTimer.hiddenDurationMs,
- },
- true
- );
+ let eventName;
+ if (document.visibilityState === 'hidden') {
+ eventName = LifeCycle.VISIBILILITY_HIDDEN;
+ } else if (document.visibilityState === 'visible') {
+ eventName = LifeCycle.VISIBILILITY_VISIBLE;
+ }
+ if (eventName)
+ this.reporter(
+ LIFECYCLE.TYPE,
+ LIFECYCLE.CATEGORY.VISIBILITY,
+ eventName,
+ undefined,
+ {
+ hiddenDurationMs: this.hiddenDurationTimer.hiddenDurationMs,
+ },
+ false
+ );
}
/**
@@ -606,7 +614,13 @@
}
reportExtension(name: string) {
- this.reporter(LIFECYCLE.TYPE, LIFECYCLE.CATEGORY.EXTENSION_DETECTED, name);
+ this.reporter(
+ LIFECYCLE.TYPE,
+ LIFECYCLE.CATEGORY.EXTENSION_DETECTED,
+ LifeCycle.EXTENSION_DETECTED,
+ undefined,
+ {name}
+ );
}
pluginLoaded(name: string) {
@@ -620,7 +634,7 @@
this.reporter(
LIFECYCLE.TYPE,
LIFECYCLE.CATEGORY.PLUGINS_INSTALLED,
- LIFECYCLE.CATEGORY.PLUGINS_INSTALLED,
+ LifeCycle.PLUGINS_INSTALLED,
undefined,
{pluginsList: pluginsList || []},
true
@@ -769,7 +783,7 @@
}
}
- reportLifeCycle(eventName: string, details: EventDetails) {
+ reportLifeCycle(eventName: LifeCycle, details: EventDetails) {
this.reporter(
LIFECYCLE.TYPE,
LIFECYCLE.CATEGORY.DEFAULT,
@@ -791,19 +805,25 @@
);
}
- reportExecution(id: string, details: EventDetails) {
+ reportExecution(name: Execution, details?: EventDetails) {
+ const id = `${name}${JSON.stringify(details)}`;
if (this.executionReported.has(id)) return;
this.executionReported.add(id);
this.reporter(
LIFECYCLE.TYPE,
LIFECYCLE.CATEGORY.EXECUTION,
- id,
+ name,
undefined,
details,
- false
+ true // skip console log
);
}
+ trackApi(pluginApi: PluginApi, object: string, method: string) {
+ const plugin = pluginApi?.getPluginName() ?? 'unknown';
+ this.reportExecution(Execution.PLUGIN_API, {plugin, object, method});
+ }
+
/**
* A draft interaction was started. Update the time-between-draft-actions
* timer.
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
index 484ce45..c4030c9 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
@@ -16,6 +16,8 @@
*/
import {ReportingService, Timer} from './gr-reporting';
import {EventDetails} from '../../api/reporting';
+import {PluginApi} from '../../api/plugin';
+import {Execution} from '../../constants/reporting';
export class MockTimer implements Timer {
end(): this {
@@ -64,9 +66,13 @@
error: () => {
log('error');
},
- reportExecution: (id: string, details: EventDetails) => {
+ reportExecution: (id: Execution, details?: EventDetails) => {
log(`reportExecution '${id}': ${JSON.stringify(details)}`);
},
+ trackApi: (pluginApi: PluginApi, object: string, method: string) => {
+ const plugin = pluginApi?.getPluginName() ?? 'unknown';
+ log(`trackApi '${plugin}', ${object}, ${method}`);
+ },
reportExtension: () => {},
reportInteraction: (eventName: string, details?: EventDetails) => {
log(`reportInteraction '${eventName}': ${JSON.stringify(details)}`);
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js b/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js
index 6e56ab1..9b71908 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js
@@ -322,7 +322,8 @@
test('reportExtension', () => {
service.reportExtension('foo');
assert.isTrue(service.reporter.calledWithExactly(
- 'lifecycle', 'Extension detected', 'foo'
+ 'lifecycle', 'Extension detected', 'Extension detected', undefined,
+ {name: 'foo'}
));
});
@@ -340,6 +341,16 @@
));
});
+ test('trackApi reports same event only once', () => {
+ sinon.spy(service, '_reportEvent');
+ const pluginApi = {getPluginName: () => 'test'};
+ service.trackApi(pluginApi, 'object', 'method');
+ service.trackApi(pluginApi, 'object', 'method');
+ assert.isTrue(service.reporter.calledOnce);
+ service.trackApi(pluginApi, 'object', 'method2');
+ assert.isTrue(service.reporter.calledTwice);
+ });
+
test('report start time', () => {
service.reporter.restore();
sinon.stub(window.performance, 'now').returns(42);
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
index 4742e65..30bf490 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
@@ -24,6 +24,7 @@
AccountInfo,
ActionNameToActionInfoMap,
Base64FileContent,
+ BasePatchSetNum,
BlameInfo,
BranchInfo,
BranchInput,
@@ -422,7 +423,7 @@
): Promise<GetDiffCommentsOutput>;
getDiffComments(
changeNum: NumericChangeId,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
patchNum?: PatchSetNum,
path?: string
):
@@ -440,7 +441,7 @@
): Promise<GetDiffRobotCommentsOutput>;
getDiffRobotComments(
changeNum: NumericChangeId,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
patchNum?: PatchSetNum,
path?: string
):
@@ -458,7 +459,7 @@
): Promise<GetDiffCommentsOutput>;
getDiffDrafts(
changeNum: NumericChangeId,
- basePatchNum?: PatchSetNum,
+ basePatchNum?: BasePatchSetNum,
patchNum?: PatchSetNum,
path?: string
):
@@ -659,6 +660,7 @@
topic: string,
changeNum: NumericChangeId
): Promise<ChangeInfo[] | undefined>;
+ getChangesWithSimilarTopic(topic: string): Promise<ChangeInfo[] | undefined>;
hasPendingDiffDrafts(): number;
awaitPendingDiffDrafts(): Promise<void>;
diff --git a/polygerrit-ui/app/styles/themes/app-theme.ts b/polygerrit-ui/app/styles/themes/app-theme.ts
index 18c12b0..c9602a7 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.ts
+++ b/polygerrit-ui/app/styles/themes/app-theme.ts
@@ -45,6 +45,7 @@
--red-200: #f6aea9;
--red-50: #fce8e6;
--blue-900: #174ea6;
+ --blue-800: #185abc;
--blue-700: #1967d2;
--blue-200: #aecbfa;
--blue-50: #e8f0fe;
@@ -61,10 +62,13 @@
--green-200: #a8dab5;
--green-50: #e6f4ea;
--gray-900: #202124;
+ --gray-800: #3c4043;
--gray-700: #5f6368;
--gray-300: #dadce0;
--gray-100: #f1f3f4;
--gray-50: #f8f9fa;
+ --purple-900: #681da8;
+ --purple-50: #f3e8fd;
--chip-color: var(--gray-900);
--error-color: var(--red-900);
@@ -74,7 +78,7 @@
--warning-background: var(--orange-50);
--info-foreground: var(--blue-700);
--info-background: var(--blue-50);
- --selected-foreground: var(--blue-700);
+ --selected-foreground: var(--blue-800);
--selected-background: var(--blue-50);
--success-foreground: var(--green-700);
--success-background: var(--green-50);
@@ -130,7 +134,7 @@
--disabled-button-background-color: #e8eaed;
--primary-button-background-color: var(--blue-700);
--selection-background-color: rgba(161, 194, 250, 0.1);
- --tooltip-background-color: #333;
+ --tooltip-background-color: var(--gray-900);
/* comment background colors */
--comment-background-color: #e8eaed;
--robot-comment-background-color: var(--blue-50);
@@ -179,6 +183,7 @@
--font-weight-h2: 400;
--font-weight-h3: 400;
--context-control-button-font: var(--font-weight-normal) var(--font-size-normal) var(--font-family);
+ --code-hint-font-weight: 500;
/* spacing */
--spacing-xxs: 1px;
@@ -219,16 +224,15 @@
--diff-trailing-whitespace-indicator: #ff9ad2;
--light-add-highlight-color: #d8fed8;
--light-rebased-add-highlight-color: #eef;
- --diff-moved-in-background: #e4f7fb;
- --diff-moved-out-background: #f3e8fd;
- --diff-moved-in-label-background: #007b83;
- --diff-moved-out-label-background: #681da8;
+ --diff-moved-in-background: var(--cyan-50);
+ --diff-moved-out-background: var(--purple-50);
+ --diff-moved-in-label-color: var(--cyan-900);
+ --diff-moved-out-label-color: var(--purple-900);
--light-remove-add-highlight-color: #fff8dc;
--light-remove-highlight-color: #ffebee;
--coverage-covered: #e0f2f1;
--coverage-not-covered: #ffd1a4;
- --ranged-comment-chip-background: #b06000;
- --ranged-comment-chip-text-color: #feefe3;
+ --ranged-comment-hint-text-color: var(--orange-900);
/* syntax colors */
--syntax-attr-color: #219;
@@ -275,6 +279,14 @@
--iron-overlay-backdrop: {
transition: none;
};
+ --paper-tooltip-duration-in: 0;
+ --paper-tooltip-duration-out: 0;
+ --paper-tooltip-background: var(--tooltip-background-color);
+ --paper-tooltip-opacity: 1.0;
+ --paper-tooltip-text-color: var(--tooltip-text-color);
+ --paper-tooltip: {
+ font-size: var(--font-size-small);
+ }
}
@media screen and (max-width: 50em) {
html {
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.ts b/polygerrit-ui/app/styles/themes/dark-theme.ts
index 5455a24..5c97597 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.ts
+++ b/polygerrit-ui/app/styles/themes/dark-theme.ts
@@ -66,7 +66,7 @@
--reviewed-text-color: #dadce0;
--vote-text-color: black;
--status-text-color: black;
- --tooltip-text-color: white;
+ --tooltip-text-color: var(--gray-200);
--negative-red-text-color: #f28b82;
--positive-green-text-color: #81c995;
@@ -87,7 +87,7 @@
--disabled-button-background-color: #484a4d;
--primary-button-background-color: var(--link-color);
--selection-background-color: rgba(161, 194, 250, 0.1);
- --tooltip-background-color: #111;
+ --tooltip-background-color: var(--gray-800);
/* comment background colors */
--comment-background-color: #3c3f43;
--robot-comment-background-color: #1e3a5f;
@@ -142,16 +142,15 @@
--diff-trailing-whitespace-indicator: #ff9ad2;
--light-add-highlight-color: #0f401f;
--light-rebased-add-highlight-color: #487165;
- --diff-moved-in-background: #006066;
- --diff-moved-out-background: #681da8;
- --diff-moved-in-label-background: #cbf0f8;
- --diff-moved-out-label-background: #e9d2fd;
+ --diff-moved-in-background: #1d4042;
+ --diff-moved-out-background: #230e34;
+ --diff-moved-in-label-color: var(--cyan-50);
+ --diff-moved-out-label-color: var(--purple-50);
--light-remove-add-highlight-color: #2f3f2f;
--light-remove-highlight-color: #320404;
--coverage-covered: #112826;
--coverage-not-covered: #6b3600;
- --ranged-comment-chip-background: #e8f0fe;
- --ranged-comment-chip-text-color: #174ea6;
+ --ranged-comment-hint-text-color: var(--blue-50);
/* syntax colors */
--syntax-attr-color: #80cbbf;
diff --git a/polygerrit-ui/app/test/mocks/comment-api.js b/polygerrit-ui/app/test/mocks/comment-api.js
index f2ca48c..526aabe 100644
--- a/polygerrit-ui/app/test/mocks/comment-api.js
+++ b/polygerrit-ui/app/test/mocks/comment-api.js
@@ -15,7 +15,6 @@
* limitations under the License.
*/
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
@@ -23,9 +22,7 @@
* This is an "abstract" class for tests. The descendant must define a template
* for this element and a tagName - see createCommentApiMockWithTemplateElement below
*/
-class CommentApiMock extends GestureEventListeners(
- LegacyElementMixin(
- PolymerElement)) {
+class CommentApiMock extends LegacyElementMixin(PolymerElement) {
static get properties() {
return {
_changeComments: Object,
diff --git a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
index 5efc562..395c9f67 100644
--- a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
+++ b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
@@ -78,6 +78,7 @@
createConfig,
createPreferences,
createServerInfo,
+ createSubmittedTogetherInfo,
} from '../test-data-generators';
import {
createDefaultDiffPrefs,
@@ -253,11 +254,14 @@
return Promise.resolve([]);
},
getChangesSubmittedTogether(): Promise<SubmittedTogetherInfo | undefined> {
- throw new Error('getChangesSubmittedTogether() not implemented by mock.');
+ return Promise.resolve(createSubmittedTogetherInfo());
},
getChangesWithSameTopic(): Promise<ChangeInfo[] | undefined> {
return Promise.resolve([]);
},
+ getChangesWithSimilarTopic(): Promise<ChangeInfo[] | undefined> {
+ return Promise.resolve([]);
+ },
getConfig(): Promise<ServerInfo | undefined> {
return Promise.resolve(createServerInfo());
},
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index d2c1cd2..2ac8ffe 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -61,6 +61,9 @@
Requirement,
RequirementType,
UrlEncodedCommentId,
+ BasePatchSetNum,
+ RelatedChangeAndCommitInfo,
+ SubmittedTogetherInfo,
} from '../types/common';
import {
AccountsVisibility,
@@ -89,6 +92,7 @@
import {GerritView} from '../services/router/router-model';
import {ChangeComments} from '../elements/diff/gr-comment-api/gr-comment-api';
import {EditRevisionInfo, ParsedChangeInfo} from '../types/types';
+import {ChangeMessage} from '../elements/change/gr-message/gr-message';
export function dateToTimestamp(date: Date): Timestamp {
const nanosecondSuffix = '.000000000';
@@ -200,10 +204,12 @@
};
}
-export function createCommitInfoWithRequiredCommit(): CommitInfoWithRequiredCommit {
+export function createCommitInfoWithRequiredCommit(
+ commit = 'commit'
+): CommitInfoWithRequiredCommit {
return {
...createCommit(),
- commit: 'commit' as CommitId,
+ commit: commit as CommitId,
};
}
@@ -221,12 +227,12 @@
export function createEditRevision(): EditRevisionInfo {
return {
_number: EditPatchSetNum,
- basePatchNum: 1 as PatchSetNum,
+ basePatchNum: 1 as BasePatchSetNum,
commit: createCommit(),
};
}
-export function createChangeMessage(id = 'cm_id_1'): ChangeMessageInfo {
+export function createChangeMessageInfo(id = 'cm_id_1'): ChangeMessageInfo {
return {
id: id as ChangeMessageId,
date: dateToTimestamp(TEST_CHANGE_CREATED),
@@ -234,6 +240,15 @@
};
}
+export function createChangeMessage(id = 'cm_id_1'): ChangeMessage {
+ return {
+ ...createChangeMessageInfo(id),
+ type: '',
+ expanded: false,
+ commentThreads: [],
+ };
+}
+
export function createRevisions(
count: number
): {[revisionId: string]: RevisionInfo} {
@@ -265,7 +280,7 @@
const messageDate = TEST_CHANGE_CREATED;
for (let i = 0; i < count; i++) {
messages.push({
- ...createChangeMessage((i + messageIdStart).toString(16)),
+ ...createChangeMessageInfo((i + messageIdStart).toString(16)),
date: dateToTimestamp(messageDate),
});
messageDate.setDate(messageDate.getDate() + 1);
@@ -453,6 +468,7 @@
message: 'hello world',
updated: '2018-02-13 22:48:48.018000000' as Timestamp,
unresolved: false,
+ path: 'abc.txt',
};
}
@@ -569,9 +585,26 @@
}
export function createCommentThread(comments: UIComment[]) {
+ if (!comments.length) {
+ throw new Error('comment is required to create a thread');
+ }
comments = comments.map(comment => {
return {...createComment(), ...comment};
});
const threads = createCommentThreads(comments);
- return threads.length > 0 ? threads[0] : {};
+ return threads[0];
+}
+
+export function createRelatedChangeAndCommitInfo(): RelatedChangeAndCommitInfo {
+ return {
+ project: TEST_PROJECT_NAME,
+ commit: createCommitInfoWithRequiredCommit(),
+ };
+}
+
+export function createSubmittedTogetherInfo(): SubmittedTogetherInfo {
+ return {
+ changes: [],
+ non_visible_changes: 0,
+ };
}
diff --git a/polygerrit-ui/app/test/test-utils.ts b/polygerrit-ui/app/test/test-utils.ts
index 25366fe..50f465f 100644
--- a/polygerrit-ui/app/test/test-utils.ts
+++ b/polygerrit-ui/app/test/test-utils.ts
@@ -23,6 +23,7 @@
} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {appContext} from '../services/app-context';
import {RestApiService} from '../services/gr-rest-api/gr-rest-api';
+import {SinonSpy} from 'sinon/pkg/sinon-esm';
export interface MockPromise extends Promise<unknown> {
resolve: (value?: unknown) => void;
@@ -42,10 +43,31 @@
return getComputedStyle(el).display === 'none';
}
-export function query(el: Element | undefined, selectors: string) {
- if (!el) return null;
- const root = el.shadowRoot || el;
- return root.querySelector(selectors);
+export function queryAll<E extends Element = Element>(
+ el: Element | undefined,
+ selector: string
+): NodeListOf<E> {
+ if (!el) assert.fail('element not defined');
+ const root = el.shadowRoot ?? el;
+ return root.querySelectorAll<E>(selector);
+}
+
+export function query<E extends Element = Element>(
+ el: Element | undefined,
+ selector: string
+): E | undefined {
+ if (!el) return undefined;
+ const root = el.shadowRoot ?? el;
+ return root.querySelector<E>(selector) ?? undefined;
+}
+
+export function queryAndAssert<E extends Element = Element>(
+ el: Element | undefined,
+ selector: string
+): E {
+ const found = query<E>(el, selector);
+ if (!found) assert.fail(`selector '${selector}' did not match anything'`);
+ return found;
}
// Some tests/elements can define its own binding. We want to restore bindings
@@ -143,6 +165,11 @@
return sinon.spy(appContext.restApiService, method);
}
+export type SinonSpyMember<F extends (...args: any) => any> = SinonSpy<
+ Parameters<F>,
+ ReturnType<F>
+>;
+
/**
* Forcing an opacity of 0 onto the ironOverlayBackdrop is required, because
* otherwise the backdrop stays around in the DOM for too long waiting for
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 711b11b..0649002 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -76,11 +76,13 @@
export type ParsedJSON = BrandType<unknown, '_parsedJSON'>;
export type PatchSetNum = BrandType<'PARENT' | 'edit' | number, '_patchSet'>;
+export type BasePatchSetNum = BrandType<'PARENT' | number, '_patchSet'>;
+export type PatchSetNumber = BrandType<number, '_patchSet'>;
export const EditPatchSetNum = 'edit' as PatchSetNum;
// TODO(TS): This is not correct, it is better to have a separate ApiPatchSetNum
// without 'parent'.
-export const ParentPatchSetNum = 'PARENT' as PatchSetNum;
+export const ParentPatchSetNum = 'PARENT' as BasePatchSetNum;
export type ChangeId = BrandType<string, '_changeId'>;
export type ChangeMessageId = BrandType<string, '_changeMessageId'>;
@@ -550,7 +552,7 @@
commit_with_footers?: boolean;
push_certificate?: PushCertificateInfo;
description?: string;
- basePatchNum?: PatchSetNum;
+ basePatchNum?: BasePatchSetNum;
}
/**
@@ -746,7 +748,7 @@
export interface AuthInfo {
auth_type: AuthType; // docs incorrectly names it 'type'
use_contributor_agreements?: boolean;
- contributor_agreements?: ContributorAgreementInfo;
+ contributor_agreements?: ContributorAgreementInfo[];
editable_account_fields: EditableAccountField[];
login_url?: string;
login_text?: string;
@@ -1174,6 +1176,7 @@
change_message_id?: string;
commit_id?: string;
context_lines?: ContextLine[];
+ source_content_type?: string;
}
export type PathToCommentsInfoMap = {[path: string]: CommentInfo[]};
@@ -1619,7 +1622,7 @@
*/
export interface PatchRange {
patchNum: PatchSetNum;
- basePatchNum: PatchSetNum;
+ basePatchNum: BasePatchSetNum;
}
/**
@@ -1936,7 +1939,7 @@
*/
export interface EditInfo {
commit: CommitInfo;
- base_patch_set_number: PatchSetNum;
+ base_patch_set_number: BasePatchSetNum;
base_revision: string;
ref: GitRef;
fetch?: ProtocolToFetchInfoMap;
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index 5965453..d389533 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -20,6 +20,7 @@
import {FetchRequest} from './types';
import {MovedLinkClickedEventDetail} from '../api/diff';
import {Category, RunStatus} from '../api/checks';
+import {ChangeMessage} from '../elements/change/gr-message/gr-message';
export interface TitleChangeEventDetail {
title: string;
@@ -33,6 +34,32 @@
}
}
+export interface ChangeMessageDeletedEventDetail {
+ message: ChangeMessage;
+}
+
+export type ChangeMessageDeletedEvent = CustomEvent<
+ ChangeMessageDeletedEventDetail
+>;
+
+declare global {
+ interface HTMLElementEventMap {
+ 'change-message-deleted': ChangeMessageDeletedEvent;
+ }
+}
+
+export interface ReplyEventDetail {
+ message: ChangeMessage;
+}
+
+export type ReplyEvent = CustomEvent<ReplyEventDetail>;
+
+declare global {
+ interface HTMLElementEventMap {
+ reply: ReplyEvent;
+ }
+}
+
export interface PageErrorEventDetail {
response: Response;
}
diff --git a/polygerrit-ui/app/types/types.ts b/polygerrit-ui/app/types/types.ts
index be80411..e3e1ada 100644
--- a/polygerrit-ui/app/types/types.ts
+++ b/polygerrit-ui/app/types/types.ts
@@ -21,6 +21,7 @@
import {PaperInputElement} from '@polymer/paper-input/paper-input';
import {
AccountInfo,
+ BasePatchSetNum,
ChangeId,
ChangeViewChangeInfo,
CommitId,
@@ -237,7 +238,7 @@
export interface EditRevisionInfo extends Partial<RevisionInfo> {
// EditRevisionInfo has less required properties then RevisionInfo
_number: PatchSetNum;
- basePatchNum: PatchSetNum;
+ basePatchNum: BasePatchSetNum;
commit: CommitInfo;
}
diff --git a/polygerrit-ui/app/utils/change-util.ts b/polygerrit-ui/app/utils/change-util.ts
index a7f8b49..018d70d 100644
--- a/polygerrit-ui/app/utils/change-util.ts
+++ b/polygerrit-ui/app/utils/change-util.ts
@@ -213,7 +213,7 @@
return owner || uploader || reviewer || cc;
}
-export function getCurrentRevision(change?: ChangeInfo) {
+export function getCurrentRevision(change?: ChangeInfo | ParsedChangeInfo) {
if (!change?.revisions || !change?.current_revision) return undefined;
return change.revisions[change.current_revision];
}
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index de12a2a..110832d 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -25,6 +25,7 @@
PatchRange,
ParentPatchSetNum,
ContextLine,
+ BasePatchSetNum,
} from '../types/common';
import {CommentSide, Side} from '../constants/constants';
import {parseDate} from './date-util';
@@ -283,12 +284,16 @@
} else {
return {
patchNum: latestPatchNum,
- basePatchNum: comment.patch_set,
+ basePatchNum: comment.patch_set as BasePatchSetNum,
};
}
}
-export function computeDiffFromContext(context: ContextLine[], path: string) {
+export function computeDiffFromContext(
+ context: ContextLine[],
+ path: string,
+ content_type?: string
+) {
// do not render more than 20 lines of context
context = context.slice(0, 20);
const diff: DiffInfo = {
@@ -300,7 +305,7 @@
},
meta_b: {
name: path,
- content_type: '',
+ content_type: content_type || '',
lines: context.length + context?.[0].line_number,
web_links: [],
},
diff --git a/polygerrit-ui/app/utils/comment-util_test.js b/polygerrit-ui/app/utils/comment-util_test.js
deleted file mode 100644
index 4ce95ef..0000000
--- a/polygerrit-ui/app/utils/comment-util_test.js
+++ /dev/null
@@ -1,206 +0,0 @@
-/**
- * @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.
- */
-
-import '../test/common-test-setup-karma.js';
-import {
- isUnresolved, getPatchRangeForCommentUrl, createCommentThreads, sortComments,
-} from './comment-util.js';
-import {createComment} from '../test/test-data-generators.js';
-import {CommentSide, Side} from '../constants/constants.js';
-import {ParentPatchSetNum} from '../types/common.js';
-
-suite('comment-util', () => {
- test('isUnresolved', () => {
- assert.isFalse(isUnresolved(undefined));
- assert.isFalse(isUnresolved({comments: []}));
- assert.isTrue(isUnresolved({comments: [{unresolved: true}]}));
- assert.isFalse(isUnresolved({comments: [{unresolved: false}]}));
- assert.isTrue(isUnresolved(
- {comments: [{unresolved: false}, {unresolved: true}]}));
- assert.isFalse(isUnresolved(
- {comments: [{unresolved: true}, {unresolved: false}]}));
- });
-
- test('getPatchRangeForCommentUrl', () => {
- test('comment created with side=PARENT does not navigate to latest ps',
- () => {
- const comment = {
- ...createComment(),
- id: 'c4',
- line: 10,
- patch_set: 4,
- side: CommentSide.PARENT,
- path: '/COMMIT_MSG',
- };
- assert.deepEqual(getPatchRangeForCommentUrl(comment, 11), {
- basePatchNum: ParentPatchSetNum,
- patchNum: 4,
- });
- });
- });
-
- test('comments sorting', () => {
- const comments = [
- {
- id: 'new_draft',
- message: 'i do not like either of you',
- diffSide: Side.LEFT,
- __draft: true,
- updated: '2015-12-20 15:01:20.396000000',
- },
- {
- id: 'sallys_confession',
- message: 'i like you, jack',
- updated: '2015-12-23 15:00:20.396000000',
- line: 1,
- diffSide: Side.LEFT,
- }, {
- id: 'jacks_reply',
- message: 'i like you, too',
- updated: '2015-12-24 15:01:20.396000000',
- diffSide: Side.LEFT,
- line: 1,
- in_reply_to: 'sallys_confession',
- },
- ];
- const sortedComments = sortComments(comments);
- assert.equal(sortedComments[0], comments[1]);
- assert.equal(sortedComments[1], comments[2]);
- assert.equal(sortedComments[2], comments[0]);
- });
-
- suite('createCommentThreads', () => {
- test('creates threads from individual comments', () => {
- const comments = [
- {
- id: 'sallys_confession',
- message: 'i like you, jack',
- updated: '2015-12-23 15:00:20.396000000',
- line: 1,
- patch_set: 1,
- path: 'some/path',
- }, {
- id: 'jacks_reply',
- message: 'i like you, too',
- updated: '2015-12-24 15:01:20.396000000',
- line: 1,
- in_reply_to: 'sallys_confession',
- patch_set: 1,
- path: 'some/path',
- },
- {
- id: 'new_draft',
- message: 'i do not like either of you',
- __draft: true,
- updated: '2015-12-20 15:01:20.396000000',
- patch_set: 1,
- path: 'some/path',
- },
- ];
-
- const actualThreads = createCommentThreads(comments,
- {basePatchNum: 1, patchNum: 4});
-
- assert.equal(actualThreads.length, 2);
-
- assert.equal(actualThreads[0].diffSide, Side.LEFT);
- assert.equal(actualThreads[0].comments.length, 2);
- assert.deepEqual(actualThreads[0].comments[0], comments[0]);
- assert.deepEqual(actualThreads[0].comments[1], comments[1]);
- assert.equal(actualThreads[0].patchNum, 1);
- assert.equal(actualThreads[0].line, 1);
-
- assert.equal(actualThreads[1].diffSide, Side.LEFT);
- assert.equal(actualThreads[1].comments.length, 1);
- assert.deepEqual(actualThreads[1].comments[0], comments[2]);
- assert.equal(actualThreads[1].patchNum, 1);
- assert.equal(actualThreads[1].line, 'FILE');
- });
-
- test('derives patchNum and range', () => {
- const comments = [{
- id: 'betsys_confession',
- message: 'i like you, jack',
- updated: '2015-12-24 15:00:10.396000000',
- range: {
- start_line: 1,
- start_character: 1,
- end_line: 1,
- end_character: 2,
- },
- patch_set: 5,
- path: '/p',
- line: 1,
- }];
-
- const expectedThreads = [
- {
- diffSide: Side.LEFT,
- commentSide: CommentSide.REVISION,
- path: '/p',
- rootId: 'betsys_confession',
- mergeParentNum: undefined,
- comments: [{
- id: 'betsys_confession',
- path: '/p',
- message: 'i like you, jack',
- updated: '2015-12-24 15:00:10.396000000',
- range: {
- start_line: 1,
- start_character: 1,
- end_line: 1,
- end_character: 2,
- },
- patch_set: 5,
- line: 1,
- }],
- patchNum: 5,
- range: {
- start_line: 1,
- start_character: 1,
- end_line: 1,
- end_character: 2,
- },
- line: 1,
- },
- ];
-
- assert.deepEqual(
- createCommentThreads(comments, {basePatchNum: 5, patchNum: 10}),
- expectedThreads);
- });
-
- test('does not thread unrelated comments at same location', () => {
- const comments = [
- {
- id: 'sallys_confession',
- message: 'i like you, jack',
- updated: '2015-12-23 15:00:20.396000000',
- diffSide: Side.LEFT,
- path: '/p',
- }, {
- id: 'jacks_reply',
- message: 'i like you, too',
- updated: '2015-12-24 15:01:20.396000000',
- diffSide: Side.LEFT,
- path: '/p',
- },
- ];
- assert.equal(createCommentThreads(comments).length, 2);
- });
- });
-});
diff --git a/polygerrit-ui/app/utils/comment-util_test.ts b/polygerrit-ui/app/utils/comment-util_test.ts
new file mode 100644
index 0000000..29eb5b7
--- /dev/null
+++ b/polygerrit-ui/app/utils/comment-util_test.ts
@@ -0,0 +1,257 @@
+/**
+ * @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.
+ */
+
+import '../test/common-test-setup-karma.js';
+import {
+ isUnresolved,
+ getPatchRangeForCommentUrl,
+ createCommentThreads,
+ sortComments,
+} from './comment-util.js';
+import {
+ createComment,
+ createCommentThread,
+} from '../test/test-data-generators.js';
+import {CommentSide, Side} from '../constants/constants.js';
+import {
+ BasePatchSetNum,
+ ParentPatchSetNum,
+ PatchSetNum,
+ Timestamp,
+ UrlEncodedCommentId,
+} from '../types/common.js';
+
+suite('comment-util', () => {
+ test('isUnresolved', () => {
+ const thread = createCommentThread([createComment()]);
+
+ assert.isFalse(isUnresolved(undefined));
+ assert.isFalse(isUnresolved(thread));
+
+ assert.isTrue(
+ isUnresolved({
+ ...thread,
+ comments: [{...createComment(), unresolved: true}],
+ })
+ );
+ assert.isFalse(
+ isUnresolved({
+ ...thread,
+ comments: [{...createComment(), unresolved: false}],
+ })
+ );
+ assert.isTrue(
+ isUnresolved({
+ ...thread,
+ comments: [
+ {...createComment(), unresolved: false},
+ {...createComment(), unresolved: true},
+ ],
+ })
+ );
+ assert.isFalse(
+ isUnresolved({
+ ...thread,
+ comments: [
+ {...createComment(), unresolved: true},
+ {...createComment(), unresolved: false},
+ ],
+ })
+ );
+ });
+
+ test('getPatchRangeForCommentUrl', () => {
+ test('comment created with side=PARENT does not navigate to latest ps', () => {
+ const comment = {
+ ...createComment(),
+ id: 'c4' as UrlEncodedCommentId,
+ line: 10,
+ patch_set: 4 as PatchSetNum,
+ side: CommentSide.PARENT,
+ path: '/COMMIT_MSG',
+ };
+ assert.deepEqual(getPatchRangeForCommentUrl(comment, 11 as PatchSetNum), {
+ basePatchNum: ParentPatchSetNum,
+ patchNum: 4 as PatchSetNum,
+ });
+ });
+ });
+
+ test('comments sorting', () => {
+ const comments = [
+ {
+ id: 'new_draft' as UrlEncodedCommentId,
+ message: 'i do not like either of you',
+ diffSide: Side.LEFT,
+ __draft: true,
+ updated: '2015-12-20 15:01:20.396000000' as Timestamp,
+ },
+ {
+ id: 'sallys_confession' as UrlEncodedCommentId,
+ message: 'i like you, jack',
+ updated: '2015-12-23 15:00:20.396000000' as Timestamp,
+ line: 1,
+ diffSide: Side.LEFT,
+ },
+ {
+ id: 'jacks_reply' as UrlEncodedCommentId,
+ message: 'i like you, too',
+ updated: '2015-12-24 15:01:20.396000000' as Timestamp,
+ diffSide: Side.LEFT,
+ line: 1,
+ in_reply_to: 'sallys_confession',
+ },
+ ];
+ const sortedComments = sortComments(comments);
+ assert.equal(sortedComments[0], comments[1]);
+ assert.equal(sortedComments[1], comments[2]);
+ assert.equal(sortedComments[2], comments[0]);
+ });
+
+ suite('createCommentThreads', () => {
+ test('creates threads from individual comments', () => {
+ const comments = [
+ {
+ id: 'sallys_confession' as UrlEncodedCommentId,
+ message: 'i like you, jack',
+ updated: '2015-12-23 15:00:20.396000000' as Timestamp,
+ line: 1,
+ patch_set: 1 as PatchSetNum,
+ path: 'some/path',
+ },
+ {
+ id: 'jacks_reply' as UrlEncodedCommentId,
+ message: 'i like you, too',
+ updated: '2015-12-24 15:01:20.396000000' as Timestamp,
+ line: 1,
+ in_reply_to: 'sallys_confession' as UrlEncodedCommentId,
+ patch_set: 1 as PatchSetNum,
+ path: 'some/path',
+ },
+ {
+ id: 'new_draft' as UrlEncodedCommentId,
+ message: 'i do not like either of you' as UrlEncodedCommentId,
+ __draft: true,
+ updated: '2015-12-20 15:01:20.396000000' as Timestamp,
+ patch_set: 1 as PatchSetNum,
+ path: 'some/path',
+ },
+ ];
+
+ const actualThreads = createCommentThreads(comments, {
+ basePatchNum: 1 as BasePatchSetNum,
+ patchNum: 4 as PatchSetNum,
+ });
+
+ assert.equal(actualThreads.length, 2);
+
+ assert.equal(actualThreads[0].diffSide, Side.LEFT);
+ assert.equal(actualThreads[0].comments.length, 2);
+ assert.deepEqual(actualThreads[0].comments[0], comments[0]);
+ assert.deepEqual(actualThreads[0].comments[1], comments[1]);
+ assert.equal(actualThreads[0].patchNum, 1 as PatchSetNum);
+ assert.equal(actualThreads[0].line, 1);
+
+ assert.equal(actualThreads[1].diffSide, Side.LEFT);
+ assert.equal(actualThreads[1].comments.length, 1);
+ assert.deepEqual(actualThreads[1].comments[0], comments[2]);
+ assert.equal(actualThreads[1].patchNum, 1 as PatchSetNum);
+ assert.equal(actualThreads[1].line, 'FILE');
+ });
+
+ test('derives patchNum and range', () => {
+ const comments = [
+ {
+ id: 'betsys_confession' as UrlEncodedCommentId,
+ message: 'i like you, jack',
+ updated: '2015-12-24 15:00:10.396000000' as Timestamp,
+ range: {
+ start_line: 1,
+ start_character: 1,
+ end_line: 1,
+ end_character: 2,
+ },
+ patch_set: 5 as PatchSetNum,
+ path: '/p',
+ line: 1,
+ },
+ ];
+
+ const expectedThreads = [
+ {
+ diffSide: Side.LEFT,
+ commentSide: CommentSide.REVISION,
+ path: '/p',
+ rootId: 'betsys_confession' as UrlEncodedCommentId,
+ mergeParentNum: undefined,
+ comments: [
+ {
+ id: 'betsys_confession' as UrlEncodedCommentId,
+ path: '/p',
+ message: 'i like you, jack',
+ updated: '2015-12-24 15:00:10.396000000' as Timestamp,
+ range: {
+ start_line: 1,
+ start_character: 1,
+ end_line: 1,
+ end_character: 2,
+ },
+ patch_set: 5 as PatchSetNum,
+ line: 1,
+ },
+ ],
+ patchNum: 5 as PatchSetNum,
+ range: {
+ start_line: 1,
+ start_character: 1,
+ end_line: 1,
+ end_character: 2,
+ },
+ line: 1,
+ },
+ ];
+
+ assert.deepEqual(
+ createCommentThreads(comments, {
+ basePatchNum: 5 as BasePatchSetNum,
+ patchNum: 10 as PatchSetNum,
+ }),
+ expectedThreads
+ );
+ });
+
+ test('does not thread unrelated comments at same location', () => {
+ const comments = [
+ {
+ id: 'sallys_confession' as UrlEncodedCommentId,
+ message: 'i like you, jack',
+ updated: '2015-12-23 15:00:20.396000000' as Timestamp,
+ diffSide: Side.LEFT,
+ path: '/p',
+ },
+ {
+ id: 'jacks_reply' as UrlEncodedCommentId,
+ message: 'i like you, too',
+ updated: '2015-12-24 15:01:20.396000000' as Timestamp,
+ diffSide: Side.LEFT,
+ path: '/p',
+ },
+ ];
+ assert.equal(createCommentThreads(comments).length, 2);
+ });
+ });
+});
diff --git a/polygerrit-ui/app/utils/common-util.ts b/polygerrit-ui/app/utils/common-util.ts
index f4d6d51..08b5e49 100644
--- a/polygerrit-ui/app/utils/common-util.ts
+++ b/polygerrit-ui/app/utils/common-util.ts
@@ -122,3 +122,7 @@
set.add(value);
}
}
+
+export function unique<T>(item: T, index: number, array: T[]) {
+ return array.indexOf(item) === index;
+}
diff --git a/polygerrit-ui/app/utils/event-util.ts b/polygerrit-ui/app/utils/event-util.ts
index cf590e0..46b1b49 100644
--- a/polygerrit-ui/app/utils/event-util.ts
+++ b/polygerrit-ui/app/utils/event-util.ts
@@ -132,3 +132,15 @@
})
);
}
+
+export function waitForEventOnce<K extends keyof HTMLElementEventMap>(
+ el: EventTarget,
+ eventName: K
+): Promise<HTMLElementEventMap[K]> {
+ return new Promise<HTMLElementEventMap[K]>(resolve => {
+ const callback = (event: HTMLElementEventMap[K]) => {
+ resolve(event);
+ };
+ el.addEventListener(eventName, callback as EventListener, {once: true});
+ });
+}
diff --git a/polygerrit-ui/app/utils/patch-set-util.ts b/polygerrit-ui/app/utils/patch-set-util.ts
index 40e3eef..a0946a3 100644
--- a/polygerrit-ui/app/utils/patch-set-util.ts
+++ b/polygerrit-ui/app/utils/patch-set-util.ts
@@ -3,11 +3,13 @@
ChangeInfo,
PatchSetNum,
EditPatchSetNum,
- BrandType,
ParentPatchSetNum,
+ PatchSetNumber,
+ BasePatchSetNum,
} from '../types/common';
import {RestApiService} from '../services/gr-rest-api/gr-rest-api';
import {EditRevisionInfo, ParsedChangeInfo} from '../types/types';
+import {check} from './common-util';
/**
* @license
@@ -46,7 +48,7 @@
interface PatchRange {
patchNum?: PatchSetNum;
- basePatchNum?: PatchSetNum;
+ basePatchNum?: BasePatchSetNum;
}
/**
@@ -82,9 +84,7 @@
return patchset as PatchSetNum;
}
-export function isNumber(
- psn: PatchSetNum
-): psn is BrandType<number, '_patchSet'> {
+export function isNumber(psn: PatchSetNum): psn is PatchSetNumber {
return typeof psn === 'number';
}
@@ -250,19 +250,21 @@
export function computeLatestPatchNum(
allPatchSets?: PatchSet[]
-): PatchSetNum | undefined {
+): PatchSetNumber | undefined {
if (!allPatchSets || !allPatchSets.length) {
return undefined;
}
- if (allPatchSets[0].num === EditPatchSetNum) {
- return allPatchSets[1].num;
+ let latest = allPatchSets[0].num;
+ if (latest === EditPatchSetNum) {
+ latest = allPatchSets[1].num;
}
- return allPatchSets[0].num;
+ check(isNumber(latest), 'Latest patchset cannot be EDIT or PARENT.');
+ return latest;
}
export function computePredecessor(
patchset?: PatchSetNum
-): PatchSetNum | undefined {
+): BasePatchSetNum | undefined {
if (
!patchset ||
patchset === ParentPatchSetNum ||
@@ -271,7 +273,7 @@
return undefined;
}
if (patchset === 1) return ParentPatchSetNum;
- return (Number(patchset) - 1) as PatchSetNum;
+ return (Number(patchset) - 1) as BasePatchSetNum;
}
export function hasEditBasedOnCurrentPatchSet(allPatchSets: PatchSet[]) {
diff --git a/polygerrit-ui/app/utils/string-util.ts b/polygerrit-ui/app/utils/string-util.ts
index 1b400cf..6aae67f 100644
--- a/polygerrit-ui/app/utils/string-util.ts
+++ b/polygerrit-ui/app/utils/string-util.ts
@@ -26,3 +26,7 @@
export function addQuotesWhen(string: string, cond: boolean): string {
return cond ? `"${string}"` : string;
}
+
+export function charsOnly(s: string): string {
+ return s.replace(/[^a-zA-Z]+/g, '');
+}
diff --git a/polygerrit-ui/app/utils/url-util.ts b/polygerrit-ui/app/utils/url-util.ts
index f977ab6..4115062 100644
--- a/polygerrit-ui/app/utils/url-util.ts
+++ b/polygerrit-ui/app/utils/url-util.ts
@@ -78,3 +78,33 @@
const withoutPlus = url.replace(/\+/g, '%20');
return decodeURIComponent(withoutPlus);
}
+
+/**
+ * @param path URL path including search params, but without host
+ */
+export function toPathname(path: string) {
+ const i = path.indexOf('?');
+ const hasQuery = i > -1;
+ const pathname = hasQuery ? path.slice(0, i) : path;
+ return pathname;
+}
+
+/**
+ * @param path URL path including search params, but without host
+ */
+export function toSearchParams(path: string) {
+ const i = path.indexOf('?');
+ const hasQuery = i > -1;
+ const querystring = hasQuery ? path.slice(i + 1) : '';
+ return new URLSearchParams(querystring);
+}
+
+/**
+ * @param pathname URL path without search params
+ * @param params
+ */
+export function toPath(pathname: string, searchParams: URLSearchParams) {
+ const paramString = searchParams.toString();
+ const middle = paramString ? '?' : '';
+ return pathname + middle + paramString;
+}
diff --git a/polygerrit-ui/app/utils/url-util_test.js b/polygerrit-ui/app/utils/url-util_test.js
index b1b17f4..5cd4bb4 100644
--- a/polygerrit-ui/app/utils/url-util_test.js
+++ b/polygerrit-ui/app/utils/url-util_test.js
@@ -20,7 +20,11 @@
getBaseUrl,
getDocsBaseUrl,
_testOnly_clearDocsBaseUrlCache,
- encodeURL, singleDecodeURL,
+ encodeURL,
+ singleDecodeURL,
+ toPath,
+ toPathname,
+ toSearchParams,
} from './url-util.js';
suite('url-util tests', () => {
@@ -124,4 +128,23 @@
});
});
});
+
+ test('toPathname', () => {
+ assert.equal(toPathname('asdf'), 'asdf');
+ assert.equal(toPathname('asdf?qwer=zxcv'), 'asdf');
+ });
+
+ test('toSearchParams', () => {
+ assert.equal(toSearchParams('asdf').toString(), '');
+ assert.equal(toSearchParams('asdf?qwer=zxcv').get('qwer'), 'zxcv');
+ });
+
+ test('toPathname', () => {
+ const params = new URLSearchParams();
+ assert.equal(toPath('asdf', params), 'asdf');
+ params.set('qwer', 'zxcv');
+ assert.equal(toPath('asdf', params), 'asdf?qwer=zxcv');
+ assert.equal(toPath(toPathname('asdf?qwer=zxcv'),
+ toSearchParams('asdf?qwer=zxcv')), 'asdf?qwer=zxcv');
+ });
});
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index ec3b7a0..f94f5ad 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -310,6 +310,14 @@
"@polymer/paper-styles" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0"
+"@polymer/paper-tooltip@^3.0.1":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@polymer/paper-tooltip/-/paper-tooltip-3.0.1.tgz#cdbb06442737513f081437c6302842170ce714dc"
+ integrity sha512-yiUk09opTEnE1lK+tb501ENb+yQBi4p++Ep0eGJAHesVYKVMPNgPphVKkIizkDaU+n0SE+zXfTsRbYyOMDYXSg==
+ dependencies:
+ "@polymer/paper-styles" "^3.0.0-pre.26"
+ "@polymer/polymer" "^3.0.0"
+
"@polymer/polymer@^3.0.0", "@polymer/polymer@^3.0.5", "@polymer/polymer@^3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.4.1.tgz#333bef25711f8411bb5624fb3eba8212ef8bee96"
diff --git a/proto/cache.proto b/proto/cache.proto
index 4fd037d..874e60a 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -524,13 +524,14 @@
}
// Serialized form of a list of com.google.gerrit.extensions.common.ContextLineInfo
-// Next ID: 2
+// Next ID: 3
message AllCommentContextProto {
message CommentContextProto {
int32 line_number = 1;
string context_line = 2;
}
repeated CommentContextProto context = 1;
+ string content_type = 2;
}
// Serialized key for
@@ -617,7 +618,7 @@
// Serialized form of
// com.google.gerrit.server.patch.filediff.FileDiffOutput
-// Next ID: 9
+// Next ID: 12
message FileDiffOutputProto {
// Next ID: 5
message Edit {
@@ -632,6 +633,11 @@
Edit edit = 1;
bool due_to_rebase = 2;
}
+ // Next ID: 3
+ message ComparisonType {
+ int32 parent_num = 1;
+ bool auto_merge = 2;
+ }
string old_path = 1;
string new_path = 2;
string change_type = 3;
@@ -640,4 +646,7 @@
int64 size = 6;
int64 size_delta = 7;
repeated TaggedEdit edits = 8;
+ bytes old_commit = 9;
+ bytes new_commit = 10;
+ ComparisonType comparison_type = 11;
}
diff --git a/tools/bzl/maven_jar.bzl b/tools/bzl/maven_jar.bzl
index 15b1797..d96ffc2 100644
--- a/tools/bzl/maven_jar.bzl
+++ b/tools/bzl/maven_jar.bzl
@@ -60,7 +60,7 @@
if len(parts) == 3:
group_id, artifact_id, version = parts
elif len(parts) == 4:
- group_id, artifact_id, version, packaging = parts
+ group_id, artifact_id, version, classifier = parts
elif len(parts) == 5:
group_id, artifact_id, version, packaging, classifier = parts
else:
@@ -158,7 +158,10 @@
srcjar = None
if ctx.attr.src_sha1 or ctx.attr.attach_source:
srcjar = jar + "-src.jar"
- srcurl = url + "-sources.jar"
+ srcurl = url
+ if coordinates.classifier != None:
+ srcurl = url.replace("-" + coordinates.classifier, "")
+ srcurl += "-sources.jar"
srcjar_path = ctx.path("jar/" + srcjar)
args = [python, script, "-o", srcjar_path, "-u", srcurl]
if ctx.attr.src_sha1:
diff --git a/tools/node_tools/node_modules_licenses/utils.ts b/tools/node_tools/node_modules_licenses/utils.ts
index 5f8e7b3..e73bf96 100644
--- a/tools/node_tools/node_modules_licenses/utils.ts
+++ b/tools/node_tools/node_modules_licenses/utils.ts
@@ -22,6 +22,15 @@
process.exit(1);
}
+// Bazel params may be surrounded with quotes
+function removeSurrondedQuotes(str: string): string {
+ return str.startsWith("'") && str.endsWith("'") ?
+ str.slice(1, -1) : str;
+}
+
export function readMultilineParamFile(path: string): string[] {
- return fs.readFileSync(path, {encoding: 'utf-8'}).split(/\r?\n/).filter(f => f.length > 0);
+ return fs.readFileSync(path, {encoding: 'utf-8'})
+ .split(/\r?\n/)
+ .filter(f => f.length > 0)
+ .map(removeSurrondedQuotes);
}
diff --git a/tools/remote-bazelrc b/tools/remote-bazelrc
index 22ee330..bbfe3a8 100644
--- a/tools/remote-bazelrc
+++ b/tools/remote-bazelrc
@@ -25,7 +25,7 @@
# this higher can make builds faster by allowing more jobs to run in parallel.
# Setting it too high can result in jobs that timeout, however, while waiting
# for a remote machine to execute them.
-build:remote --jobs=100
+build:remote --jobs=200
build:remote --disk_cache=
# Set several flags related to specifying the platform, toolchain and java