Merge changes I2a63f725,I582c12ea,I476c6371,I886e4fcf
* changes:
Store submit requirements for merged changes in NoteDb
Enable parsing submit requirements results from change meta commits
Add Gson type adapter to submit requirement entities
Submit requirements: refactor SubmitRequirementResult
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 61565f8..c3237ed 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -365,6 +365,13 @@
bazel test --test_tag_filters=api,git //...
----
+To run the tests against a specific index backend (LUCENE, FAKE):
+----
+ bazel test --test_env=GERRIT_INDEX_TYPE=LUCENE //...
+----
+
+Elastic search is not currently supported in integration tests.
+
The following values are currently supported for the group name:
* annotation
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-change-info-cannot-merge.png b/Documentation/images/gwt-user-review-ui-change-screen-change-info-cannot-merge.png
deleted file mode 100644
index 69a28ec..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-change-info-cannot-merge.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-change-info.png b/Documentation/images/gwt-user-review-ui-change-screen-change-info.png
deleted file mode 100644
index e92b49d..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-change-info.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-change-update.png b/Documentation/images/gwt-user-review-ui-change-screen-change-update.png
deleted file mode 100644
index 227db40..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-change-update.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-commit-info-merge-commit.png b/Documentation/images/gwt-user-review-ui-change-screen-commit-info-merge-commit.png
deleted file mode 100644
index 097637e..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-commit-info-merge-commit.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-commit-info.png b/Documentation/images/gwt-user-review-ui-change-screen-commit-info.png
deleted file mode 100644
index fe0c1d1..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-commit-info.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-included-in-list.png b/Documentation/images/gwt-user-review-ui-change-screen-included-in-list.png
deleted file mode 100644
index ad30fe2..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-included-in-list.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-not-current.png b/Documentation/images/gwt-user-review-ui-change-screen-not-current.png
deleted file mode 100644
index 9a87c67..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-not-current.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-permalink.png b/Documentation/images/gwt-user-review-ui-change-screen-permalink.png
deleted file mode 100644
index a1aede9..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-permalink.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-plugin-extensions.png b/Documentation/images/gwt-user-review-ui-change-screen-plugin-extensions.png
deleted file mode 100644
index 120b99c..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-plugin-extensions.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-comment.png b/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-comment.png
deleted file mode 100644
index 2ecc47e..0000000
--- a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-comment.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-commented.png b/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-commented.png
deleted file mode 100644
index 598d18d..0000000
--- a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-commented.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-keyboard-shortcuts.png b/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-keyboard-shortcuts.png
deleted file mode 100644
index 6f63f0e4..0000000
--- a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-keyboard-shortcuts.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-project-and-file.png b/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-project-and-file.png
deleted file mode 100644
index 836964b..0000000
--- a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-project-and-file.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen.png b/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen.png
deleted file mode 100644
index d76ecef..0000000
--- a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/user-review-ui-change-screen-change-update.png b/Documentation/images/user-review-ui-change-screen-change-update.png
new file mode 100644
index 0000000..fe07ef9
--- /dev/null
+++ b/Documentation/images/user-review-ui-change-screen-change-update.png
Binary files differ
diff --git a/Documentation/images/user-review-ui-change-screen-plugin-extensions.png b/Documentation/images/user-review-ui-change-screen-plugin-extensions.png
new file mode 100644
index 0000000..5d6fee7
--- /dev/null
+++ b/Documentation/images/user-review-ui-change-screen-plugin-extensions.png
Binary files differ
diff --git a/Documentation/images/user-review-ui-side-by-side-diff-screen.png b/Documentation/images/user-review-ui-side-by-side-diff-screen.png
new file mode 100644
index 0000000..74d02e3
--- /dev/null
+++ b/Documentation/images/user-review-ui-side-by-side-diff-screen.png
Binary files differ
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt
index 0d8da89..813ff44 100644
--- a/Documentation/js_licenses.txt
+++ b/Documentation/js_licenses.txt
@@ -1077,6 +1077,38 @@
----
+[[immer]]
+immer
+
+* immer
+
+[[immer_license]]
+----
+MIT License
+
+Copyright (c) 2017 Michel Weststrate
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----
+
+
[[isarray]]
isarray
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index ed1a336..11f9ff3 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -4036,6 +4036,38 @@
----
+[[immer]]
+immer
+
+* immer
+
+[[immer_license]]
+----
+MIT License
+
+Copyright (c) 2017 Michel Weststrate
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----
+
+
[[isarray]]
isarray
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index d4fe6b5..6f5f729 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -218,7 +218,7 @@
** [[plugin-actions]]Further actions may be available if plugins are installed.
+
-image::images/user-review-ui-change-screen-change-info-actions.png[width=600, link="images/user-review-ui-change-screen-change-info-actions.png"]
+image::images/user-review-ui-change-screen-change-info-actions.png[width=400, link="images/user-review-ui-change-screen-change-info-actions.png"]
- [[labels]]Labels & Votes:
+
@@ -259,7 +259,7 @@
set is currently viewed can be seen from the `Patch Sets` drop-down
panel in the change header.
-image::images/user-review-ui-change-screen-patch-sets.png[width=487, link="images/user-review-ui-change-screen-patch-sets.png"]
+image::images/user-review-ui-change-screen-patch-sets.png[width=300, link="images/user-review-ui-change-screen-patch-sets.png"]
[[download]]
@@ -458,7 +458,7 @@
comments; a summary comment is only added if the reply popup panel is
open when the quick approve button is clicked.
-image::images/user-review-ui-change-screen-quick-approve.png[width=800, link="images/gwt-user-review-ui-change-screen-quick-approve.png"]
+image::images/user-review-ui-change-screen-quick-approve.png[width=800, link="images/user-review-ui-change-screen-quick-approve.png"]
[[history]]
=== History
@@ -480,16 +480,16 @@
it is 30 seconds. Polling may also be completely disabled by the
administrator.
-image::images/gwt-user-review-ui-change-screen-change-update.png[width=800, link="images/gwt-user-review-ui-change-screen-change-update.png"]
+image::images/user-review-ui-change-screen-change-update.png[width=400, link="images/user-review-ui-change-screen-change-update.png"]
[[plugin-extensions]]
=== Plugin Extensions
-Gerrit plugins may extend the change screen; they can add buttons for
-additional actions to the change info block and display arbitrary UI
-controls below the change info block.
+Gerrit plugins may extend the change screen. Java plugins in the
+backend can add additional actions to the triple-dot menu block.
+Frontend plugins can change the UI controls in arbitrary ways.
-image::images/gwt-user-review-ui-change-screen-plugin-extensions.png[width=800, link="images/gwt-user-review-ui-change-screen-plugin-extensions.png"]
+image::images/user-review-ui-change-screen-plugin-extensions.png[width=300, link="images/user-review-ui-change-screen-plugin-extensions.png"]
[[side-by-side]]
== Side-by-Side Diff Screen
@@ -500,17 +500,8 @@
This screen allows to review a patch and to comment on it.
-image::images/gwt-user-review-ui-side-by-side-diff-screen.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen.png"]
+image::images/user-review-ui-side-by-side-diff-screen.png[width=800, link="images/user-review-ui-side-by-side-diff-screen.png"]
-[[side-by-side-header]]
-In the screen header the project name and the name of the viewed patch
-file are shown.
-
-If a Git web browser is configured on the server, the project name and
-the file path are displayed as links to the project and the folder in
-the Git web browser.
-
-image::images/gwt-user-review-ui-side-by-side-diff-screen-project-and-file.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-project-and-file.png"]
[[side-by-side-mark-reviewed]]
The checkbox in front of the file name allows the
@@ -518,7 +509,7 @@
diff preference allows to control whether the files should be
automatically marked as reviewed when they are viewed.
-image::images/user-review-ui-side-by-side-diff-screen-reviewed.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-reviewed.png"]
+image::images/user-review-ui-side-by-side-diff-screen-reviewed.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-reviewed.png"]
[[patch-set-selection]]
In the header, on each side, the list of patch sets is shown. Clicking
@@ -615,13 +606,9 @@
For typing the new comment, a new comment box is shown under the code
that is commented.
-Clicking on the `Save` button saves the new comment as a draft. To make
-it visible to other users it must be published from the change screen
-by link:#reply[replying] to the change.
-
-Clicking on the `Discard` button deletes the new comment.
-
-image::images/gwt-user-review-ui-side-by-side-diff-screen-commented.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-commented.png"]
+Clicking on the `Save` button saves the new comment as a draft. To
+make it visible to other users it must be published from the change
+screen by link:#reply[replying] to the change.
[[file-level-comments]]
=== File Level Comments
@@ -629,7 +616,7 @@
File level comments are added by clicking the 'File' header at the top
of the file.
-image::images/user-review-ui-side-by-side-diff-screen-file-level-comment.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-file-level-comment.png"]
+image::images/user-review-ui-side-by-side-diff-screen-file-level-comment.png[width=400, link="images/user-review-ui-side-by-side-diff-screen-file-level-comment.png"]
[[diff-preferences]]
=== Diff Preferences
@@ -639,7 +626,7 @@
preferences. The diff preferences can be accessed by clicking on the
settings icon in the screen header.
-image::images/user-review-ui-side-by-side-diff-screen-preferences.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-preferences.png"]
+image::images/user-review-ui-side-by-side-diff-screen-preferences.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-preferences.png"]
The following diff preferences can be configured:
@@ -686,7 +673,7 @@
If many lines are skipped there are additional links to expand the
context by ten lines before and after the skipped block.
+
-image::images/user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png"]
+image::images/user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png"]
- [[syntax-highlighting]]`Syntax Highlighting`:
+
@@ -716,7 +703,7 @@
a popup that shows a list of available keyboard shortcuts.
-image::images/user-review-ui-change-screen-keyboard-shortcuts.png[width=800, link="images/gwt-user-review-ui-change-screen-keyboard-shortcuts.png"]
+image::images/user-review-ui-change-screen-keyboard-shortcuts.png[width=800, link="images/user-review-ui-change-screen-keyboard-shortcuts.png"]
In addition, Vim-like commands can be used to link:#key-navigation[
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index a2dc31f..a9779b1 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -702,6 +702,18 @@
to one of the fields in the
link:rest-api-changes.html#submit-record[SubmitRecord] REST API entity.
+`label:Code-Review=MAX`::
++
+Matches changes with label voted with the highest possible score.
+
+`label:Code-Review=MIN`::
++
+Matches changes with label voted with the lowest possible score.
+
+`label:Code-Review=ANY`::
++
+Matches changes with label voted with any score.
+
`label:Non-Author-Code-Review=need`::
+
Matches changes where the submit rules indicate that a label named
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 5ee1a08..fa62cd9 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -75,6 +75,7 @@
"//java/com/google/gerrit/extensions/restapi/testing:restapi-test-util",
"//java/com/google/gerrit/gpg/testing:gpg-test-util",
"//java/com/google/gerrit/git/testing",
+ "//java/com/google/gerrit/index/testing",
]
PGM_DEPLOY_ENV = [
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index 93c1237..085fef5 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -45,6 +45,8 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.index.testing.FakeIndexModule;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.pgm.Daemon;
import com.google.gerrit.pgm.Init;
@@ -449,9 +451,29 @@
cfg.setString("gitweb", null, "cgi", "");
cfg.setString(
"accountPatchReviewDb", null, "url", JdbcAccountPatchReviewStore.TEST_IN_MEMORY_URL);
+
+ String configuredIndexBackend = cfg.getString("index", null, "type");
+ IndexType indexType;
+ if (configuredIndexBackend != null) {
+ // Explicitly configured index backend from gerrit.config trumps any other ways to configure
+ // index backends so that Reindex tests can be explicit about the backend they want to test
+ // against.
+ indexType = new IndexType(configuredIndexBackend);
+ } else {
+ // Allow configuring the index backend based on sys/env variables so that integration tests
+ // can be run against different index backends.
+ indexType = IndexType.fromEnvironment().orElse(new IndexType("fake"));
+ }
+ if (indexType.isLucene()) {
+ daemon.setIndexModule(
+ LuceneIndexModule.singleVersionAllLatest(0, ReplicaUtil.isReplica(baseConfig)));
+ } else {
+ daemon.setIndexModule(FakeIndexModule.latestVersion(false));
+ }
+ // Elastic search is not supported in integration tests yet.
+
daemon.setEnableHttpd(desc.httpd());
- daemon.setLuceneModule(
- LuceneIndexModule.singleVersionAllLatest(0, ReplicaUtil.isReplica(baseConfig)));
+ daemon.setInMemory(true);
daemon.setDatabaseForTesting(
ImmutableList.of(
new InMemoryTestingDatabaseModule(cfg, site, inMemoryRepoManager),
@@ -476,6 +498,8 @@
String[] additionalArgs)
throws Exception {
requireNonNull(site);
+ daemon.addAdditionalSysModuleForTesting(
+ new ReindexProjectsAtStartup.Module(), new ReindexGroupsAtStartup.Module());
ExecutorService daemonService = Executors.newSingleThreadExecutor();
String[] args =
Stream.concat(
diff --git a/java/com/google/gerrit/entities/SubscribeSection.java b/java/com/google/gerrit/entities/SubscribeSection.java
index b95517c..574cae8 100644
--- a/java/com/google/gerrit/entities/SubscribeSection.java
+++ b/java/com/google/gerrit/entities/SubscribeSection.java
@@ -99,9 +99,10 @@
public ImmutableSet<BranchNameKey> getDestinationBranches(
BranchNameKey src, Collection<Ref> allRefsInRefsHeads) {
Set<BranchNameKey> ret = new HashSet<>();
- logger.atFine().log("Inspecting SubscribeSection %s", this);
- for (RefSpec r : matchingRefSpecs()) {
- logger.atFine().log("Inspecting [matching] ref %s", r);
+
+ ImmutableList<RefSpec> matching = matchingRefSpecs();
+ ImmutableList<RefSpec> multiMatch = multiMatchRefSpecs();
+ for (RefSpec r : matching) {
if (!r.matchSource(src.branch())) {
continue;
}
@@ -118,8 +119,7 @@
}
}
- for (RefSpec r : multiMatchRefSpecs()) {
- logger.atFine().log("Inspecting [all] ref %s", r);
+ for (RefSpec r : multiMatch) {
if (!r.matchSource(src.branch())) {
continue;
}
@@ -133,7 +133,9 @@
}
}
}
- logger.atFine().log("Returning possible branches: %s for project %s", ret, project());
+ logger.atFine().log(
+ "getDestinationBranches(%s): %s. matching refs: %s, multimatch refs: %s",
+ this, ret, matching, multiMatch);
return ImmutableSet.copyOf(ret);
}
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 079f306..91d032e 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -122,6 +122,8 @@
import com.google.inject.spi.Message;
import com.google.inject.util.Providers;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -365,6 +367,19 @@
return LuceneIndexModule.latestVersion(false);
} else if (indexType.isElasticsearch()) {
return ElasticIndexModule.latestVersion(false);
+ } else if (indexType.isFake()) {
+ // Use Reflection so that we can omit the fake index binary in production code. Test code does
+ // compile the component in.
+ try {
+ Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModule");
+ Method m = clazz.getMethod("latestVersion", boolean.class);
+ return (Module) m.invoke(null, false);
+ } catch (NoSuchMethodException
+ | ClassNotFoundException
+ | IllegalAccessException
+ | InvocationTargetException e) {
+ throw new IllegalStateException("can't create index", e);
+ }
} else {
throw new IllegalStateException("unsupported index.type = " + indexType);
}
diff --git a/java/com/google/gerrit/index/IndexType.java b/java/com/google/gerrit/index/IndexType.java
index cade439..0c3a76a 100644
--- a/java/com/google/gerrit/index/IndexType.java
+++ b/java/com/google/gerrit/index/IndexType.java
@@ -14,8 +14,12 @@
package com.google.gerrit.index;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
+import java.util.Optional;
/**
* Index types supported by the secondary index.
@@ -28,12 +32,42 @@
* allows to not break that case upon core implementation changes.
*/
public class IndexType {
+ public static final String SYS_PROP = "gerrit.index.type";
+ private static final String ENV_VAR = "GERRIT_INDEX_TYPE";
+
private static final String LUCENE = "lucene";
private static final String ELASTICSEARCH = "elasticsearch";
private static final String FAKE = "fake";
private final String type;
+ /**
+ * Returns the index type in case it was set by an environment variable. This is useful to run
+ * tests against a certain index backend.
+ */
+ public static Optional<IndexType> fromEnvironment() {
+ String value = System.getenv(ENV_VAR);
+ if (Strings.isNullOrEmpty(value)) {
+ value = System.getProperty(SYS_PROP);
+ }
+ if (Strings.isNullOrEmpty(value)) {
+ return Optional.empty();
+ }
+ value = value.toUpperCase().replace("-", "_");
+ IndexType type = new IndexType(value);
+ if (!Strings.isNullOrEmpty(System.getenv(ENV_VAR))) {
+ checkArgument(
+ type != null, "Invalid value for env variable %s: %s", ENV_VAR, System.getenv(ENV_VAR));
+ } else {
+ checkArgument(
+ type != null,
+ "Invalid value for system property %s: %s",
+ SYS_PROP,
+ System.getProperty(SYS_PROP));
+ }
+ return Optional.of(type);
+ }
+
public IndexType(@Nullable String type) {
this.type = type == null ? getDefault() : type.toLowerCase();
}
diff --git a/java/com/google/gerrit/index/testing/FakeIndexModuleOnInit.java b/java/com/google/gerrit/index/testing/FakeIndexModuleOnInit.java
new file mode 100644
index 0000000..75d8de2
--- /dev/null
+++ b/java/com/google/gerrit/index/testing/FakeIndexModuleOnInit.java
@@ -0,0 +1,37 @@
+// 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.index.testing;
+
+import com.google.gerrit.index.testing.AbstractFakeIndex.FakeAccountIndex;
+import com.google.gerrit.index.testing.AbstractFakeIndex.FakeGroupIndex;
+import com.google.gerrit.server.index.account.AccountIndex;
+import com.google.gerrit.server.index.group.GroupIndex;
+import com.google.inject.AbstractModule;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+
+public class FakeIndexModuleOnInit extends AbstractModule {
+ @Override
+ protected void configure() {
+ install(
+ new FactoryModuleBuilder()
+ .implement(AccountIndex.class, FakeAccountIndex.class)
+ .build(AccountIndex.Factory.class));
+
+ install(
+ new FactoryModuleBuilder()
+ .implement(GroupIndex.class, FakeGroupIndex.class)
+ .build(GroupIndex.Factory.class));
+ }
+}
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 29c5788..2b4cfef 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -87,6 +87,7 @@
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.group.PeriodicGroupIndexer;
+import com.google.gerrit.server.index.AbstractIndexModule;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.OnlineUpgrader;
import com.google.gerrit.server.index.VersionManager;
@@ -128,6 +129,8 @@
import com.google.inject.Provider;
import com.google.inject.Stage;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -207,7 +210,7 @@
private Injector httpdInjector;
private Path runFile;
private boolean inMemoryTest;
- private AbstractModule luceneModule;
+ private AbstractModule indexModule;
private Module emailModule;
private List<Module> testSysModules = new ArrayList<>();
private List<Module> testSshModules = new ArrayList<>();
@@ -336,9 +339,13 @@
}
@VisibleForTesting
- public void setLuceneModule(LuceneIndexModule m) {
- luceneModule = m;
- inMemoryTest = true;
+ public void setIndexModule(AbstractIndexModule m) {
+ indexModule = m;
+ }
+
+ @VisibleForTesting
+ public void setInMemory(boolean inMemory) {
+ this.inMemoryTest = inMemory;
}
@VisibleForTesting
@@ -523,8 +530,8 @@
}
private Module createIndexModule() {
- if (luceneModule != null) {
- return luceneModule;
+ if (indexModule != null) {
+ return indexModule;
}
if (indexType.isLucene()) {
return LuceneIndexModule.latestVersion(replica);
@@ -532,6 +539,20 @@
if (indexType.isElasticsearch()) {
return ElasticIndexModule.latestVersion(replica);
}
+ if (indexType.isFake()) {
+ // Use Reflection so that we can omit the fake index binary in production code. Test code does
+ // compile the component in.
+ try {
+ Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModule");
+ Method m = clazz.getMethod("latestVersion", boolean.class);
+ return (Module) m.invoke(null, replica);
+ } catch (NoSuchMethodException
+ | ClassNotFoundException
+ | IllegalAccessException
+ | InvocationTargetException e) {
+ throw new IllegalStateException("can't create index", e);
+ }
+ }
throw new IllegalStateException("unsupported index.type = " + indexType);
}
diff --git a/java/com/google/gerrit/pgm/Reindex.java b/java/com/google/gerrit/pgm/Reindex.java
index 3935268..6e99007 100644
--- a/java/com/google/gerrit/pgm/Reindex.java
+++ b/java/com/google/gerrit/pgm/Reindex.java
@@ -39,6 +39,8 @@
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -154,6 +156,21 @@
} else if (indexType.isElasticsearch()) {
indexModule =
ElasticIndexModule.singleVersionWithExplicitVersions(versions, threads, replica);
+ } else if (indexType.isFake()) {
+ // Use Reflection so that we can omit the fake index binary in production code. Test code does
+ // compile the component in.
+ try {
+ Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModule");
+ Method m =
+ clazz.getMethod(
+ "singleVersionWithExplicitVersions", Map.class, int.class, boolean.class);
+ indexModule = (Module) m.invoke(null, versions, threads, replica);
+ } catch (NoSuchMethodException
+ | ClassNotFoundException
+ | IllegalAccessException
+ | InvocationTargetException e) {
+ throw new IllegalStateException("can't create index", e);
+ }
} else {
throw new IllegalStateException("unsupported index.type = " + indexType);
}
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index 62ff66a..c4b0040 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -32,6 +32,7 @@
import com.google.gerrit.pgm.init.api.InstallPlugins;
import com.google.gerrit.pgm.init.api.LibraryDownload;
import com.google.gerrit.pgm.init.index.IndexManagerOnInit;
+import com.google.gerrit.pgm.init.index.IndexModuleOnInit;
import com.google.gerrit.pgm.init.index.elasticsearch.ElasticIndexModuleOnInit;
import com.google.gerrit.pgm.init.index.lucene.LuceneIndexModuleOnInit;
import com.google.gerrit.pgm.util.SiteProgram;
@@ -57,6 +58,7 @@
import com.google.inject.util.Providers;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -418,6 +420,19 @@
modules.add(new LuceneIndexModuleOnInit());
} else if (indexType.isElasticsearch()) {
modules.add(new ElasticIndexModuleOnInit());
+ } else if (indexType.isFake()) {
+ try {
+ Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModuleOnInit");
+ Module indexOnInitModule = (Module) clazz.getDeclaredConstructor().newInstance();
+ modules.add(indexOnInitModule);
+ } catch (InstantiationException
+ | IllegalAccessException
+ | ClassNotFoundException
+ | NoSuchMethodException
+ | InvocationTargetException e) {
+ throw new IllegalStateException("unable to create fake index", e);
+ }
+ modules.add(new IndexModuleOnInit());
} else {
throw new IllegalStateException("unsupported index.type = " + indexType);
}
diff --git a/java/com/google/gerrit/server/config/PluginConfigFactory.java b/java/com/google/gerrit/server/config/PluginConfigFactory.java
index 2d0f9a5..c49e928 100644
--- a/java/com/google/gerrit/server/config/PluginConfigFactory.java
+++ b/java/com/google/gerrit/server/config/PluginConfigFactory.java
@@ -28,13 +28,11 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.internal.storage.file.FileSnapshot;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
@@ -52,7 +50,6 @@
private final SecureStore secureStore;
private final Map<String, Config> pluginConfigs;
- private volatile FileSnapshot cfgSnapshot;
private volatile Config cfg;
@Inject
@@ -69,7 +66,6 @@
this.secureStore = secureStore;
this.pluginConfigs = new HashMap<>();
- this.cfgSnapshot = FileSnapshot.save(site.gerrit_config.toFile());
this.cfg = cfgProvider.get();
}
@@ -103,12 +99,10 @@
* @return the plugin configuration from the 'gerrit.config' file
*/
public PluginConfig getFromGerritConfig(String pluginName, boolean refresh) {
- if (refresh && secureStore.isOutdated()) {
- secureStore.reload();
- }
- File configFile = site.gerrit_config.toFile();
- if (refresh && cfgSnapshot.isModified(configFile)) {
- cfgSnapshot = FileSnapshot.save(configFile);
+ if (refresh) {
+ if (secureStore.isOutdated()) {
+ secureStore.reload();
+ }
cfg = cfgProvider.get();
}
return PluginConfig.createFromGerritConfig(pluginName, cfg);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 005aebb..454df66 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -659,7 +659,9 @@
// Process as many commands as possible, but may leave some commands in state NOT_ATTEMPTED.
private void processCommandsUnsafe(
Collection<ReceiveCommand> commands, MultiProgressMonitor progress) {
- logger.atFine().log("Calling user: %s", user.getLoggableName());
+ logger.atFine().log("Calling user: %s, commands: %d", user.getLoggableName(), commands.size());
+
+ // If the list of groups is large, the log entry may get dropped, so separate out.
logger.atFine().log("Groups: %s", lazy(() -> user.getEffectiveGroups().getKnownGroups()));
if (!projectState.getProject().getState().permitsWrite()) {
@@ -669,8 +671,6 @@
return;
}
- logger.atFine().log("Parsing %d commands", commands.size());
-
List<ReceiveCommand> magicCommands = new ArrayList<>();
List<ReceiveCommand> regularCommands = new ArrayList<>();
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 9c39c6e..810cd4d 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -43,11 +43,13 @@
import com.google.common.flogger.FluentLogger;
import com.google.common.io.Files;
import com.google.common.primitives.Longs;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.LegacySubmitRequirement;
import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.entities.Project;
@@ -72,6 +74,7 @@
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
+import com.google.gerrit.server.query.change.MagicLabelValue;
import com.google.gson.Gson;
import com.google.protobuf.MessageLite;
import java.sql.Timestamp;
@@ -602,16 +605,35 @@
for (PatchSetApproval a : cd.currentApprovals()) {
if (a.value() != 0 && !a.isLegacySubmit()) {
allApprovals.add(formatLabel(a.label(), a.value(), a.accountId()));
+ LabelType labelType = cd.getLabelTypes().byLabel(a.labelId());
+ allApprovals.addAll(getMaxMinAnyLabels(a.label(), a.value(), labelType, a.accountId()));
if (owners && cd.change().getOwner().equals(a.accountId())) {
allApprovals.add(formatLabel(a.label(), a.value(), ChangeQueryBuilder.OWNER_ACCOUNT_ID));
+ allApprovals.addAll(
+ getMaxMinAnyLabels(
+ a.label(), a.value(), labelType, ChangeQueryBuilder.OWNER_ACCOUNT_ID));
}
distinctApprovals.add(formatLabel(a.label(), a.value()));
+ distinctApprovals.addAll(getMaxMinAnyLabels(a.label(), a.value(), labelType, null));
}
}
allApprovals.addAll(distinctApprovals);
return allApprovals;
}
+ private static List<String> getMaxMinAnyLabels(
+ String label, short labelVal, LabelType labelType, @Nullable Account.Id accountId) {
+ List<String> labels = new ArrayList<>();
+ if (labelVal == labelType.getMaxPositive()) {
+ labels.add(formatLabel(label, MagicLabelValue.MAX.name(), accountId));
+ }
+ if (labelVal == labelType.getMaxNegative()) {
+ labels.add(formatLabel(label, MagicLabelValue.MIN.name(), accountId));
+ }
+ labels.add(formatLabel(label, MagicLabelValue.ANY.name(), accountId));
+ return labels;
+ }
+
public static Set<String> getAuthorParts(ChangeData cd) {
return SchemaUtil.getPersonParts(cd.getAuthor());
}
@@ -696,6 +718,17 @@
+ (accountId != null ? "," + formatAccount(accountId) : "");
}
+ public static String formatLabel(String label, String value) {
+ return formatLabel(label, value, null);
+ }
+
+ public static String formatLabel(String label, String value, @Nullable Account.Id accountId) {
+ return label.toLowerCase()
+ + "="
+ + value
+ + (accountId != null ? "," + formatAccount(accountId) : "");
+ }
+
private static String formatAccount(Account.Id accountId) {
if (ChangeQueryBuilder.OWNER_ACCOUNT_ID.equals(accountId)) {
return ChangeQueryBuilder.ARG_ID_OWNER;
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 6355674..879da4f 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -150,6 +150,9 @@
*/
static final Schema<ChangeData> V63 = schema(V62, false);
+ /** Added support for MIN/MAX/ANY for {@link ChangeField#LABEL} */
+ static final Schema<ChangeData> V64 = schema(V63, false);
+
/**
* Name of the change index to be used when contacting index backends or loading configurations.
*/
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 3253282..0d710b9 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -126,12 +126,12 @@
public static final String KEY_CAN_OVERRIDE = "canOverride";
public static final String KEY_BRANCH = "branch";
- public static final String SUBMIT_REQUIREMENT = "submitRequirement";
+ public static final String SUBMIT_REQUIREMENT = "submit-requirement";
public static final String KEY_SR_NAME = "name";
public static final String KEY_SR_DESCRIPTION = "description";
- public static final String KEY_SR_APPLICABILITY_EXPRESSION = "applicabilityExpression";
- public static final String KEY_SR_SUBMITTABILITY_EXPRESSION = "submittabilityExpression";
- public static final String KEY_SR_OVERRIDE_EXPRESSION = "overrideExpression";
+ public static final String KEY_SR_APPLICABILITY_EXPRESSION = "applicableIf";
+ public static final String KEY_SR_SUBMITTABILITY_EXPRESSION = "submittableIf";
+ public static final String KEY_SR_OVERRIDE_EXPRESSION = "overrideIf";
public static final String KEY_SR_OVERRIDE_IN_CHILD_PROJECTS = "canOverrideInChildProjects";
public static final String KEY_MATCH = "match";
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 94b5442..131de74 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -973,7 +973,7 @@
int eq = name.indexOf('=');
if (args.getSchema().hasField(ChangeField.SUBMIT_RECORD) && eq > 0) {
String statusName = name.substring(eq + 1).toUpperCase();
- if (!isInt(statusName)) {
+ if (!isInt(statusName) && !MagicLabelValue.tryParse(statusName).isPresent()) {
SubmitRecord.Label.Status status =
Enums.getIfPresent(SubmitRecord.Label.Status.class, statusName).orNull();
if (status == null) {
diff --git a/java/com/google/gerrit/server/query/change/LabelPredicate.java b/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 38d1dbe..989b4bb 100644
--- a/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.change;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
@@ -59,12 +60,12 @@
protected static class Parsed {
protected final String label;
protected final String test;
- protected final int expVal;
+ protected final int numericValue;
- protected Parsed(String label, String test, int expVal) {
+ protected Parsed(String label, String test, int numericValue) {
this.label = label;
this.test = test;
- this.expVal = expVal;
+ this.numericValue = numericValue;
}
}
@@ -83,6 +84,14 @@
protected static List<Predicate<ChangeData>> predicates(Args args) {
String v = args.value;
+
+ try {
+ MagicLabelVote mlv = MagicLabelVote.parseWithEquals(v);
+ return ImmutableList.of(new MagicLabelPredicate(args, mlv));
+ } catch (IllegalArgumentException e) {
+ // Try next format.
+ }
+
Parsed parsed = null;
try {
@@ -108,7 +117,7 @@
} else {
range =
RangeUtil.getRange(
- parsed.label, parsed.test, parsed.expVal, -MAX_LABEL_VALUE, MAX_LABEL_VALUE);
+ parsed.label, parsed.test, parsed.numericValue, -MAX_LABEL_VALUE, MAX_LABEL_VALUE);
}
String prefix = range.prefix;
int min = range.min;
diff --git a/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java b/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
new file mode 100644
index 0000000..e3c58e47
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
@@ -0,0 +1,101 @@
+// 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.query.change;
+
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelType;
+import com.google.gerrit.entities.LabelTypes;
+import com.google.gerrit.entities.LabelValue;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.project.ProjectState;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public class MagicLabelPredicate extends ChangeIndexPredicate {
+ protected final LabelPredicate.Args args;
+ private final MagicLabelVote magicLabelVote;
+
+ public MagicLabelPredicate(LabelPredicate.Args args, MagicLabelVote magicLabelVote) {
+ super(ChangeField.LABEL, magicLabelVote.formatLabel());
+ this.args = args;
+ this.magicLabelVote = magicLabelVote;
+ }
+
+ @Override
+ public boolean match(ChangeData changeData) {
+ Change change = changeData.change();
+ if (change == null) {
+ // The change has disappeared.
+ //
+ return false;
+ }
+
+ Optional<ProjectState> project = args.projectCache.get(change.getDest().project());
+ if (!project.isPresent()) {
+ // The project has disappeared.
+ //
+ return false;
+ }
+
+ LabelType labelType = type(project.get().getLabelTypes(), magicLabelVote.label());
+ if (labelType == null) {
+ return false; // Label is not defined by this project.
+ }
+
+ switch (magicLabelVote.value()) {
+ case ANY:
+ return matchAny(changeData, labelType);
+ case MIN:
+ return matchNumeric(changeData, magicLabelVote.label(), labelType.getMin().getValue());
+ case MAX:
+ return matchNumeric(changeData, magicLabelVote.label(), labelType.getMax().getValue());
+ }
+
+ throw new IllegalStateException("Unsupported magic label value: " + magicLabelVote.value());
+ }
+
+ private boolean matchAny(ChangeData changeData, LabelType labelType) {
+ List<Predicate<ChangeData>> predicates = new ArrayList<>();
+ for (LabelValue labelValue : labelType.getValues()) {
+ if (labelValue.getValue() != 0) {
+ predicates.add(numericPredicate(labelType.getName(), labelValue.getValue()));
+ }
+ }
+ return or(predicates).asMatchable().match(changeData);
+ }
+
+ private boolean matchNumeric(ChangeData changeData, String label, short value) {
+ return numericPredicate(label, value).match(changeData);
+ }
+
+ private EqualsLabelPredicate numericPredicate(String label, short value) {
+ return new EqualsLabelPredicate(args, label, value, /* account= */ null);
+ }
+
+ protected static LabelType type(LabelTypes types, String toFind) {
+ if (types.byLabel(toFind) != null) {
+ return types.byLabel(toFind);
+ }
+
+ for (LabelType lt : types.getLabelTypes()) {
+ if (toFind.equalsIgnoreCase(lt.getName())) {
+ return lt;
+ }
+ }
+ return null;
+ }
+}
diff --git a/java/com/google/gerrit/server/query/change/MagicLabelValue.java b/java/com/google/gerrit/server/query/change/MagicLabelValue.java
new file mode 100644
index 0000000..c4bcbe3
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/MagicLabelValue.java
@@ -0,0 +1,31 @@
+// 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.query.change;
+
+import java.util.Optional;
+
+public enum MagicLabelValue {
+ ANY,
+ MIN,
+ MAX;
+
+ public static Optional<MagicLabelValue> tryParse(String value) {
+ try {
+ return Optional.of(MagicLabelValue.valueOf(value));
+ } catch (IllegalArgumentException e) {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/query/change/MagicLabelVote.java b/java/com/google/gerrit/server/query/change/MagicLabelVote.java
new file mode 100644
index 0000000..c29ac72
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/MagicLabelVote.java
@@ -0,0 +1,47 @@
+// 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.query.change;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Strings;
+import com.google.gerrit.entities.LabelType;
+import java.util.Locale;
+
+/** An entity representing a special label vote that's not numeric, e.g. MAX, MIN, etc... */
+@AutoValue
+public abstract class MagicLabelVote {
+ public static MagicLabelVote parseWithEquals(String text) {
+ checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
+ int e = text.lastIndexOf('=');
+ checkArgument(e >= 0, "Label vote missing '=': %s", text);
+ String label = text.substring(0, e);
+ String voteValue = text.substring(e + 1);
+ return create(label, MagicLabelValue.valueOf(voteValue));
+ }
+
+ public static MagicLabelVote create(String label, MagicLabelValue value) {
+ return new AutoValue_MagicLabelVote(LabelType.checkNameInternal(label), value);
+ }
+
+ public abstract String label();
+
+ public abstract MagicLabelValue value();
+
+ public String formatLabel() {
+ return label().toLowerCase(Locale.US) + "=" + value().name();
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java b/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java
index 0356cdd..53d0f18 100644
--- a/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java
+++ b/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java
@@ -19,7 +19,9 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.HumanComment;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.AttentionSetInput;
@@ -60,6 +62,7 @@
* This class is used to update the attention set when performing a review or replying on a change.
*/
public class ReplyAttentionSetUpdates {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final PermissionBackend permissionBackend;
private final AddToAttentionSetOp.Factory addToAttentionSetOpFactory;
@@ -316,11 +319,15 @@
AttentionSetUtil.validateInput(add);
try {
Account.Id attentionUserId =
- getAccountIdAndValidateUser(changeNotes, add.user, accountsChangedInCommit);
+ getAccountIdAndValidateUser(
+ changeNotes, add.user, accountsChangedInCommit, AttentionSetUpdate.Operation.ADD);
addToAttentionSet(bu, changeNotes, attentionUserId, add.reason, false);
} catch (AccountResolver.UnresolvableAccountException ex) {
// This happens only when the account doesn't exist. Silently ignore it. If we threw an error
// message here, then it would be possible to probe whether an account exists.
+ } catch (AuthException ex) {
+ // adding users without permission to the attention set should fail silently.
+ logger.atFine().log(ex.getMessage());
}
}
@@ -334,17 +341,25 @@
AttentionSetUtil.validateInput(remove);
try {
Account.Id attentionUserId =
- getAccountIdAndValidateUser(changeNotes, remove.user, accountsChangedInCommit);
+ getAccountIdAndValidateUser(
+ changeNotes,
+ remove.user,
+ accountsChangedInCommit,
+ AttentionSetUpdate.Operation.REMOVE);
removeFromAttentionSet(bu, changeNotes, attentionUserId, remove.reason, false);
} catch (AccountResolver.UnresolvableAccountException ex) {
// This happens only when the account doesn't exist. Silently ignore it. If we threw an error
// message here, then it would be possible to probe whether an account exists.
+ } catch (AuthException ex) {
+ // this should never happen since removing users with permissions should work.
+ logger.atSevere().log(ex.getMessage());
}
}
- private Account.Id getAccountId(ChangeNotes changeNotes, String user)
+ private Account.Id getAccountId(
+ ChangeNotes changeNotes, String user, AttentionSetUpdate.Operation operation)
throws ConfigInvalidException, IOException, UnprocessableEntityException,
- PermissionBackendException {
+ PermissionBackendException, AuthException {
Account.Id attentionUserId = accountResolver.resolve(user).asUnique().account().id();
try {
permissionBackend
@@ -352,22 +367,29 @@
.change(changeNotes)
.check(ChangePermission.READ);
} catch (AuthException e) {
+ // If the change is private, it is okay to add the user to the attention set since that
+ // person will be granted visibility when a reviewer.
if (!changeNotes.getChange().isPrivate()) {
- // If the change is private, it is okay to add the user to the attention set since that
- // person will be granted visibility when a reviewer.
- throw new UnprocessableEntityException(
- "Can't add to attention set: Read not permitted for " + attentionUserId, e);
+
+ // Removing users without access is allowed, adding is not allowed
+ if (operation == AttentionSetUpdate.Operation.ADD) {
+ throw new AuthException(
+ "Can't modify attention set: Read not permitted for " + attentionUserId, e);
+ }
}
}
return attentionUserId;
}
private Account.Id getAccountIdAndValidateUser(
- ChangeNotes changeNotes, String user, Set<Account.Id> accountsChangedInCommit)
+ ChangeNotes changeNotes,
+ String user,
+ Set<Account.Id> accountsChangedInCommit,
+ AttentionSetUpdate.Operation operation)
throws ConfigInvalidException, IOException, PermissionBackendException,
- UnprocessableEntityException, BadRequestException {
+ UnprocessableEntityException, BadRequestException, AuthException {
try {
- Account.Id attentionUserId = getAccountId(changeNotes, user);
+ Account.Id attentionUserId = getAccountId(changeNotes, user, operation);
if (accountsChangedInCommit.contains(attentionUserId)) {
throw new BadRequestException(
String.format(
diff --git a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
index 3dc5be0..179a3d0 100644
--- a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
+++ b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
@@ -158,6 +158,8 @@
return ruleError("Error looking up change " + cd.getId(), e);
}
+ logger.atFine().log("input approvals: %s", cd.approvals());
+
List<Term> results;
try {
results =
@@ -178,7 +180,9 @@
getSubmitRuleName(), cd.getId(), projectState.getName()));
}
- return resultsToSubmitRecord(getSubmitRule(), results);
+ SubmitRecord submitRecord = resultsToSubmitRecord(getSubmitRule(), results);
+ logger.atFine().log("submit record: %s", submitRecord);
+ return submitRecord;
}
private String getSubmitRuleName() {
@@ -320,6 +324,7 @@
logger.atSevere().withCause(e).log(err);
return createRuleError(DEFAULT_MSG);
}
+ logger.atFine().log("rule error: %s", err);
return createRuleError(err);
}
diff --git a/java/com/google/gerrit/server/submit/SubscriptionGraph.java b/java/com/google/gerrit/server/submit/SubscriptionGraph.java
index ad16cb0..26c8ac9 100644
--- a/java/com/google/gerrit/server/submit/SubscriptionGraph.java
+++ b/java/com/google/gerrit/server/submit/SubscriptionGraph.java
@@ -231,7 +231,6 @@
Map<BranchNameKey, GitModules> branchGitModules,
MergeOpRepoManager orm)
throws SubmoduleConflictException {
- logger.atFine().log("Calculating superprojects - submodules map");
LinkedHashSet<BranchNameKey> allVisited = new LinkedHashSet<>();
for (BranchNameKey updatedBranch : updatedBranches) {
if (allVisited.contains(updatedBranch)) {
@@ -332,7 +331,6 @@
Map<BranchNameKey, GitModules> branchGitModules,
MergeOpRepoManager orm)
throws IOException {
- logger.atFine().log("Calculating possible superprojects for %s", srcBranch);
Collection<SubmoduleSubscription> ret = new ArrayList<>();
Project.NameKey srcProject = srcBranch.project();
for (SubscribeSection s :
@@ -340,7 +338,6 @@
.get(srcProject)
.orElseThrow(illegalState(srcProject))
.getSubscribeSections(srcBranch)) {
- logger.atFine().log("Checking subscribe section %s", s);
Collection<BranchNameKey> branches = getDestinationBranches(srcBranch, s, orm);
for (BranchNameKey targetBranch : branches) {
Project.NameKey targetProject = targetBranch.project();
@@ -348,11 +345,11 @@
OpenRepo or = orm.getRepo(targetProject);
ObjectId id = or.repo.resolve(targetBranch.branch());
if (id == null) {
- logger.atFine().log("The branch %s doesn't exist.", targetBranch);
+ logger.atFine().log("SubscribeSection %s: branch %s doesn't exist.", s, targetBranch);
continue;
}
} catch (NoSuchProjectException e) {
- logger.atFine().log("The project %s doesn't exist", targetProject);
+ logger.atFine().log("SubscribeSection %s: project %s doesn't exist", s, targetProject);
continue;
}
diff --git a/java/com/google/gerrit/server/update/BatchUpdate.java b/java/com/google/gerrit/server/update/BatchUpdate.java
index 3b0cd9a..f558d30 100644
--- a/java/com/google/gerrit/server/update/BatchUpdate.java
+++ b/java/com/google/gerrit/server/update/BatchUpdate.java
@@ -75,6 +75,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;
@@ -553,11 +554,15 @@
try {
logDebug("Executing updateRepo on %d ops", ops.size());
RepoContextImpl ctx = new RepoContextImpl();
- for (BatchUpdateOp op : ops.values()) {
+ for (Entry<Change.Id, BatchUpdateOp> op : ops.entries()) {
try (TraceContext.TraceTimer ignored =
TraceContext.newTimer(
- op.getClass().getSimpleName() + "#updateRepo", Metadata.empty())) {
- op.updateRepo(ctx);
+ op.getClass().getSimpleName() + "#updateRepo",
+ Metadata.builder()
+ .projectName(project.get())
+ .changeId(op.getKey().get())
+ .build())) {
+ op.getValue().updateRepo(ctx);
}
}
@@ -672,7 +677,8 @@
for (BatchUpdateOp op : e.getValue()) {
try (TraceContext.TraceTimer ignored =
TraceContext.newTimer(
- op.getClass().getSimpleName() + "#updateChange", Metadata.empty())) {
+ op.getClass().getSimpleName() + "#updateChange",
+ Metadata.builder().projectName(project.get()).changeId(id.get()).build())) {
dirty |= op.updateChange(ctx);
}
}
diff --git a/java/com/google/gerrit/truth/NullAwareCorrespondence.java b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
index 687ad94..5b107a6 100644
--- a/java/com/google/gerrit/truth/NullAwareCorrespondence.java
+++ b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
@@ -7,15 +7,6 @@
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
-// 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
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index d091ae1..9e51054 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -47,8 +47,8 @@
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import com.github.rholder.retry.StopStrategies;
import com.google.common.collect.FluentIterable;
@@ -686,7 +686,7 @@
() -> gApi.accounts().id(activatableAccountId.get()).setActive(false));
assertThat(thrown).hasMessageThat().isEqualTo("account not active");
assertThat(accountOperations.account(activatableAccountId).get().active()).isFalse();
- verifyZeroInteractions(listener);
+ verifyNoInteractions(listener);
// Activate account that can be activated
gApi.accounts().id(activatableAccountId.get()).setActive(true);
@@ -697,7 +697,7 @@
// Activate account that is already active
gApi.accounts().id(activatableAccountId.get()).setActive(true);
assertThat(accountOperations.account(activatableAccountId).get().active()).isTrue();
- verifyZeroInteractions(listener);
+ verifyNoMoreInteractions(listener);
// Try deactivating account that cannot be deactivated
thrown =
@@ -706,13 +706,13 @@
() -> gApi.accounts().id(activatableAccountId.get()).setActive(false));
assertThat(thrown).hasMessageThat().isEqualTo("not allowed to deactive account");
assertThat(accountOperations.account(activatableAccountId).get().active()).isTrue();
- verifyZeroInteractions(listener);
+ verifyNoMoreInteractions(listener);
/* Test account that can be deactivated, but not activated */
// Activate account that is already inactive
gApi.accounts().id(deactivatableAccountId.get()).setActive(true);
assertThat(accountOperations.account(deactivatableAccountId).get().active()).isTrue();
- verifyZeroInteractions(listener);
+ verifyNoMoreInteractions(listener);
// Deactivate account that can be deactivated
gApi.accounts().id(deactivatableAccountId.get()).setActive(false);
@@ -727,7 +727,7 @@
() -> gApi.accounts().id(deactivatableAccountId.get()).setActive(false));
assertThat(thrown).hasMessageThat().isEqualTo("account not active");
assertThat(accountOperations.account(deactivatableAccountId).get().active()).isFalse();
- verifyZeroInteractions(listener);
+ verifyNoMoreInteractions(listener);
// Try activating account that cannot be activated
thrown =
@@ -736,7 +736,7 @@
() -> gApi.accounts().id(deactivatableAccountId.get()).setActive(true));
assertThat(thrown).hasMessageThat().isEqualTo("not allowed to active account");
assertThat(accountOperations.account(deactivatableAccountId).get().active()).isFalse();
- verifyZeroInteractions(listener);
+ verifyNoMoreInteractions(listener);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index fcc0dec..c08aa7f 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -4024,6 +4024,86 @@
}
@Test
+ public void submitRequirement_withLabelEqualsMax() throws Exception {
+ configSubmitRequirement(
+ project,
+ SubmitRequirement.builder()
+ .setName("code-review")
+ .setSubmittabilityExpression(
+ SubmitRequirementExpression.create("label:code-review=MAX"))
+ .setAllowOverrideInChildProjects(false)
+ .build());
+
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+
+ ChangeInfo change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+
+ voteLabel(changeId, "code-review", 2);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+ }
+
+ @Test
+ public void submitRequirement_withLabelEqualsMinBlockingSubmission() throws Exception {
+ configSubmitRequirement(
+ project,
+ SubmitRequirement.builder()
+ .setName("code-review")
+ .setSubmittabilityExpression(
+ SubmitRequirementExpression.create("-label:code-review=MIN"))
+ .setAllowOverrideInChildProjects(false)
+ .build());
+
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+
+ ChangeInfo change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ // Requirement is satisfied because there are no votes
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+
+ voteLabel(changeId, "code-review", -1);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ // Requirement is still satisfied because -1 is not the max negative value
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+
+ voteLabel(changeId, "code-review", -2);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ // Requirement is now unsatisfied because -2 is the max negative value
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+ }
+
+ @Test
+ public void submitRequirement_withLabelEqualsAny() throws Exception {
+ configSubmitRequirement(
+ project,
+ SubmitRequirement.builder()
+ .setName("code-review")
+ .setSubmittabilityExpression(
+ SubmitRequirementExpression.create("label:code-review=ANY"))
+ .setAllowOverrideInChildProjects(false)
+ .build());
+
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+
+ ChangeInfo change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+
+ voteLabel(changeId, "code-review", 1);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+ }
+
+ @Test
public void submitRequirementIsSatisfied_whenSubmittabilityExpressionIsFulfilled()
throws Exception {
configSubmitRequirement(
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 9d0b1f4..4590d34 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -1207,10 +1207,15 @@
ChangeApi originalChange = gApi.changes().id(project.get() + "~master~" + result.getChangeId());
ChangeApi cherryPick = originalChange.revision(result.getCommit().name()).cherryPick(input);
+ String firstCherryPickChangeId = cherryPick.id();
cherryPick.setWorkInProgress();
- cherryPick = originalChange.revision(result.getCommit().name()).cherryPick(input);
+ gApi.changes()
+ .id(project.get() + "~master~" + result.getChangeId())
+ .revision(result.getCommit().name())
+ .cherryPick(input);
- ChangeInfo secondCherryPickResult = cherryPick.get(ALL_REVISIONS);
+ ChangeInfo secondCherryPickResult =
+ gApi.changes().id(firstCherryPickChangeId).get(ALL_REVISIONS);
assertThat(secondCherryPickResult.revisions).hasSize(2);
assertThat(secondCherryPickResult.workInProgress).isNull();
}
@@ -1416,22 +1421,12 @@
BadRequestException thrown =
assertThrows(
BadRequestException.class,
- () ->
- gApi.changes()
- .id(r.getChangeId())
- .revision(r.getCommit().name())
- .files(3)
- .keySet());
+ () -> gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).files(3));
assertThat(thrown).hasMessageThat().isEqualTo("invalid parent number: 3");
thrown =
assertThrows(
BadRequestException.class,
- () ->
- gApi.changes()
- .id(r.getChangeId())
- .revision(r.getCommit().name())
- .files(-1)
- .keySet());
+ () -> gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).files(-1));
assertThat(thrown).hasMessageThat().isEqualTo("invalid parent number: -1");
}
@@ -1444,14 +1439,13 @@
BadRequestException thrown =
assertThrows(
- BadRequestException.class,
- () -> gApi.changes().id(changeId).revision(revId2).files(2).keySet());
+ BadRequestException.class, () -> gApi.changes().id(changeId).revision(revId2).files(2));
assertThat(thrown).hasMessageThat().isEqualTo("invalid parent number: 2");
thrown =
assertThrows(
BadRequestException.class,
- () -> gApi.changes().id(changeId).revision(revId2).files(-1).keySet());
+ () -> gApi.changes().id(changeId).revision(revId2).files(-1));
assertThat(thrown).hasMessageThat().isEqualTo("invalid parent number: -1");
}
diff --git a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index b7acbe2..92770ba 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -38,7 +38,6 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.AccountGroup;
-import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Permission;
@@ -52,7 +51,6 @@
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.receive.ReceiveCommitsAdvertiseRefsHookChain;
import com.google.gerrit.server.git.receive.testing.TestRefAdvertiser;
-import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
@@ -69,7 +67,6 @@
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
@@ -82,7 +79,6 @@
@NoHttpd
public class RefAdvertisementIT extends AbstractDaemonTest {
@Inject private AllUsersName allUsersName;
- @Inject private ChangeNoteUtil noteUtil;
@Inject private PermissionBackend permissionBackend;
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@@ -1106,37 +1102,10 @@
@Test
public void receivePackOmitsMissingObject() throws Exception {
- String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
try (Repository repo = repoManager.openRepository(project);
TestRepository<Repository> tr = new TestRepository<>(repo)) {
- String subject = "Subject for missing commit";
- Change c = new Change(cd3.change());
- PatchSet.Id psId = PatchSet.id(cd3.getId(), 2);
- c.setCurrentPatchSet(psId, subject, c.getOriginalSubject());
-
- PersonIdent committer = serverIdent.get();
- PersonIdent author =
- noteUtil.newAccountIdIdent(getAccount(admin.id()).id(), committer.getWhen(), committer);
- tr.branch(RefNames.changeMetaRef(cd3.getId()))
- .commit()
- .author(author)
- .committer(committer)
- .message(
- "Update patch set "
- + psId.get()
- + "\n"
- + "\n"
- + "Patch-set: "
- + psId.get()
- + "\n"
- + "Commit: "
- + rev
- + "\n"
- + "Subject: "
- + subject
- + "\n")
- .create();
- indexer.index(c.getProject(), c.getId());
+ PatchSet.Id psId = PatchSet.id(cd3.getId(), 1);
+ tr.delete(psId.toRefName());
}
assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(cd4, 1));
diff --git a/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java b/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java
index cad0b83..cac376f 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java
@@ -31,14 +31,18 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.index.IndexDefinition;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.server.index.GerritIndexStatus;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.inject.Injector;
+import com.google.inject.Key;
import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
import java.nio.file.Files;
+import java.util.Collection;
import java.util.Set;
import java.util.function.Consumer;
import org.eclipse.jgit.lib.Config;
@@ -48,9 +52,6 @@
@NoHttpd
public abstract class AbstractReindexTests extends StandaloneSiteTest {
- /** @param injector injector */
- public abstract void configureIndex(Injector injector) throws Exception;
-
private static final String CHANGES = ChangeSchemaDefinitions.NAME;
private Project.NameKey project;
@@ -223,10 +224,18 @@
}
}
+ protected static void createAllIndexes(Injector injector) {
+ Collection<IndexDefinition<?, ?, ?>> indexDefs =
+ injector.getInstance(Key.get(new TypeLiteral<Collection<IndexDefinition<?, ?, ?>>>() {}));
+ for (IndexDefinition<?, ?, ?> indexDef : indexDefs) {
+ indexDef.getIndexCollection().getSearchIndex().deleteAll();
+ }
+ }
+
private void setUpChange() throws Exception {
project = Project.nameKey("reindex-project-test");
try (ServerContext ctx = startServer()) {
- configureIndex(ctx.getInjector());
+ createAllIndexes(ctx.getInjector());
GerritApi gApi = ctx.getInjector().getInstance(GerritApi.class);
gApi.projects().create(project.get());
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index f23cc10..0632241 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -14,23 +14,15 @@
package com.google.gerrit.acceptance.pgm;
-import static com.google.gerrit.elasticsearch.ElasticTestUtils.createAllIndexes;
import static com.google.gerrit.elasticsearch.ElasticTestUtils.getConfig;
import com.google.gerrit.elasticsearch.ElasticVersion;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.inject.Injector;
import org.eclipse.jgit.lib.Config;
public class ElasticReindexIT extends AbstractReindexTests {
-
@ConfigSuite.Default
public static Config elasticsearchV7() {
return getConfig(ElasticVersion.V7_8);
}
-
- @Override
- public void configureIndex(Injector injector) {
- createAllIndexes(injector);
- }
}
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/LuceneReindexIT.java
similarity index 68%
rename from javatests/com/google/gerrit/acceptance/pgm/ReindexIT.java
rename to javatests/com/google/gerrit/acceptance/pgm/LuceneReindexIT.java
index 223851e..e630bca 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ReindexIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/LuceneReindexIT.java
@@ -14,9 +14,14 @@
package com.google.gerrit.acceptance.pgm;
-import com.google.inject.Injector;
+import com.google.gerrit.testing.ConfigSuite;
+import org.eclipse.jgit.lib.Config;
-public class ReindexIT extends AbstractReindexTests {
- @Override
- public void configureIndex(Injector injector) {}
+public class LuceneReindexIT extends AbstractReindexTests {
+ @ConfigSuite.Default
+ public static Config luceneConfig() {
+ Config cfg = new Config();
+ cfg.setString("index", null, "type", "lucene");
+ return cfg;
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
index 4b780f8..530f2ec 100644
--- a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
@@ -24,7 +24,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
@@ -409,7 +409,7 @@
PushOneCommit.Result r = push.to("refs/heads/master");
r.assertOkStatus();
- verifyZeroInteractions(testPerformanceLogger);
+ verifyNoInteractions(testPerformanceLogger);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
index 800ee42..4b45476 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.extensions.restapi.testing.AttentionSetUpdateSubject.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
@@ -32,23 +33,29 @@
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.AttentionSetUpdate.Operation;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Permission;
import com.google.gerrit.extensions.api.changes.AttentionSetInput;
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewerInput;
+import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.server.account.ServiceUserClassifier;
import com.google.gerrit.server.query.change.ChangeData;
@@ -82,6 +89,7 @@
@Inject private FakeEmailSender email;
@Inject private TestCommentHelper testCommentHelper;
@Inject private Provider<InternalChangeQuery> changeQueryProvider;
+ @Inject private ProjectOperations projectOperations;
/** Simulates a fake clock. Uses second granularity. */
private static class FakeClock implements LongSupplier {
@@ -622,7 +630,7 @@
assertThat(attentionSet).hasReasonThat().isEqualTo("reason");
// No emails for adding to attention set were sent.
- email.getMessages().isEmpty();
+ assertThat(email.getMessages()).isEmpty();
}
@Test
@@ -631,6 +639,7 @@
// implictly adds the user to the attention set when adding as reviewer
change(r).addReviewer(user.email());
requestScopeOperations.setApiUser(user.id());
+ email.clear();
ReviewInput reviewInput =
ReviewInput.create().removeUserFromAttentionSet(user.email(), "reason");
@@ -643,7 +652,7 @@
assertThat(attentionSet).hasReasonThat().isEqualTo("reason");
// No emails for removing from attention set were sent.
- email.getMessages().isEmpty();
+ assertThat(email.getMessages()).isEmpty();
}
@Test
@@ -1834,6 +1843,44 @@
assertThat(attentionSet).hasReasonThat().isEqualTo("Their vote was deleted");
}
+ @Test
+ public void accountsWithNoReadPermissionIgnoredOnReply() throws Exception {
+ // Create a group with user.
+ GroupInput groupInput = new GroupInput();
+ groupInput.name = name("User");
+ groupInput.members = ImmutableList.of(String.valueOf(user.id()));
+ GroupInfo group = gApi.groups().create(groupInput).get();
+
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).addReviewer(user.email());
+
+ // remove read permission for user.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/*").group(AccountGroup.uuid(group.id)))
+ .update();
+
+ // removing user without permissions from attention set is allowed on reply.
+ gApi.changes()
+ .id(r.getChangeId())
+ .current()
+ .review(new ReviewInput().removeUserFromAttentionSet(user.email(), "reason"));
+
+ // Add user to attention throws an exception.
+ assertThrows(
+ AuthException.class,
+ () -> change(r).addToAttentionSet(new AttentionSetInput(user.email(), "reason")));
+
+ // Add user to attention set is ignored on reply.
+ gApi.changes()
+ .id(r.getChangeId())
+ .current()
+ .review(new ReviewInput().addUserToAttentionSet(user.email(), "reason"));
+ assertThat(Iterables.getOnlyElement(getAttentionSetUpdatesForUser(r, user)).operation())
+ .isEqualTo(Operation.REMOVE);
+ }
+
private List<AttentionSetUpdate> getAttentionSetUpdatesForUser(
PushOneCommit.Result r, TestAccount account) {
return getAttentionSetUpdates(r.getChange().getId()).stream()
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index e39f967..9d821b7 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -257,7 +257,6 @@
+ "Groups: "
+ rev
+ "\n");
- indexer.index(c.getProject(), c.getId());
ChangeNotes notes = changeNotesFactory.create(c.getProject(), c.getId());
FixInput fix = new FixInput();
@@ -817,8 +816,6 @@
+ "Subject: "
+ subject
+ "\n");
- indexer.index(c.getProject(), c.getId());
-
return ps;
}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java
new file mode 100644
index 0000000..f5e4e09
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.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.acceptance.testsuite.index;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.index.testing.AbstractFakeIndex;
+import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import javax.inject.Inject;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Test to check that the expected index backend was bound depending on sys/env properties. */
+public class DefaultIndexBindingIT extends AbstractDaemonTest {
+
+ @Inject private ChangeIndexCollection changeIndex;
+
+ private static String propertyBeforeTest;
+
+ @BeforeClass
+ public static void setup() {
+ propertyBeforeTest = System.getProperty(IndexType.SYS_PROP);
+ System.setProperty(IndexType.SYS_PROP, "");
+ }
+
+ @AfterClass
+ public static void teardown() {
+ System.setProperty(IndexType.SYS_PROP, propertyBeforeTest);
+ }
+
+ @Test
+ public void fakeIsBoundByDefault() throws Exception {
+ assertThat(System.getProperty(IndexType.SYS_PROP)).isEmpty();
+ assertThat(changeIndex.getSearchIndex()).isInstanceOf(AbstractFakeIndex.FakeChangeIndex.class);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java
new file mode 100644
index 0000000..4122426
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.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.acceptance.testsuite.index;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.index.testing.AbstractFakeIndex;
+import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import javax.inject.Inject;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Test to check that the expected index backend was bound depending on sys/env properties. */
+public class FakeIndexBindingIT extends AbstractDaemonTest {
+
+ @Inject private ChangeIndexCollection changeIndex;
+
+ private static String propertyBeforeTest;
+
+ @BeforeClass
+ public static void setup() {
+ propertyBeforeTest = System.getProperty(IndexType.SYS_PROP);
+ System.setProperty(IndexType.SYS_PROP, "fake");
+ }
+
+ @AfterClass
+ public static void teardown() {
+ System.setProperty(IndexType.SYS_PROP, propertyBeforeTest);
+ }
+
+ @Test
+ public void fakeIsBoundWhenConfigured() throws Exception {
+ assertThat(System.getProperty(IndexType.SYS_PROP)).isEqualTo("fake");
+ assertThat(changeIndex.getSearchIndex()).isInstanceOf(AbstractFakeIndex.FakeChangeIndex.class);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java
new file mode 100644
index 0000000..31e31fd
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.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.acceptance.testsuite.index;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.lucene.LuceneChangeIndex;
+import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import javax.inject.Inject;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Test to check that the expected index backend was bound depending on sys/env properties. */
+public class LuceneIndexBindingIT extends AbstractDaemonTest {
+
+ @Inject private ChangeIndexCollection changeIndex;
+
+ private static String propertyBeforeTest;
+
+ @BeforeClass
+ public static void setup() {
+ propertyBeforeTest = System.getProperty(IndexType.SYS_PROP);
+ System.setProperty(IndexType.SYS_PROP, "lucene");
+ }
+
+ @AfterClass
+ public static void teardown() {
+ System.setProperty(IndexType.SYS_PROP, propertyBeforeTest);
+ }
+
+ @Test
+ public void luceneIsBoundWhenConfigured() throws Exception {
+ assertThat(System.getProperty(IndexType.SYS_PROP)).isEqualTo("lucene");
+ assertThat(changeIndex.getSearchIndex()).isInstanceOf(LuceneChangeIndex.class);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
index 2f64ed0..2b80601 100644
--- a/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
+++ b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
@@ -17,7 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@@ -80,7 +80,7 @@
externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
- verifyZeroInteractions(externalIdReaderSpy);
+ verifyNoInteractions(externalIdReaderSpy);
}
@Test
@@ -92,7 +92,7 @@
externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
- verifyZeroInteractions(externalIdReaderSpy);
+ verifyNoInteractions(externalIdReaderSpy);
}
@Test
@@ -142,7 +142,7 @@
externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
- verifyZeroInteractions(externalIdReaderSpy);
+ verifyNoInteractions(externalIdReaderSpy);
}
@Test
@@ -156,7 +156,7 @@
externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
- verifyZeroInteractions(externalIdReaderSpy);
+ verifyNoInteractions(externalIdReaderSpy);
}
@Test
@@ -173,7 +173,7 @@
externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
- verifyZeroInteractions(externalIdReaderSpy);
+ verifyNoInteractions(externalIdReaderSpy);
}
@Test
@@ -186,7 +186,7 @@
externalIdCache.put(oldState, allFromGit(oldState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
- verifyZeroInteractions(externalIdReaderSpy);
+ verifyNoInteractions(externalIdReaderSpy);
}
@Test
@@ -200,7 +200,7 @@
externalIdCache.put(oldState, allFromGit(oldState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
- verifyZeroInteractions(externalIdReaderSpy);
+ verifyNoInteractions(externalIdReaderSpy);
}
private ExternalIdCacheLoader createLoader(boolean allowPartial) {
diff --git a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
index f10a281..5980071 100644
--- a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
+++ b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
@@ -18,7 +18,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import com.google.gerrit.entities.Account;
@@ -125,7 +125,7 @@
assertThat(r).isNotNull();
assertThat(r.name()).isEqualTo(ident.getName());
assertThat(r.email()).isEqualTo(ident.getEmailAddress());
- verifyZeroInteractions(accountCache);
+ verifyNoInteractions(accountCache);
}
@Test
@@ -229,7 +229,7 @@
assertThat(r).isNotNull();
assertThat(r.name()).isEqualTo(ident.getName());
assertThat(r.email()).isEqualTo(ident.getEmailAddress());
- verifyZeroInteractions(accountCache);
+ verifyNoInteractions(accountCache);
}
@Test
@@ -239,7 +239,7 @@
assertThat(r).isNotNull();
assertThat(r.name()).isEqualTo(ident.getName());
assertThat(r.email()).isEqualTo(ident.getEmailAddress());
- verifyZeroInteractions(accountCache);
+ verifyNoInteractions(accountCache);
}
@Test
@@ -304,7 +304,7 @@
assertThat(r).isNotNull();
assertThat(r.name()).isEqualTo(ident.getName());
assertThat(r.email()).isEqualTo(ident.getEmailAddress());
- verifyZeroInteractions(accountCache);
+ verifyNoInteractions(accountCache);
}
@Test
diff --git a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
index 2e934e9..9130d3e 100644
--- a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
+++ b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
@@ -209,15 +209,15 @@
.add("groups", group(developers))
.add(
"project.config",
- "[submitRequirement \"Code-review\"]\n"
+ "[submit-requirement \"Code-review\"]\n"
+ " description = At least one Code Review +2\n"
- + " applicabilityExpression = branch(refs/heads/master)\n"
- + " submittabilityExpression = label(code-review, +2)\n"
- + "[submitRequirement \"api-review\"]\n"
+ + " applicableIf =branch(refs/heads/master)\n"
+ + " submittableIf = label(code-review, +2)\n"
+ + "[submit-requirement \"api-review\"]\n"
+ " description = Additional review required for API modifications\n"
- + " applicabilityExpression = commit_filepath_contains(\\\"/api/.*\\\")\n"
- + " submittabilityExpression = label(api-review, +2)\n"
- + " overrideExpression = label(build-cop-override, +1)\n"
+ + " applicableIf =commit_filepath_contains(\\\"/api/.*\\\")\n"
+ + " submittableIf = label(api-review, +2)\n"
+ + " overrideIf = label(build-cop-override, +1)\n"
+ " canOverrideInChildProjects = true\n")
.create();
@@ -257,8 +257,8 @@
.add("groups", group(developers))
.add(
"project.config",
- "[submitRequirement \"code-review\"]\n"
- + " submittabilityExpression = label(code-review, +2)\n")
+ "[submit-requirement \"code-review\"]\n"
+ + " submittableIf = label(code-review, +2)\n")
.create();
ProjectConfig cfg = read(rev);
@@ -281,12 +281,12 @@
.add("groups", group(developers))
.add(
"project.config",
- "[submitRequirement \"code-review\"]\n"
+ "[submit-requirement \"code-review\"]\n"
+ " description = At least one Code Review +2\n"
- + " submittabilityExpression = label(code-review, +2)\n"
- + "[submitRequirement \"Code-Review\"]\n"
+ + " submittableIf = label(code-review, +2)\n"
+ + "[submit-requirement \"Code-Review\"]\n"
+ " description = Another code review label\n"
- + " submittabilityExpression = label(code-review, +2)\n"
+ + " submittableIf = label(code-review, +2)\n"
+ " canOverrideInChildProjects = true\n")
.create();
@@ -317,8 +317,8 @@
.add("groups", group(developers))
.add(
"project.config",
- "[submitRequirement \"code-review\"]\n"
- + " applicabilityExpression = label(code-review, +2)\n")
+ "[submit-requirement \"code-review\"]\n"
+ + " applicableIf =label(code-review, +2)\n")
.create();
ProjectConfig cfg = read(rev);
@@ -943,10 +943,10 @@
tr.commit()
.add(
"project.config",
- "[submitRequirement \"code-review\"]\n"
+ "[submit-requirement \"code-review\"]\n"
+ " description = At least one Code Review +2\n"
- + " applicabilityExpression = branch(refs/heads/master)\n"
- + " submittabilityExpression = label(code-review, +2)\n"
+ + " applicableIf =branch(refs/heads/master)\n"
+ + " submittableIf = label(code-review, +2)\n"
+ "[notify \"name\"]\n"
+ " email = example@example.com\n")
.create();
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 3c10fbc..1f29f45 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1007,6 +1007,7 @@
changes.put(-1, reviewMinus1Change);
changes.put(-2, reviewMinus2Change);
+ assertQuery("label:Code-Review=MIN", reviewMinus2Change);
assertQuery("label:Code-Review=-2", reviewMinus2Change);
assertQuery("label:Code-Review-2", reviewMinus2Change);
assertQuery("label:Code-Review=-1", reviewMinus1Change);
@@ -1018,6 +1019,13 @@
assertQuery("label:Code-Review=+2", reviewPlus2Change);
assertQuery("label:Code-Review=2", reviewPlus2Change);
assertQuery("label:Code-Review+2", reviewPlus2Change);
+ assertQuery("label:Code-Review=MAX", reviewPlus2Change);
+ assertQuery(
+ "label:Code-Review=ANY",
+ reviewPlus2Change,
+ reviewPlus1Change,
+ reviewMinus1Change,
+ reviewMinus2Change);
assertQuery("label:Code-Review>-3", codeReviewInRange(changes, -2, 2));
assertQuery("label:Code-Review>=-2", codeReviewInRange(changes, -2, 2));
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 4a81f38..fcf1cf4 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -119,14 +119,8 @@
"elements/diff/gr-diff/gr-diff_html.ts",
"elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts",
"elements/gr-app-element_html.ts",
- "elements/settings/gr-edit-preferences/gr-edit-preferences_html.ts",
- "elements/settings/gr-email-editor/gr-email-editor_html.ts",
- "elements/settings/gr-identities/gr-identities_html.ts",
- "elements/settings/gr-registration-dialog/gr-registration-dialog_html.ts",
"elements/settings/gr-settings-view/gr-settings-view_html.ts",
"elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts",
- "elements/shared/gr-account-entry/gr-account-entry_html.ts",
- "elements/shared/gr-account-label/gr-account-label_html.ts",
"elements/shared/gr-account-list/gr-account-list_html.ts",
"elements/shared/gr-autocomplete/gr-autocomplete_html.ts",
"elements/shared/gr-change-status/gr-change-status_html.ts",
diff --git a/polygerrit-ui/app/api/checks.ts b/polygerrit-ui/app/api/checks.ts
index aed6f8d..b64cd91 100644
--- a/polygerrit-ui/app/api/checks.ts
+++ b/polygerrit-ui/app/api/checks.ts
@@ -62,7 +62,7 @@
patchsetNumber: number;
patchsetSha: string;
repo: string;
- commmitMessage?: string;
+ commitMessage?: string;
changeInfo: ChangeInfo;
}
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index 98cd4dc..7400295 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -206,6 +206,7 @@
font_size: number;
// TODO: Missing documentation
show_file_comment_button?: boolean;
+ line_wrapping?: boolean;
}
export declare interface ImageDiffPreferences {
diff --git a/polygerrit-ui/app/api/hook.ts b/polygerrit-ui/app/api/hook.ts
index deb8566..f8a6cc1 100644
--- a/polygerrit-ui/app/api/hook.ts
+++ b/polygerrit-ui/app/api/hook.ts
@@ -16,7 +16,7 @@
*/
import {ChangeInfo, ConfigInfo, RevisionInfo} from './rest-api';
-interface GerritElementExtensions {
+export interface GerritElementExtensions {
content?: HTMLElement & {hidden?: boolean};
change?: ChangeInfo;
revision?: RevisionInfo;
@@ -25,17 +25,21 @@
config?: ConfigInfo;
}
-export type HookCallback = (el: HTMLElement & GerritElementExtensions) => void;
+export type PluginElement = HTMLElement & GerritElementExtensions;
+
+export type HookCallback<T extends PluginElement> = (el: T) => void;
export declare interface RegisterOptions {
+ /** Defaults to empty string. */
slot?: string;
- replace: boolean;
+ /** Defaults to false. */
+ replace?: boolean;
}
-export declare interface HookApi {
- onAttached(callback: HookCallback): HookApi;
+export declare interface HookApi<T extends PluginElement> {
+ onAttached(callback: HookCallback<T>): HookApi<T>;
- onDetached(callback: HookCallback): HookApi;
+ onDetached(callback: HookCallback<T>): HookApi<T>;
getAllAttached(): HTMLElement[];
diff --git a/polygerrit-ui/app/api/plugin.ts b/polygerrit-ui/app/api/plugin.ts
index 0c91546..7a56ff7 100644
--- a/polygerrit-ui/app/api/plugin.ts
+++ b/polygerrit-ui/app/api/plugin.ts
@@ -58,22 +58,25 @@
checks(): ChecksPluginApi;
eventHelper(element: Node): EventHelperPluginApi;
getPluginName(): string;
- hook(endpointName: string, opt_options?: RegisterOptions): HookApi;
+ hook<T extends HTMLElement>(
+ endpointName: string,
+ opt_options?: RegisterOptions
+ ): HookApi<T>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(eventName: EventType, target: any): void;
popup(): Promise<PopupPluginApi>;
popup(moduleName: string): Promise<PopupPluginApi>;
popup(moduleName?: string): Promise<PopupPluginApi | null>;
- registerCustomComponent(
+ registerCustomComponent<T extends HTMLElement>(
endpointName: string,
moduleName?: string,
options?: RegisterOptions
- ): HookApi;
- registerDynamicCustomComponent(
+ ): HookApi<T>;
+ registerDynamicCustomComponent<T extends HTMLElement>(
endpointName: string,
moduleName?: string,
options?: RegisterOptions
- ): HookApi;
+ ): HookApi<T>;
registerStyleModule(endpoint: string, moduleName: string): void;
reporting(): ReportingPluginApi;
restApi(): RestPluginApi;
diff --git a/polygerrit-ui/app/api/rest.ts b/polygerrit-ui/app/api/rest.ts
index 2b91bf6..4c93ef0 100644
--- a/polygerrit-ui/app/api/rest.ts
+++ b/polygerrit-ui/app/api/rest.ts
@@ -78,29 +78,29 @@
/**
* Fetch and parse REST API response, if request succeeds.
*/
- send(
+ send<T>(
method: HttpMethod,
url: string,
payload?: RequestPayload,
errFn?: ErrorCallback,
contentType?: string
- ): Promise<unknown>;
+ ): Promise<T>;
- get(url: string): Promise<unknown>;
+ get<T>(url: string): Promise<T>;
- post(
+ post<T>(
url: string,
payload?: RequestPayload,
errFn?: ErrorCallback,
contentType?: string
- ): Promise<unknown>;
+ ): Promise<T>;
- put(
+ put<T>(
url: string,
payload?: RequestPayload,
errFn?: ErrorCallback,
contentType?: string
- ): Promise<unknown>;
+ ): Promise<T>;
delete(url: string): Promise<Response>;
}
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 ca68926..07f9a4a 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
@@ -184,7 +184,7 @@
<gr-account-chip
account="[[change.owner]]"
change="[[change]]"
- highlight-attention
+ highlightAttention
></gr-account-chip>
<template is="dom-if" if="[[_pushCertificateValidation]]">
<gr-tooltip-content
@@ -213,7 +213,7 @@
<gr-account-chip
account="[[_getNonOwnerRole(change, _CHANGE_ROLE.UPLOADER)]]"
change="[[change]]"
- highlight-attention
+ highlightAttention
></gr-account-chip>
</span>
</section>
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 39a80e6..53bdb91 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
@@ -76,8 +76,9 @@
PatchSet,
} from '../../../utils/patch-set-util';
import {
- changeIsMerged,
changeIsAbandoned,
+ changeIsMerged,
+ changeIsOpen,
changeStatuses,
isCc,
isOwner,
@@ -676,7 +677,6 @@
);
this.addEventListener('open-fix-preview', e => this._onOpenFixPreview(e));
this.addEventListener('close-fix-preview', e => this._onCloseFixPreview(e));
- window.addEventListener('scroll', this.handleScroll);
document.addEventListener('visibilitychange', this.handleVisibilityChange);
this.addEventListener(EventType.SHOW_PRIMARY_TAB, e =>
@@ -693,7 +693,6 @@
/** @override */
disconnectedCallback() {
this.disconnected$.next();
- window.removeEventListener('scroll', this.handleScroll);
document.removeEventListener(
'visibilitychange',
this.handleVisibilityChange
@@ -1139,14 +1138,6 @@
this._openReplyDialog(target);
}
- readonly handleScroll = () => {
- this.scrollTask = debounce(
- this.scrollTask,
- () => (this.viewState.scrollTop = document.body.scrollTop),
- 150
- );
- };
-
_setShownFiles(e: CustomEvent<{length: number}>) {
this._shownFileCount = e.detail.length;
}
@@ -1283,11 +1274,7 @@
this._sendShowChangeEvent();
setTimeout(() => {
- if (this.viewState.scrollTop) {
- document.documentElement.scrollTop = document.body.scrollTop = this.viewState.scrollTop;
- } else {
- this._maybeScrollToMessage(window.location.hash);
- }
+ this._maybeScrollToMessage(window.location.hash);
this._initialLoadComplete = true;
});
}
@@ -1407,7 +1394,6 @@
_resetFileListViewState() {
this.set('viewState.selectedFileIndex', 0);
- this.set('viewState.scrollTop', 0);
if (
!!this.viewState.changeNum &&
this.viewState.changeNum !== this._changeNum
@@ -1768,6 +1754,17 @@
*/
_processEdit(change: ParsedChangeInfo, edit?: EditInfo | false) {
if (
+ !edit &&
+ this._patchRange?.patchNum === EditPatchSetNum &&
+ changeIsOpen(change)
+ ) {
+ fireAlert(this, 'Change edit not found. Please create a change edit.');
+ GerritNav.navigateToChange(change);
+ return;
+ }
+
+ if (
+ !edit &&
(changeIsMerged(change) || changeIsAbandoned(change)) &&
this._editMode
) {
@@ -1970,13 +1967,32 @@
_getCommitInfo() {
if (!this._changeNum)
- throw new Error('missing required changeNum property');
+ throw new Error('missing required _changeNum property');
if (!this._patchRange)
throw new Error('missing required _patchRange property');
if (this._patchRange.patchNum === undefined)
throw new Error('missing required patchNum property');
+
+ // We only call _getEdit if the patchset number is an edit.
+ // We have to do this to ensure we can tell if an edit
+ // exists or not.
+ // This safely works even if a edit does not exist.
+ if (this._patchRange!.patchNum! === EditPatchSetNum) {
+ return this._getEdit().then(edit => {
+ if (!edit) {
+ return Promise.resolve();
+ }
+
+ return this._getChangeCommitInfo();
+ });
+ }
+
+ return this._getChangeCommitInfo();
+ }
+
+ _getChangeCommitInfo() {
return this.restApiService
- .getChangeCommitInfo(this._changeNum, this._patchRange.patchNum)
+ .getChangeCommitInfo(this._changeNum!, this._patchRange!.patchNum!)
.then(commitInfo => {
this._commitInfo = commitInfo;
});
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 5b940a8..c6975e6 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
@@ -344,7 +344,7 @@
<span class="headerSubject">[[_change.subject]]</span>
<gr-copy-clipboard
class="changeCopyClipboard"
- hide-input=""
+ hideInput=""
text="[[_computeCopyTextForTitle(_change)]]"
>
</gr-copy-clipboard>
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 e882920..867b3a7 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
@@ -128,8 +128,6 @@
TestKeyboardShortcutBinder.pop();
});
- const TEST_SCROLL_TOP_PX = 100;
-
const ROBOT_COMMENTS_LIMIT = 10;
// TODO: should have a mock service to generate VALID fake data
@@ -1805,42 +1803,6 @@
assert.isTrue(awaitPluginsLoadedStub.called);
});
- suite('scroll related tests', () => {
- test('document scrolling calls function to set scroll height', done => {
- const originalHeight = document.body.scrollHeight;
- const scrollStub = sinon.stub(element, 'handleScroll').callsFake(() => {
- assert.isTrue(scrollStub.called);
- document.body.style.height = `${originalHeight}px`;
- scrollStub.restore();
- done();
- });
- document.body.style.height = '10000px';
- element.handleScroll();
- });
-
- test('scrollTop is set correctly', async () => {
- element.viewState = {scrollTop: TEST_SCROLL_TOP_PX};
-
- sinon.stub(element, 'loadData').callsFake(() => {
- // When element is reloaded, ensure that the history
- // state has the scrollTop set earlier. This will then
- // be reset.
- assert.isTrue(element.viewState.scrollTop === TEST_SCROLL_TOP_PX);
- return Promise.resolve([]);
- });
-
- // simulate reloading component, which is done when route
- // changes to match a regex of change view type.
- element.params = {...createAppElementChangeViewParams()};
- await flush();
- });
-
- test('scrollTop is reset when new change is loaded', () => {
- element._resetFileListViewState();
- assert.equal(element.viewState.scrollTop, 0);
- });
- });
-
suite('reply dialog tests', () => {
setup(() => {
sinon.stub(element.$.replyDialog, '_draftChanged');
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_html.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_html.ts
index 65ca8b5..02fa090 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_html.ts
@@ -31,9 +31,9 @@
>[[_computeShortHash(change, commitInfo, serverConfig)]]</a
>
<gr-copy-clipboard
- has-tooltip=""
- button-title="Copy full SHA to clipboard"
- hide-input=""
+ hasTooltip=""
+ buttonTitle="Copy full SHA to clipboard"
+ hideInput=""
text="[[commitInfo.commit]]"
>
</gr-copy-clipboard>
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 71b4ac4..ff303e3 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
@@ -31,8 +31,6 @@
import {
AccountInfo,
ChangeInfo,
- EditPatchSetNum,
- ParentPatchSetNum,
PatchSetNum,
CommitInfo,
ServerInfo,
@@ -189,10 +187,6 @@
) {
return;
}
- if (patchNum === EditPatchSetNum && basePatchNum === ParentPatchSetNum) {
- GerritNav.navigateToChange(this.change, undefined, undefined, true);
- return;
- }
GerritNav.navigateToChange(this.change, patchNum, basePatchNum);
}
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
index 9bc243d..dd70678 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
@@ -22,7 +22,6 @@
import 'lodash/lodash.js';
import {createRevisions} from '../../../test/test-data-generators.js';
import {stubRestApi} from '../../../test/test-utils.js';
-import {EditPatchSetNum, ParentPatchSetNum} from '../../../types/common.js';
const basicFixture = fixtureFromElement('gr-file-list-header');
@@ -162,34 +161,6 @@
.calledWithExactly(element.change, 3, 1));
});
- test('navigateToChange called when range select changes with edit', () => {
- const navigateToChangeStub = sinon.stub(GerritNav, 'navigateToChange');
- element.change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- revisions: {
- rev2: {_number: 2},
- rev1: {_number: 1},
- rev13: {_number: 13},
- rev3: {_number: 3},
- },
- status: 'NEW',
- labels: {},
- };
- element.basePatchNum = 1;
- element.patchNum = EditPatchSetNum;
-
- const detail = {
- detail: {
- basePatchNum: ParentPatchSetNum,
- patchNum: EditPatchSetNum,
- },
- };
- element._handlePatchChange(detail);
- assert.equal(navigateToChangeStub.callCount, 1);
- assert.isTrue(navigateToChangeStub.lastCall
- .calledWithExactly(element.change, undefined, undefined, true));
- });
-
test('class is applied to file list on old patch set', () => {
const allPatchSets = [{num: 4}, {num: 2}, {num: 1}];
assert.equal(element._computePatchInfoClass(1, allPatchSets),
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 106fe27..9dcb67e 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
@@ -63,6 +63,7 @@
import {customElement, observe, property} from '@polymer/decorators';
import {
BasePatchSetNum,
+ EditPatchSetNum,
ElementPropertyDeepChange,
FileInfo,
FileNameToFileInfoMap,
@@ -500,12 +501,7 @@
}
@observe('_filesByPath')
- async _updateCleanlyMergedPaths(
- // changeNum?: NumericChangeId,
- // change?: ParsedChangeInfo,
- // patchRange?: PatchRange,
- filesByPath?: FileNameToFileInfoMap
- ) {
+ async _updateCleanlyMergedPaths(filesByPath?: FileNameToFileInfoMap) {
// When viewing Auto Merge base vs a patchset, add an additional row that
// knows how many files were cleanly merged. This requires an additional RPC
// for the diffs between target parent and the patch set. The cleanly merged
@@ -516,7 +512,8 @@
this.changeNum &&
this.patchRange?.patchNum &&
new RevisionInfo(this.change).isMergeCommit(this.patchRange.patchNum) &&
- this.patchRange.basePatchNum === 'PARENT'
+ this.patchRange.basePatchNum === 'PARENT' &&
+ this.patchRange.patchNum !== EditPatchSetNum
) {
const allFilesByPath = await this.restApiService.getChangeOrEditFiles(
this.changeNum,
@@ -680,7 +677,8 @@
patchRange?: PatchRange,
file?: NormalizedFileInfo
) {
- const draftCount = changeComments?.computeDraftCountForFile(
+ if (changeComments === undefined) return '';
+ const draftCount = changeComments.computeDraftCountForFile(
patchRange,
file
);
@@ -696,7 +694,8 @@
patchRange?: PatchRange,
file?: NormalizedFileInfo
) {
- const draftCount = changeComments?.computeDraftCountForFile(
+ if (changeComments === undefined) return '';
+ const draftCount = changeComments.computeDraftCountForFile(
patchRange,
file
);
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 3b2cacf..4d04744 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
@@ -410,7 +410,7 @@
</span>
<gr-file-status-chip file="[[file]]"></gr-file-status-chip>
<gr-copy-clipboard
- hide-input=""
+ hideInput=""
text="[[file.__path]]"
></gr-copy-clipboard>
</a>
@@ -418,7 +418,7 @@
<div class="oldPath" title$="[[file.old_path]]">
[[file.old_path]]
<gr-copy-clipboard
- hide-input=""
+ hideInput=""
text="[[file.old_path]]"
></gr-copy-clipboard>
</div>
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 c32e3e5..0655721 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
@@ -33,6 +33,7 @@
mockPromise,
query,
} from '../../../test/test-utils.js';
+import {EditPatchSetNum} from '../../../types/common.js';
import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
import {createCommentThreads} from '../../../utils/comment-util.js';
import {
@@ -1232,6 +1233,15 @@
assert.notOk(query(element, '.cleanlyMergedText'));
assert.notOk(query(element, '.showParentButton'));
});
+
+ test('not shown in edit mode', async () => {
+ element.patchRange = {basePatchNum: 1, patchNum: EditPatchSetNum};
+ await element.reload();
+ await flush();
+
+ assert.notOk(query(element, '.cleanlyMergedText'));
+ assert.notOk(query(element, '.showParentButton'));
+ });
});
});
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 1711499..b097340 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -121,6 +121,9 @@
@property({type: Object})
message: ChangeMessage | undefined;
+ @property({type: Array})
+ commentThreads: CommentThread[] = [];
+
@computed('message')
get author() {
return this.message?.author || this.message?.updated_by;
@@ -195,13 +198,13 @@
'_computeMessageContentCollapsed(message.message,' +
' message.accounts_in_message,' +
' message.tag,' +
- ' message.commentThreads)',
+ ' commentThreads)',
})
_messageContentCollapsed = '';
@property({
type: String,
- computed: '_computeCommentCountText(message.commentThreads.length)',
+ computed: '_computeCommentCountText(commentThreads)',
})
_commentCountText = '';
@@ -234,12 +237,12 @@
}
}
- _computeCommentCountText(threadsLength?: number) {
- if (!threadsLength) {
+ _computeCommentCountText(commentThreads?: CommentThread[]) {
+ if (!commentThreads?.length) {
return undefined;
}
- return pluralize(threadsLength, 'comment');
+ return pluralize(commentThreads.length, 'comment');
}
_computeMessageContentExpanded(
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 e58b8a8..9e24a09 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
@@ -195,10 +195,8 @@
padding-bottom: 1px;
color: var(--vote-text-color);
}
- gr-account-label {
- --gr-account-label-text-style: {
- font-weight: var(--font-weight-bold);
- }
+ gr-account-label::part(gr-account-label-text) {
+ font-weight: var(--font-weight-bold);
}
iron-icon {
--iron-icon-height: 20px;
@@ -251,7 +249,7 @@
<div class="content messageContent">
<div class="message hideOnOpen">[[_messageContentCollapsed]]</div>
<gr-formatted-text
- no-trailing-margin=""
+ noTrailingMargin
class="message hideOnCollapsed"
content="[[_messageContentExpanded]]"
config="[[_projectConfig.commentlinks]]"
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 4e2aaf1..fae624e 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
@@ -90,9 +90,9 @@
*/
function computeThreads(
message: CombinedMessage,
- changeComments: ChangeComments
+ changeComments?: ChangeComments
): CommentThread[] {
- if (message._index === undefined) {
+ if (message._index === undefined || changeComments === undefined) {
return [];
}
const messageId = getMessageId(message);
@@ -369,6 +369,10 @@
return combinedMessages;
}
+ getCommentThreads(message: CombinedMessage, changeComments?: ChangeComments) {
+ return computeThreads(message, changeComments);
+ }
+
_updateExpandedStateOfAllMessages(exp: boolean) {
if (this._combinedMessages) {
for (let i = 0; i < this._combinedMessages.length; i++) {
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.ts
index 8fa8eab..93df77e 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.ts
@@ -93,6 +93,7 @@
change="[[change]]"
change-num="[[changeNum]]"
message="[[message]]"
+ comment-threads="[[getCommentThreads(message, changeComments)]]"
project-name="[[projectName]]"
show-reply-button="[[showReplyButtons]]"
on-message-anchor-tap="_handleAnchorClick"
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
index 25d8517..ca8bf87 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
@@ -66,7 +66,7 @@
account="[[reviewer]]"
change="[[change]]"
on-remove="_handleRemove"
- highlight-attention
+ highlightAttention
voteable-text="[[_computeVoteableText(reviewer, change)]]"
removable="[[_computeCanRemoveReviewer(reviewer, mutable)]]"
>
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.ts
index 354d360..d3f23ad 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.ts
@@ -67,7 +67,7 @@
await dialogShown;
});
- test('only show remove for removable reviewers', () => {
+ test('only show remove for removable reviewers', async () => {
element.mutable = true;
element.change = {
...createChange(),
@@ -112,7 +112,7 @@
},
],
};
- flush();
+ await flush();
const chips = element.root!.querySelectorAll('gr-account-chip');
assert.equal(chips.length, 4);
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index b348eb5..f27a383 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -588,10 +588,10 @@
.value="${this.result}"
></gr-endpoint-param>
<gr-formatted-text
- no-trailing-margin=""
+ noTrailingMargin
class="message"
- content="${this.result.message}"
- config="${this.repoConfig}"
+ .content="${this.result.message}"
+ .config="${this.repoConfig?.commentlinks}"
></gr-formatted-text>
</gr-endpoint-decorator>
`;
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 015fdf3..7532101 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -51,7 +51,6 @@
import {
BasePatchSetNum,
DashboardId,
- EditPatchSetNum,
GroupId,
NumericChangeId,
PatchSetNum,
@@ -1601,15 +1600,6 @@
queryMap: ctx.queryMap,
};
- // We do not want to allow "edit" to be used as a
- // patch number. Instead redirect to ,edit.
- if (ctx.params[4] === EditPatchSetNum && !ctx.params[6]) {
- params.basePatchNum = undefined;
- params.edit = true;
- this._redirect(this._generateUrl(params));
- return;
- }
-
this.reporting.setRepoName(params.project);
this.reporting.setChangeId(changeNum);
this._redirectOrNavigate(params);
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 c4674d2..6e80a35 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
@@ -22,7 +22,6 @@
import {stubBaseUrl, stubRestApi, addListenerForTest} from '../../../test/test-utils.js';
import {_testOnly_RoutePattern} from './gr-router.js';
import {GerritView} from '../../../services/router/router-model.js';
-import {EditPatchSetNum} from '../../../types/common.js';
const basicFixture = fixtureFromElement('gr-router');
@@ -1430,16 +1429,16 @@
suite('_handleChangeRoute', () => {
let normalizeRangeStub;
- function makeParams(path, hash, baseNum, patchNum) {
+ function makeParams(path, hash) {
return {
params: [
'foo/bar', // 0 Project
1234, // 1 Change number
null, // 2 Unused
null, // 3 Unused
- baseNum ? baseNum : 4, // 4 Base patch number
+ 4, // 4 Base patch number
null, // 5 Unused
- patchNum ? patchNum : 7, // 6 Patch number
+ 7, // 6 Patch number
],
queryMap: new Map(),
};
@@ -1477,23 +1476,6 @@
assert.isFalse(redirectStub.called);
assert.isTrue(normalizeRangeStub.called);
});
-
- test('redirect due to patchNum being an edit', () => {
- normalizeRangeStub.returns(true);
- const ctx = makeParams(null, '');
- element._handleChangeRoute(ctx, undefined, EditPatchSetNum, false);
- assert.isTrue(normalizeRangeStub.called);
- assert.isFalse(setParamsStub.called);
- assert.isTrue(redirectStub.calledOnce);
-
- const params = {
- view: GerritView.CHANGE,
- changeNum: '1234',
- project: 'test',
- edit: true,
- };
- assert.equal(element._generateUrl(params), '/c/test/+/1234,edit');
- });
});
suite('_handleDiffRoute', () => {
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 0929301..315fbfb 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
@@ -42,6 +42,7 @@
isInBaseOfPatchRange,
isInRevisionOfPatchRange,
isPatchsetLevel,
+ addPath,
} from '../../../utils/comment-util';
import {PatchSetFile, PatchNumOnly, isPatchSetFile} from '../../../types/types';
import {appContext} from '../../../services/app-context';
@@ -75,35 +76,13 @@
portedComments: PathToCommentsInfoMap | undefined,
portedDrafts: PathToCommentsInfoMap | undefined
) {
- this._comments = this._addPath(comments);
- this._robotComments = this._addPath(robotComments);
- this._drafts = this._addPath(drafts);
+ this._comments = addPath(comments);
+ this._robotComments = addPath(robotComments);
+ this._drafts = addPath(drafts);
this._portedComments = portedComments || {};
this._portedDrafts = portedDrafts || {};
}
- /**
- * Add path info to every comment as CommentInfo returned
- * from server does not have that.
- *
- * TODO(taoalpha): should consider changing BE to send path
- * back within CommentInfo
- */
- _addPath<T>(
- comments: {[path: string]: T[]} = {}
- ): {[path: string]: Array<T & {path: string}>} {
- const updatedComments: {[path: string]: Array<T & {path: string}>} = {};
- for (const filePath of Object.keys(comments)) {
- const allCommentsForPath = comments[filePath] || [];
- if (allCommentsForPath.length) {
- updatedComments[filePath] = allCommentsForPath.map(comment => {
- return {...comment, path: filePath};
- });
- }
- }
- return updatedComments;
- }
-
get drafts() {
return this._drafts;
}
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 9da3bf1..d01943c 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
@@ -429,9 +429,16 @@
}
/** Support the line length indicator **/
.full-width td.content .contentText {
- background-image: var(--line-length-indicator);
+ /*
+ Same strategy as in https://stackoverflow.com/questions/1179928/how-can-i-put-a-vertical-line-down-the-center-of-a-div
+ */
+ background-image: linear-gradient(
+ var(--line-length-indicator-color),
+ var(--line-length-indicator-color)
+ );
+ background-size: 1px 100%;
background-position: var(--line-limit) 0;
- background-repeat: repeat-y;
+ background-repeat: no-repeat;
}
.newlineWarning {
color: var(--deemphasized-text-color);
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 e61d0b8..f0a3ca1 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
@@ -36,6 +36,7 @@
} from '../../shared/gr-autocomplete/gr-autocomplete';
import {appContext} from '../../../services/app-context';
import {IronInputElement} from '@polymer/iron-input';
+import {fireAlert} from '../../../utils/event-util';
export interface GrEditControls {
$: {
@@ -176,11 +177,15 @@
'.dialog'
) as NodeListOf<GrDialog>;
for (const dialog of dialogs) {
- this._closeDialog(dialog);
+ // We set the second param to false, because this function
+ // is called by _showDialog which when you open either restore,
+ // delete or rename dialogs, it reseted the automatically
+ // set input.
+ this._closeDialog(dialog, false);
}
}
- _closeDialog(dialog?: GrDialog, clearInputs = false) {
+ _closeDialog(dialog?: GrDialog, clearInputs = true) {
if (!dialog) return;
if (clearInputs) {
@@ -205,18 +210,24 @@
}
_handleOpenConfirm(e: Event) {
+ if (!this.change || !this._path) {
+ fireAlert(this, 'You must enter a path.');
+ this._closeDialog(this.$.openDialog);
+ return;
+ }
const url = GerritNav.getEditUrlForDiff(
this.change,
this._path,
this.patchNum
);
GerritNav.navigateToRelativeUrl(url);
- this._closeDialog(this._getDialogFromEvent(e), true);
+ this._closeDialog(this._getDialogFromEvent(e));
}
_handleUploadConfirm(path: string, fileData: string) {
if (!this.change || !path || !fileData) {
- this._closeDialog(this.$.openDialog, true);
+ fireAlert(this, 'You must enter a path and data.');
+ this._closeDialog(this.$.openDialog);
return Promise.resolve();
}
return this.restApiService
@@ -225,7 +236,7 @@
if (!res || !res.ok) {
return;
}
- this._closeDialog(this.$.openDialog, true);
+ this._closeDialog(this.$.openDialog);
GerritNav.navigateToChange(this.change);
});
}
@@ -234,39 +245,54 @@
// Get the dialog before the api call as the event will change during bubbling
// which will make Polymer.dom(e).path an empty array in polymer 2
const dialog = this._getDialogFromEvent(e);
+ if (!this.change || !this._path) {
+ fireAlert(this, 'You must enter a path.');
+ this._closeDialog(dialog);
+ return;
+ }
this.restApiService
.deleteFileInChangeEdit(this.change._number, this._path)
.then(res => {
if (!res || !res.ok) {
return;
}
- this._closeDialog(dialog, true);
+ this._closeDialog(dialog);
GerritNav.navigateToChange(this.change);
});
}
_handleRestoreConfirm(e: Event) {
const dialog = this._getDialogFromEvent(e);
+ if (!this.change || !this._path) {
+ fireAlert(this, 'You must enter a path.');
+ this._closeDialog(dialog);
+ return;
+ }
this.restApiService
.restoreFileInChangeEdit(this.change._number, this._path)
.then(res => {
if (!res || !res.ok) {
return;
}
- this._closeDialog(dialog, true);
+ this._closeDialog(dialog);
GerritNav.navigateToChange(this.change);
});
}
_handleRenameConfirm(e: Event) {
const dialog = this._getDialogFromEvent(e);
+ if (!this.change || !this._path || !this._newPath) {
+ fireAlert(this, 'You must enter a old path and a new path.');
+ this._closeDialog(dialog);
+ return;
+ }
return this.restApiService
.renameFileInChangeEdit(this.change._number, this._path, this._newPath)
.then(res => {
if (!res || !res.ok) {
return;
}
- this._closeDialog(dialog, true);
+ this._closeDialog(dialog);
GerritNav.navigateToChange(this.change);
});
}
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
index d9f5cd7..a0b5392 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
@@ -117,7 +117,7 @@
assert.isFalse(editDiffStub.called);
assert.isFalse(navStub.called);
assert.isTrue(closeDialogSpy.called);
- assert.equal(element._path, 'src/test.cpp');
+ assert.equal(element._path, '');
});
});
});
@@ -202,7 +202,7 @@
);
assert.isFalse(navStub.called);
assert.isTrue(closeDialogSpy.called);
- assert.equal(element._path, 'src/test.cpp');
+ assert.equal(element._path, '');
});
});
});
@@ -296,8 +296,8 @@
);
assert.isFalse(navStub.called);
assert.isTrue(closeDialogSpy.called);
- assert.equal(element._path, 'src/test.cpp');
- assert.equal(element._newPath, 'src/test.newPath');
+ assert.equal(element._path, '');
+ assert.equal(element._newPath, '');
});
});
});
@@ -365,7 +365,7 @@
);
assert.isFalse(navStub.called);
assert.isTrue(closeDialogSpy.called);
- assert.equal(element._path, 'src/test.cpp');
+ assert.equal(element._path, '');
});
});
});
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index c9f0506..ebd143c 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -295,7 +295,6 @@
showDownloadDialog: false,
diffMode: null,
numFilesShown: null,
- scrollTop: 0,
},
changeListView: {
query: null,
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.ts b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.ts
index 3e8f0a4..3613f4c 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.ts
@@ -16,10 +16,10 @@
*/
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {PluginApi} from '../../../api/plugin';
-import {HookApi, HookCallback} from '../../../api/hook';
+import {HookApi, HookCallback, PluginElement} from '../../../api/hook';
export class GrDomHooksManager {
- private hooks: Record<string, GrDomHook>;
+ private hooks: Record<string, GrDomHook<PluginElement>>;
private plugin: PluginApi;
@@ -41,23 +41,33 @@
}
}
- getDomHook(endpointName: string, moduleName?: string) {
+ getDomHook<T extends PluginElement>(
+ endpointName: string,
+ moduleName?: string
+ ): HookApi<T> {
const hookName = this._getHookName(endpointName, moduleName);
if (!this.hooks[hookName]) {
- this.hooks[hookName] = new GrDomHook(hookName, moduleName);
+ this.hooks[hookName] = (new GrDomHook<T>(
+ hookName,
+ moduleName
+ ) as unknown) as GrDomHook<PluginElement>;
}
- return this.hooks[hookName];
+ return (this.hooks[hookName] as unknown) as GrDomHook<T>;
}
}
-export class GrDomHook implements HookApi {
+export class GrDomHook<T extends PluginElement> implements HookApi<T> {
private instances: HTMLElement[] = [];
- private attachCallbacks: HookCallback[] = [];
+ private attachCallbacks: HookCallback<T>[] = [];
- private detachCallbacks: HookCallback[] = [];
+ private detachCallbacks: HookCallback<T>[] = [];
- private moduleName: string;
+ /**
+ * The name of the (custom) element that is going to be created. Matches the T
+ * type parameter.
+ */
+ private readonly moduleName: string;
private lastAttachedPromise: Promise<HTMLElement> | null = null;
@@ -87,7 +97,7 @@
customElements.define(HookPlaceholder.is, HookPlaceholder);
}
- handleInstanceDetached(instance: HTMLElement) {
+ handleInstanceDetached(instance: T) {
const index = this.instances.indexOf(instance);
if (index !== -1) {
this.instances.splice(index, 1);
@@ -95,7 +105,7 @@
this.detachCallbacks.forEach(callback => callback(instance));
}
- handleInstanceAttached(instance: HTMLElement) {
+ handleInstanceAttached(instance: T) {
this.instances.push(instance);
this.attachCallbacks.forEach(callback => callback(instance));
}
@@ -109,7 +119,7 @@
return Promise.resolve(this.instances.slice(-1)[0]);
}
if (!this.lastAttachedPromise) {
- let resolve: HookCallback;
+ let resolve: HookCallback<T>;
const promise = new Promise<HTMLElement>(r => {
resolve = r;
this.attachCallbacks.push(resolve);
@@ -137,7 +147,7 @@
* Install a new callback to invoke when a new instance of DOM hook element
* is attached.
*/
- onAttached(callback: HookCallback) {
+ onAttached(callback: HookCallback<T>) {
this.attachCallbacks.push(callback);
return this;
}
@@ -147,7 +157,7 @@
* is detached.
*
*/
- onDetached(callback: HookCallback) {
+ onDetached(callback: HookCallback<T>) {
this.detachCallbacks.push(callback);
return this;
}
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 0e8cbcb..4776eac 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
@@ -23,7 +23,7 @@
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {customElement, property} from '@polymer/decorators';
import {PluginApi} from '../../../api/plugin';
-import {HookApi} from '../../../api/hook';
+import {HookApi, PluginElement} from '../../../api/hook';
const INIT_PROPERTIES_TIMEOUT_MS = 10000;
@@ -37,7 +37,7 @@
name!: string;
@property({type: Object})
- _domHooks = new Map<HTMLElement, HookApi>();
+ _domHooks = new Map<PluginElement, HookApi<PluginElement>>();
@property({type: Object})
_initializedPlugins = new Map<string, boolean>();
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 5e2c5cb..4909bef 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
@@ -26,6 +26,9 @@
export interface GrEditPreferences {
$: {
+ editTabWidth: HTMLInputElement;
+ editColumns: HTMLInputElement;
+ editIndentUnit: HTMLInputElement;
editSyntaxHighlighting: HTMLInputElement;
showAutoCloseBrackets: HTMLInputElement;
showIndentWithTabs: HTMLInputElement;
@@ -58,6 +61,21 @@
this.hasUnsavedChanges = true;
}
+ _handleEditTabWidthChanged() {
+ this.set('editPrefs.tab_size', Number(this.$.editTabWidth.value));
+ this._handleEditPrefsChanged();
+ }
+
+ _handleEditLineLengthChanged() {
+ this.set('editPrefs.line_length', Number(this.$.editColumns.value));
+ this._handleEditPrefsChanged();
+ }
+
+ _handleEditIndentUnitChanged() {
+ this.set('editPrefs.indent_unit', Number(this.$.editIndentUnit.value));
+ this._handleEditPrefsChanged();
+ }
+
_handleEditSyntaxHighlightingChanged() {
this.set(
'editPrefs.syntax_highlighting',
@@ -101,6 +119,16 @@
this.hasUnsavedChanges = false;
});
}
+
+ /**
+ * bind-value has type string so we have to convert
+ * anything inputed to string.
+ *
+ * This is so typescript checker doesn't fail.
+ */
+ _convertToString(key?: number) {
+ return key !== undefined ? String(key) : '';
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_html.ts b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_html.ts
index f6344f0..a51deaf 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_html.ts
@@ -28,23 +28,11 @@
<label for="editTabWidth" class="title">Tab width</label>
<span class="value">
<iron-input
- type="number"
- prevent-invalid-input=""
allowed-pattern="[0-9]"
- bind-value="{{editPrefs.tab_size}}"
- on-keypress="_handleEditPrefsChanged"
- on-change="_handleEditPrefsChanged"
+ bind-value="[[_convertToString(editPrefs.tab_size)]]"
+ on-change="_handleEditTabWidthChanged"
>
- <input
- is="iron-input"
- id="editTabWidth"
- type="number"
- prevent-invalid-input=""
- allowed-pattern="[0-9]"
- bind-value="{{editPrefs.tab_size}}"
- on-keypress="_handleEditPrefsChanged"
- on-change="_handleEditPrefsChanged"
- />
+ <input id="editTabWidth" type="number" />
</iron-input>
</span>
</section>
@@ -52,47 +40,23 @@
<label for="editColumns" class="title">Columns</label>
<span class="value">
<iron-input
- type="number"
- prevent-invalid-input=""
allowed-pattern="[0-9]"
- bind-value="{{editPrefs.line_length}}"
- on-keypress="_handleEditPrefsChanged"
- on-change="_handleEditPrefsChanged"
+ bind-value="[[_convertToString(editPrefs.line_length)]]"
+ on-change="_handleEditLineLengthChanged"
>
- <input
- id="editColumns"
- is="iron-input"
- type="number"
- prevent-invalid-input=""
- allowed-pattern="[0-9]"
- bind-value="{{editPrefs.line_length}}"
- on-keypress="_handleEditPrefsChanged"
- on-change="_handleEditPrefsChanged"
- />
+ <input id="editColumns" type="number" />
</iron-input>
</span>
</section>
<section>
- <label for="indentUnit" class="title">Indent unit</label>
+ <label for="editIndentUnit" class="title">Indent unit</label>
<span class="value">
<iron-input
- type="number"
- prevent-invalid-input=""
allowed-pattern="[0-9]"
- bind-value="{{editPrefs.indent_unit}}"
- on-keypress="_handleEditPrefsChanged"
- on-change="_handleEditPrefsChanged"
+ bind-value="[[_convertToString(editPrefs.indent_unit)]]"
+ on-change="_handleEditIndentUnitChanged"
>
- <input
- is="iron-input"
- id="indentUnit"
- type="number"
- prevent-invalid-input=""
- allowed-pattern="[0-9]"
- bind-value="{{editPrefs.indent_unit}}"
- on-keypress="_handleEditPrefsChanged"
- on-change="_handleEditPrefsChanged"
- />
+ <input id="indentUnit" type="number" />
</iron-input>
</span>
</section>
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 74c6ac0..7f25a86 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
@@ -105,6 +105,10 @@
}
}
}
+
+ _checkPreferred(preferred?: boolean) {
+ return preferred ?? false;
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_html.ts b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_html.ts
index 0591e4b..666afb7 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_html.ts
@@ -71,12 +71,10 @@
checked$="[[item.preferred]]"
>
<input
- is="iron-input"
class="preferredRadio"
type="radio"
on-change="_handlePreferredChange"
name="preferred"
- value="[[item.email]]"
checked$="[[item.preferred]]"
/>
</iron-input>
@@ -85,7 +83,7 @@
<gr-button
data-index$="[[index]]"
on-click="_handleDeleteButton"
- disabled="[[item.preferred]]"
+ disabled="[[_checkPreferred(item.preferred)]]"
class="remove-button"
>Delete</gr-button
>
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_html.ts b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_html.ts
index ab24168..f4641c2 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_html.ts
@@ -70,9 +70,9 @@
</td>
<td>
<gr-copy-clipboard
- has-tooltip=""
- button-title="Copy GPG public key to clipboard"
- hide-input=""
+ hasTooltip=""
+ buttonTitle="Copy GPG public key to clipboard"
+ hideInput=""
text="[[key.key]]"
>
</gr-copy-clipboard>
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_html.ts b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_html.ts
index 549fc93..811b85c 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_html.ts
@@ -78,9 +78,9 @@
<span class="title">New Password:</span>
<span class="value">[[_generatedPassword]]</span>
<gr-copy-clipboard
- has-tooltip=""
- button-title="Copy password to clipboard"
- hide-input=""
+ hasTooltip=""
+ buttonTitle="Copy password to clipboard"
+ hideInput=""
text="[[_generatedPassword]]"
>
</gr-copy-clipboard>
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.js b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.js
deleted file mode 100644
index 867473f..0000000
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 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-identities.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-
-const basicFixture = fixtureFromElement('gr-identities');
-
-suite('gr-identities tests', () => {
- let element;
-
- const ids = [
- {
- identity: 'username:john',
- email_address: 'john.doe@example.com',
- trusted: true,
- }, {
- identity: 'gerrit:gerrit',
- email_address: 'gerrit@example.com',
- }, {
- identity: 'mailto:gerrit2@example.com',
- email_address: 'gerrit2@example.com',
- trusted: true,
- can_delete: true,
- },
- ];
-
- setup(async () => {
- stubRestApi('getExternalIds').returns(Promise.resolve(ids));
-
- element = basicFixture.instantiate();
- await element.loadData();
- await flush();
- });
-
- test('renders', () => {
- const rows = Array.from(
- element.root.querySelectorAll('tbody tr'));
-
- assert.equal(rows.length, 2);
-
- const nameCells = rows.map(row =>
- row.querySelectorAll('td')[2].textContent
- );
-
- assert.equal(nameCells[0].trim(), 'gerrit:gerrit');
- assert.equal(nameCells[1].trim(), '');
- });
-
- test('renders email', () => {
- const rows = Array.from(
- element.root.querySelectorAll('tbody tr'));
-
- assert.equal(rows.length, 2);
-
- const nameCells = rows.map(row =>
- row.querySelectorAll('td')[1].textContent
- );
-
- assert.equal(nameCells[0], 'gerrit@example.com');
- assert.equal(nameCells[1], 'gerrit2@example.com');
- });
-
- test('_computeIdentity', () => {
- assert.equal(
- element._computeIdentity(ids[0].identity), 'username:john');
- assert.equal(element._computeIdentity(ids[2].identity), '');
- });
-
- test('filterIdentities', () => {
- assert.isFalse(element.filterIdentities(ids[0]));
-
- assert.isTrue(element.filterIdentities(ids[1]));
- });
-
- test('delete id', done => {
- element._idName = 'mailto:gerrit2@example.com';
- const loadDataStub = sinon.stub(element, 'loadData');
- element._handleDeleteItemConfirm().then(() => {
- assert.isTrue(loadDataStub.called);
- done();
- });
- });
-
- test('_handleDeleteItem opens modal', () => {
- const deleteBtn =
- element.root.querySelector('.deleteButton');
- const deleteItem = sinon.stub(element, '_handleDeleteItem');
- MockInteractions.tap(deleteBtn);
- assert.isTrue(deleteItem.called);
- });
-
- test('_computeShowLinkAnotherIdentity', () => {
- let serverConfig;
-
- serverConfig = {
- auth: {
- git_basic_auth_policy: 'OAUTH',
- },
- };
- assert.isTrue(element._computeShowLinkAnotherIdentity(serverConfig));
-
- serverConfig = {
- auth: {
- git_basic_auth_policy: 'OpenID',
- },
- };
- assert.isTrue(element._computeShowLinkAnotherIdentity(serverConfig));
-
- serverConfig = {
- auth: {
- git_basic_auth_policy: 'HTTP_LDAP',
- },
- };
- assert.isFalse(element._computeShowLinkAnotherIdentity(serverConfig));
-
- serverConfig = {
- auth: {
- git_basic_auth_policy: 'LDAP',
- },
- };
- assert.isFalse(element._computeShowLinkAnotherIdentity(serverConfig));
-
- serverConfig = {
- auth: {
- git_basic_auth_policy: 'HTTP',
- },
- };
- assert.isFalse(element._computeShowLinkAnotherIdentity(serverConfig));
-
- serverConfig = {};
- assert.isFalse(element._computeShowLinkAnotherIdentity(serverConfig));
- });
-
- test('_showLinkAnotherIdentity', () => {
- element.serverConfig = {
- auth: {
- git_basic_auth_policy: 'OAUTH',
- },
- };
-
- assert.isTrue(element._showLinkAnotherIdentity);
-
- element.serverConfig = {
- auth: {
- git_basic_auth_policy: 'LDAP',
- },
- };
-
- assert.isFalse(element._showLinkAnotherIdentity);
- });
-});
-
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts
new file mode 100644
index 0000000..d1a4f8f
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts
@@ -0,0 +1,148 @@
+/**
+ * @license
+ * Copyright (C) 2017 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-identities';
+import {GrIdentities} from './gr-identities';
+import {stubRestApi} from '../../../test/test-utils';
+import {ServerInfo} from '../../../types/common';
+import {createServerInfo} from '../../../test/test-data-generators';
+import {queryAll, queryAndAssert} from '../../../test/test-utils';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
+
+const basicFixture = fixtureFromElement('gr-identities');
+
+suite('gr-identities tests', () => {
+ let element: GrIdentities;
+
+ const ids = [
+ {
+ identity: 'username:john',
+ email_address: 'john.doe@example.com',
+ trusted: true,
+ },
+ {
+ identity: 'gerrit:gerrit',
+ email_address: 'gerrit@example.com',
+ },
+ {
+ identity: 'mailto:gerrit2@example.com',
+ email_address: 'gerrit2@example.com',
+ trusted: true,
+ can_delete: true,
+ },
+ ];
+
+ setup(async () => {
+ stubRestApi('getExternalIds').returns(Promise.resolve(ids));
+
+ element = basicFixture.instantiate();
+ await element.loadData();
+ await flush();
+ });
+
+ test('renders', () => {
+ const rows = Array.from(queryAll(element, 'tbody tr'));
+
+ assert.equal(rows.length, 2);
+
+ const nameCells = rows.map(row => queryAll(row, 'td')[2].textContent);
+
+ assert.equal(nameCells[0]!.trim(), 'gerrit:gerrit');
+ assert.equal(nameCells[1]!.trim(), '');
+ });
+
+ test('renders email', () => {
+ const rows = Array.from(queryAll(element, 'tbody tr'));
+
+ assert.equal(rows.length, 2);
+
+ const nameCells = rows.map(row => queryAll(row, 'td')[1]!.textContent);
+
+ assert.equal(nameCells[0]!, 'gerrit@example.com');
+ assert.equal(nameCells[1]!, 'gerrit2@example.com');
+ });
+
+ test('_computeIdentity', () => {
+ assert.equal(element._computeIdentity(ids[0].identity), 'username:john');
+ assert.equal(element._computeIdentity(ids[2].identity), '');
+ });
+
+ test('filterIdentities', () => {
+ assert.isFalse(element.filterIdentities(ids[0]));
+
+ assert.isTrue(element.filterIdentities(ids[1]));
+ });
+
+ test('delete id', done => {
+ element._idName = 'mailto:gerrit2@example.com';
+ const loadDataStub = sinon.stub(element, 'loadData');
+ element._handleDeleteItemConfirm().then(() => {
+ assert.isTrue(loadDataStub.called);
+ done();
+ });
+ });
+
+ test('_handleDeleteItem opens modal', () => {
+ const deleteBtn = queryAndAssert(element, '.deleteButton');
+ const deleteItem = sinon.stub(element, '_handleDeleteItem');
+ MockInteractions.tap(deleteBtn);
+ assert.isTrue(deleteItem.called);
+ });
+
+ test('_computeShowLinkAnotherIdentity', () => {
+ const config: ServerInfo = {
+ ...createServerInfo(),
+ };
+
+ config.auth.git_basic_auth_policy = 'OAUTH';
+ assert.isTrue(element._computeShowLinkAnotherIdentity(config));
+
+ config.auth.git_basic_auth_policy = 'OpenID';
+ assert.isTrue(element._computeShowLinkAnotherIdentity(config));
+
+ config.auth.git_basic_auth_policy = 'HTTP_LDAP';
+ assert.isFalse(element._computeShowLinkAnotherIdentity(config));
+
+ config.auth.git_basic_auth_policy = 'LDAP';
+ assert.isFalse(element._computeShowLinkAnotherIdentity(config));
+
+ config.auth.git_basic_auth_policy = 'HTTP';
+ assert.isFalse(element._computeShowLinkAnotherIdentity(config));
+
+ assert.isFalse(element._computeShowLinkAnotherIdentity(undefined));
+ });
+
+ test('_showLinkAnotherIdentity', () => {
+ let config: ServerInfo = {
+ ...createServerInfo(),
+ };
+ config.auth.git_basic_auth_policy = 'OAUTH';
+
+ element.serverConfig = config;
+
+ assert.isTrue(element._showLinkAnotherIdentity);
+
+ config = {
+ ...createServerInfo(),
+ };
+ config.auth.git_basic_auth_policy = 'LDAP';
+ element.serverConfig = config;
+
+ assert.isFalse(element._showLinkAnotherIdentity);
+ });
+});
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_html.ts b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_html.ts
index 4b86709..6f270f5 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_html.ts
@@ -75,12 +75,7 @@
<span class="title">Full Name</span>
<span class="value">
<iron-input bind-value="{{_account.name}}">
- <input
- is="iron-input"
- id="name"
- bind-value="{{_account.name}}"
- disabled="[[_saving]]"
- />
+ <input id="name" disabled="[[_saving]]" />
</iron-input>
</span>
</section>
@@ -92,12 +87,7 @@
>
<span hidden$="[[!_usernameMutable]]" class="value">
<iron-input bind-value="{{_username}}">
- <input
- is="iron-input"
- id="username"
- bind-value="{{_username}}"
- disabled="[[_saving]]"
- />
+ <input id="username" disabled="[[_saving]]" />
</iron-input>
</span>
</section>
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_html.ts b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_html.ts
index 0bee1d3..e853b58 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_html.ts
@@ -76,9 +76,9 @@
</td>
<td>
<gr-copy-clipboard
- has-tooltip=""
- button-title="Copy SSH public key to clipboard"
- hide-input=""
+ hasTooltip=""
+ buttonTitle="Copy SSH public key to clipboard"
+ hideInput=""
text="[[key.ssh_public_key]]"
>
</gr-copy-clipboard>
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 6178e7a..4381a59 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
@@ -31,6 +31,7 @@
import {hasOwnProperty} from '../../../utils/common-util';
import {ProjectWatchInfo} from '../../../types/common';
import {appContext} from '../../../services/app-context';
+import {IronInputElement} from '@polymer/iron-input';
const NOTIFICATION_TYPES = [
{name: 'Changes', key: 'notify_new_changes'},
@@ -43,9 +44,11 @@
export interface GrWatchedProjectsEditor {
$: {
newFilter: HTMLInputElement;
+ newFilterInput: IronInputElement;
newProject: GrAutocomplete;
};
}
+
@customElement('gr-watched-projects-editor')
export class GrWatchedProjectsEditor extends PolymerElement {
static get template() {
@@ -62,7 +65,7 @@
_projectsToRemove: ProjectWatchInfo[] = [];
@property({type: Object})
- _query?: AutocompleteQuery;
+ _query: AutocompleteQuery;
private readonly restApiService = appContext.restApiService;
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts
index edc8fb2..fb65a03 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts
@@ -102,13 +102,13 @@
</th>
<th colspan$="[[_getTypeCount()]]">
<iron-input
+ id="newFilterInput"
class="newFilterInput"
placeholder="branch:name, or other search expression"
>
<input
id="newFilter"
class="newFilterInput"
- is="iron-input"
placeholder="branch:name, or other search expression"
/>
</iron-input>
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.js b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
similarity index 76%
rename from polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.js
rename to polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
index aac8995..cb4b86d 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
@@ -15,14 +15,18 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-watched-projects-editor.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import '../../../test/common-test-setup-karma';
+import './gr-watched-projects-editor';
+import {GrWatchedProjectsEditor} from './gr-watched-projects-editor';
+import {stubRestApi} from '../../../test/test-utils';
+import {ProjectWatchInfo} from '../../../types/common';
+import {queryAll, queryAndAssert} from '../../../test/test-utils';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
const basicFixture = fixtureFromElement('gr-watched-projects-editor');
suite('gr-watched-projects-editor tests', () => {
- let element;
+ let element: GrWatchedProjectsEditor;
setup(done => {
const projects = [
@@ -30,29 +34,34 @@
project: 'project a',
notify_submitted_changes: true,
notify_abandoned_changes: true,
- }, {
+ },
+ {
project: 'project b',
filter: 'filter 1',
notify_new_changes: true,
- }, {
+ },
+ {
project: 'project b',
filter: 'filter 2',
- }, {
+ },
+ {
project: 'project c',
notify_new_changes: true,
notify_new_patch_sets: true,
notify_all_comments: true,
},
- ];
+ ] as ProjectWatchInfo[];
stubRestApi('getWatchedProjects').returns(Promise.resolve(projects));
stubRestApi('getSuggestedProjects').callsFake(input => {
if (input.startsWith('th')) {
- return Promise.resolve({'the project': {
- id: 'the project',
- state: 'ACTIVE',
- web_links: [],
- }});
+ return Promise.resolve({
+ 'the project': {
+ id: 'the project',
+ state: 'ACTIVE',
+ web_links: [],
+ },
+ });
} else {
return Promise.resolve({});
}
@@ -60,18 +69,18 @@
element = basicFixture.instantiate();
- element.loadData().then(() => { flush(done); });
+ element.loadData().then(() => {
+ flush(done);
+ });
});
test('renders', () => {
- const rows = element.shadowRoot
- .querySelector('table').querySelectorAll('tbody tr');
+ const rows = queryAndAssert(element, 'table').querySelectorAll('tbody tr');
assert.equal(rows.length, 4);
- function getKeysOfRow(row) {
- const boxes = rows[row].querySelectorAll('input[checked]');
- return Array.prototype.map.call(boxes,
- e => e.getAttribute('data-key'));
+ function getKeysOfRow(row: number) {
+ const boxes = queryAll(rows[row], 'input[checked]');
+ return Array.prototype.map.call(boxes, e => e.getAttribute('data-key'));
}
let checkedKeys = getKeysOfRow(0);
@@ -157,41 +166,44 @@
test('_handleAddProject', () => {
element.$.newProject.value = 'project d';
element.$.newProject.setText('project d');
- element.$.newFilter.bindValue = '';
+ element.$.newFilterInput.bindValue = '';
element._handleAddProject();
- assert.equal(element._projects.length, 5);
- assert.equal(element._projects[4].project, 'project d');
- assert.isNotOk(element._projects[4].filter);
- assert.isTrue(element._projects[4]._is_local);
+ const projects = element._projects!;
+ assert.equal(projects.length, 5);
+ assert.equal(projects[4].project, 'project d');
+ assert.isNotOk(projects[4].filter);
+ assert.isTrue(projects[4]._is_local);
});
test('_handleAddProject with invalid inputs', () => {
element.$.newProject.value = 'project b';
element.$.newProject.setText('project b');
- element.$.newFilter.bindValue = 'filter 1';
+ element.$.newFilterInput.bindValue = 'filter 1';
element.$.newFilter.value = 'filter 1';
element._handleAddProject();
- assert.equal(element._projects.length, 4);
+ assert.equal(element._projects!.length, 4);
});
test('_handleRemoveProject', () => {
- assert.equal(element._projectsToRemove, 0);
- const button = element.shadowRoot
- .querySelector('table tbody tr:nth-child(2) gr-button');
+ assert.deepEqual(element._projectsToRemove, []);
+
+ const button = queryAndAssert(
+ element,
+ 'table tbody tr:nth-child(2) gr-button'
+ );
MockInteractions.tap(button);
flush();
- const rows = element.shadowRoot
- .querySelector('table tbody').querySelectorAll('tr');
+ const rows = queryAndAssert(element, 'table tbody').querySelectorAll('tr');
+
assert.equal(rows.length, 3);
assert.equal(element._projectsToRemove.length, 1);
assert.equal(element._projectsToRemove[0].project, 'project b');
});
});
-
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 dab778b..f703037 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
@@ -17,19 +17,14 @@
import '../gr-account-link/gr-account-link';
import '../gr-button/gr-button';
import '../gr-icons/gr-icons';
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-account-chip_html';
-import {customElement, property} from '@polymer/decorators';
import {AccountInfo, ChangeInfo} from '../../../types/common';
import {appContext} from '../../../services/app-context';
+import {GrLitElement} from '../../lit/gr-lit-element';
+import {css, customElement, html, property} from 'lit-element';
+import {classMap} from 'lit-html/directives/class-map';
@customElement('gr-account-chip')
-export class GrAccountChip extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrAccountChip extends GrLitElement {
/**
* Fired to indicate a key was pressed while this chip was focused.
*
@@ -64,10 +59,10 @@
@property({type: String})
voteableText?: string;
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, reflect: true})
disabled = false;
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, reflect: true})
removable = false;
/**
@@ -78,7 +73,7 @@
@property({type: Boolean})
highlightAttention = false;
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, reflect: true})
showAvatar?: boolean;
@property({type: Boolean})
@@ -86,18 +81,126 @@
private readonly restApiService = appContext.restApiService;
- /** @override */
- ready() {
- super.ready();
+ static get styles() {
+ return [
+ css`
+ :host {
+ display: block;
+ overflow: hidden;
+ }
+ .container {
+ align-items: center;
+ background-color: var(--background-color-primary);
+ /** round */
+ border-radius: var(--account-chip-border-radius, 20px);
+ border: 1px solid var(--border-color);
+ display: inline-flex;
+ padding: 0 1px;
+ }
+ :host:focus {
+ border-color: transparent;
+ box-shadow: none;
+ outline: none;
+ }
+ :host:focus .container,
+ :host:focus gr-button {
+ background: #ccc;
+ }
+ .transparentBackground,
+ gr-button.transparentBackground {
+ background-color: transparent;
+ }
+ :host([disabled]) {
+ opacity: 0.6;
+ pointer-events: none;
+ }
+ iron-icon {
+ height: 1.2rem;
+ width: 1.2rem;
+ }
+ .container gr-account-link::part(gr-account-link-text) {
+ color: var(--deemphasized-text-color);
+ }
+ `,
+ ];
+ }
+
+ render() {
+ // To pass CSS mixins for @apply to Polymer components, they need to appear
+ // in <style> inside the template.
+ const customStyle = html`
+ <style>
+ .container {
+ --account-label-padding-horizontal: 6px;
+ }
+ gr-button.remove {
+ --gr-remove-button-style: {
+ border-top-width: 0;
+ border-right-width: 0;
+ border-bottom-width: 0;
+ border-left-width: 0;
+ color: var(--deemphasized-text-color);
+ font-weight: var(--font-weight-normal);
+ height: 0.6em;
+ line-height: 10px;
+ /* This cancels most of the --account-label-padding-horizontal. */
+ margin-left: -4px;
+ padding: 0 2px 0 0;
+ text-decoration: none;
+ }
+ }
+
+ gr-button.remove:hover,
+ gr-button.remove:focus {
+ --gr-button: {
+ @apply --gr-remove-button-style;
+ }
+ }
+ gr-button.remove {
+ --gr-button: {
+ @apply --gr-remove-button-style;
+ }
+ }
+ </style>
+ `;
+ return html`${customStyle}
+ <div
+ class="${classMap({
+ container: true,
+ transparentBackground: this.transparentBackground,
+ })}"
+ >
+ <gr-account-link
+ .account="${this.account}"
+ .change="${this.change}"
+ ?forceAttention=${this.forceAttention}
+ ?highlightAttention=${this.highlightAttention}
+ .voteableText=${this.voteableText}
+ >
+ </gr-account-link>
+ <gr-button
+ id="remove"
+ link=""
+ ?hidden=${!this.removable}
+ aria-label="Remove"
+ class="${classMap({
+ remove: true,
+ transparentBackground: this.transparentBackground,
+ })}"
+ @click=${this._handleRemoveTap}
+ >
+ <iron-icon icon="gr-icons:close"></iron-icon>
+ </gr-button>
+ </div>`;
+ }
+
+ constructor() {
+ super();
this._getHasAvatars().then(hasAvatars => {
this.showAvatar = hasAvatars;
});
}
- _getBackgroundClass(transparent: boolean) {
- return transparent ? 'transparentBackground' : '';
- }
-
_handleRemoveTap(e: MouseEvent) {
e.preventDefault();
this.dispatchEvent(
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.ts b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.ts
deleted file mode 100644
index e5efebb..0000000
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.ts
+++ /dev/null
@@ -1,114 +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 {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style>
- :host {
- display: block;
- overflow: hidden;
- }
- .container {
- align-items: center;
- background-color: var(--background-color-primary);
- /** round */
- border-radius: var(--account-chip-border-radius, 20px);
- border: 1px solid var(--border-color);
- display: inline-flex;
- padding: 0 1px;
-
- --account-label-padding-horizontal: 6px;
- --gr-account-label-text-style: {
- color: var(--deemphasized-text-color);
- }
- }
- :host([show-avatar]) .container {
- }
- :host([removable]) .container {
- }
- gr-button.remove {
- --gr-remove-button-style: {
- border-top-width: 0;
- border-right-width: 0;
- border-bottom-width: 0;
- border-left-width: 0;
- color: var(--deemphasized-text-color);
- font-weight: var(--font-weight-normal);
- height: 0.6em;
- line-height: 10px;
- /* This cancels most of the --account-label-padding-horizontal. */
- margin-left: -4px;
- padding: 0 2px 0 0;
- text-decoration: none;
- }
- }
-
- gr-button.remove:hover,
- gr-button.remove:focus {
- --gr-button: {
- @apply --gr-remove-button-style;
- }
- }
- gr-button.remove {
- --gr-button: {
- @apply --gr-remove-button-style;
- }
- }
- :host:focus {
- border-color: transparent;
- box-shadow: none;
- outline: none;
- }
- :host:focus .container,
- :host:focus gr-button {
- background: #ccc;
- }
- .transparentBackground,
- gr-button.transparentBackground {
- background-color: transparent;
- }
- :host([disabled]) {
- opacity: 0.6;
- pointer-events: none;
- }
- iron-icon {
- height: 1.2rem;
- width: 1.2rem;
- }
- </style>
- <div class$="container [[_getBackgroundClass(transparentBackground)]]">
- <gr-account-link
- account="[[account]]"
- change="[[change]]"
- force-attention="[[forceAttention]]"
- highlight-attention="[[highlightAttention]]"
- voteable-text="[[voteableText]]"
- >
- </gr-account-link>
- <gr-button
- id="remove"
- link=""
- hidden$="[[!removable]]"
- hidden=""
- aria-label="Remove"
- class$="remove [[_getBackgroundClass(transparentBackground)]]"
- on-click="_handleRemoveTap"
- >
- <iron-icon icon="gr-icons:close"></iron-icon>
- </gr-button>
- </div>
-`;
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 944054e..c250428 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
@@ -54,13 +54,13 @@
*/
@property({type: Boolean})
- allowAnyInput?: boolean;
+ allowAnyInput = false;
@property({type: Boolean})
- borderless?: boolean;
+ borderless = false;
@property({type: String})
- placeholder?: string;
+ placeholder = '';
@property({type: Number})
suggestFrom = 0;
@@ -69,7 +69,7 @@
querySuggestions: AutocompleteQuery = () => Promise.resolve([]);
@property({type: String, observer: '_inputTextChanged'})
- _inputText?: string;
+ _inputText = '';
get focusStart() {
return this.$.input.focusStart;
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 d7078ed..cc66734 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
@@ -190,11 +190,7 @@
: '';
}
- _computeName(
- account?: AccountInfo,
- config?: ServerInfo,
- firstName?: boolean
- ) {
+ _computeName(account: AccountInfo, firstName: boolean, config?: ServerInfo) {
return getDisplayName(config, account, firstName);
}
@@ -264,12 +260,12 @@
highlight: boolean,
account: AccountInfo,
change: ChangeInfo,
- selfAccount: AccountInfo,
- selected: boolean
+ selected: boolean,
+ selfAccount?: AccountInfo
) {
if (selected) return true;
return (
- this._hasUnforcedAttention(highlight, account, change) &&
+ !!this._hasUnforcedAttention(highlight, account, change) &&
(isInvolved(change, selfAccount) || isSelf(account, selfAccount))
);
}
@@ -278,16 +274,16 @@
highlight: boolean,
account: AccountInfo,
change: ChangeInfo,
- selfAccount: AccountInfo,
force: boolean,
- selected: boolean
+ selected: boolean,
+ selfAccount?: AccountInfo
) {
const enabled = this._computeAttentionButtonEnabled(
highlight,
account,
change,
- selfAccount,
- selected
+ selected,
+ selfAccount
);
return enabled
? 'Click to remove the user from the attention set'
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.ts
index a642337..352763b 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.ts
@@ -59,12 +59,6 @@
position: relative;
top: 1px;
}
- .text {
- @apply --gr-account-label-text-style;
- }
- .text:hover {
- @apply --gr-account-label-text-hover-style;
- }
#attentionButton {
/* This negates the 4px horizontal padding, which we appreciate as a
larger click target, but which we don't want to consume space. :-) */
@@ -87,6 +81,7 @@
}
.name {
display: inline-block;
+ text-decoration: inherit;
vertical-align: top;
overflow: hidden;
text-overflow: ellipsis;
@@ -116,9 +111,9 @@
link=""
aria-label="Remove user from attention set"
on-click="_handleRemoveAttentionClick"
- disabled="[[!_computeAttentionButtonEnabled(highlightAttention, account, change, _selfAccount, selected)]]"
- has-tooltip="[[_computeAttentionButtonEnabled(highlightAttention, account, change, _selfAccount, false)]]"
- title="[[_computeAttentionIconTitle(highlightAttention, account, change, _selfAccount, forceAttention, selected)]]"
+ disabled="[[!_computeAttentionButtonEnabled(highlightAttention, account, change, selected, _selfAccount)]]"
+ has-tooltip="[[_computeAttentionButtonEnabled(highlightAttention, account, change, false, _selfAccount)]]"
+ title="[[_computeAttentionIconTitle(highlightAttention, account, change, forceAttention, selected, _selfAccount)]]"
><iron-icon class="attention" icon="gr-icons:attention"></iron-icon>
</gr-button>
</template>
@@ -130,8 +125,8 @@
<template is="dom-if" if="[[!hideAvatar]]">
<gr-avatar account="[[account]]" imageSize="32"></gr-avatar>
</template>
- <span class="text">
- <span class="name">[[_computeName(account, _config, firstName)]]</span>
+ <span class="text" part="gr-account-label-text">
+ <span class="name">[[_computeName(account, firstName, _config)]]</span>
<template is="dom-if" if="[[!hideStatus]]">
<template is="dom-if" if="[[account.status]]">
<iron-icon class="status" icon="gr-icons:calendar"></iron-icon>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
index efaa9f7..574e450 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
@@ -55,12 +55,12 @@
suite('_computeName', () => {
test('not showing anonymous', () => {
const account = {name: 'Wyatt'};
- assert.deepEqual(element._computeName(account), 'Wyatt');
+ assert.deepEqual(element._computeName(account, false), 'Wyatt');
});
test('showing anonymous but no config', () => {
const account = {};
- assert.deepEqual(element._computeName(account), 'Anonymous');
+ assert.deepEqual(element._computeName(account, false), 'Anonymous');
});
test('test for Anonymous Coward user and replace with Anonymous', () => {
@@ -71,7 +71,10 @@
},
};
const account = {};
- assert.deepEqual(element._computeName(account, config), 'Anonymous');
+ assert.deepEqual(
+ element._computeName(account, false, config),
+ 'Anonymous'
+ );
});
test('test for anonymous_coward_name', () => {
@@ -82,7 +85,10 @@
},
};
const account = {};
- assert.deepEqual(element._computeName(account, config), 'TestAnon');
+ assert.deepEqual(
+ element._computeName(account, false, config),
+ 'TestAnon'
+ );
});
});
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 94a24e1..317806c 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
@@ -18,10 +18,11 @@
import '../gr-account-label/gr-account-label';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {AccountInfo, ChangeInfo} from '../../../types/common';
-import {css, customElement, html, LitElement, property} from 'lit-element';
+import {css, customElement, html, property} from 'lit-element';
+import {GrLitElement} from '../../lit/gr-lit-element';
@customElement('gr-account-link')
-export class GrAccountLink extends LitElement {
+export class GrAccountLink extends GrLitElement {
@property({type: String})
voteableText?: string;
@@ -74,10 +75,8 @@
color: var(--primary-text-color);
text-decoration: none;
}
- gr-account-label {
- --gr-account-label-text-hover-style: {
- text-decoration: underline;
- }
+ gr-account-label::part(gr-account-label-text):hover {
+ text-decoration: underline !important;
}
`,
];
@@ -96,6 +95,7 @@
?hide-status=${this.hideStatus}
?first-name=${this.firstName}
.voteable-text=${this.voteableText}
+ part="gr-account-link-text => gr-account-label-text"
>
</gr-account-label>
</a>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts
index 43c949a..a848b2f 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts
@@ -236,12 +236,12 @@
margin-left: var(--spacing-s);
}
.headerLeft gr-account-label {
- --gr-account-label-text-style: {
- font-weight: var(--font-weight-bold);
- }
--account-max-length: 130px;
width: 150px;
}
+ .headerLeft gr-account-label::part(gr-account-label-text) {
+ font-weight: var(--font-weight-bold);
+ }
.draft gr-account-label {
width: unset;
}
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 2fe6fed..4a2bcee 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
@@ -15,16 +15,14 @@
* limitations under the License.
*/
import '@polymer/iron-input/iron-input';
-import '../../../styles/shared-styles';
import '../gr-button/gr-button';
import '../gr-icons/gr-icons';
-import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-copy-clipboard_html';
-import {GrButton} from '../gr-button/gr-button';
-import {customElement, property} from '@polymer/decorators';
import {IronIconElement} from '@polymer/iron-icon';
-import {assertIsDefined} from '../../../utils/common-util';
+import {assertIsDefined, queryAndAssert} from '../../../utils/common-util';
+import {classMap} from 'lit-html/directives/class-map';
+import {css, customElement, html, property} from 'lit-element';
+import {GrLitElement} from '../../lit/gr-lit-element';
+import {GrButton} from '../gr-button/gr-button';
const COPY_TIMEOUT_MS = 1000;
@@ -33,17 +31,8 @@
'gr-copy-clipboard': GrCopyClipboard;
}
}
-
-export interface GrCopyClipboard {
- $: {button: GrButton; icon: IronIconElement; input: HTMLInputElement};
-}
-
@customElement('gr-copy-clipboard')
-export class GrCopyClipboard extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrCopyClipboard extends GrLitElement {
@property({type: String})
text: string | undefined;
@@ -56,29 +45,121 @@
@property({type: Boolean})
hideInput = false;
- focusOnCopy() {
- this.$.button.focus();
+ static get styles() {
+ return [
+ css`
+ .text {
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ }
+ .copyText {
+ flex-grow: 1;
+ margin-right: var(--spacing-s);
+ }
+ .hideInput {
+ display: none;
+ }
+ input#input {
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
+ width: 100%;
+ }
+ /*
+ * Typically icons are 20px, which is the normal line-height.
+ * The copy icon is too prominent at 20px, so we choose 16px
+ * here, but add 2x2px padding below, so the entire
+ * component should still fit nicely into a normal inline
+ * layout flow.
+ */
+ #icon {
+ height: 16px;
+ width: 16px;
+ }
+ iron-icon {
+ color: var(--deemphasized-text-color);
+ vertical-align: top;
+ }
+ `,
+ ];
}
- _computeInputClass(hideInput: boolean) {
- return hideInput ? 'hideInput' : '';
+ render() {
+ // To pass CSS mixins for @apply to Polymer components, they need to appear
+ // in <style> inside the template.
+ const customStyle = html`
+ <style>
+ iron-icon {
+ --iron-icon-height: 20px;
+ --iron-icon-width: 20px;
+ }
+ gr-button {
+ --gr-button: {
+ padding: 2px;
+ }
+ }
+ </style>
+ `;
+ return html`${customStyle}
+ <div class="text">
+ <iron-input
+ class="copyText"
+ type="text"
+ @click="${this._handleInputClick}"
+ readonly=""
+ bind-value=${this.text}
+ >
+ <input
+ id="input"
+ is="iron-input"
+ class="${classMap({hideInput: this.hideInput})}"
+ type="text"
+ @click="${this._handleInputClick}"
+ readonly=""
+ .value=${this.text}
+ part="text-container-style"
+ />
+ </iron-input>
+ <gr-button
+ id="copy-clipboard-button"
+ link=""
+ ?has-tooltip=${this.hasTooltip}
+ class="copyToClipboard"
+ title="${this.buttonTitle}"
+ @click="${this._copyToClipboard}"
+ aria-label="Click to copy to clipboard"
+ >
+ <iron-icon id="icon" icon="gr-icons:content-copy"></iron-icon>
+ </gr-button>
+ </div> `;
+ }
+
+ focusOnCopy() {
+ queryAndAssert<GrButton>(this, '#copy-clipboard-button').focus();
}
_handleInputClick(e: MouseEvent) {
e.preventDefault();
- ((dom(e) as EventApi).rootTarget as HTMLInputElement).select();
+ const rootTarget = e.composedPath()[0];
+ (rootTarget as HTMLInputElement).select();
}
_copyToClipboard(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
+ this.text = queryAndAssert<HTMLInputElement>(this, '#input').value;
assertIsDefined(this.text, 'text');
- this.$.icon.icon = 'gr-icons:check';
+ this.iconEl.icon = 'gr-icons:check';
navigator.clipboard.writeText(this.text);
setTimeout(
- () => (this.$.icon.icon = 'gr-icons:content-copy'),
+ () => (this.iconEl.icon = 'gr-icons:content-copy'),
COPY_TIMEOUT_MS
);
}
+
+ private get iconEl(): IronIconElement {
+ return queryAndAssert<IronIconElement>(this, '#icon');
+ }
}
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_html.ts b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_html.ts
deleted file mode 100644
index 3ccc46f..0000000
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_html.ts
+++ /dev/null
@@ -1,93 +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 {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style>
- .text {
- align-items: center;
- display: flex;
- flex-wrap: wrap;
- }
- .copyText {
- flex-grow: 1;
- margin-right: var(--spacing-s);
- }
- .hideInput {
- display: none;
- }
- input#input {
- font-family: var(--monospace-font-family);
- font-size: var(--font-size-mono);
- line-height: var(--line-height-mono);
- @apply --text-container-style;
- width: 100%;
- }
- /*
- * Typically icons are 20px, which is the normal line-height.
- * The copy icon is too prominent at 20px, so we choose 16px
- * here, but add 2x2px padding below, so the entire
- * component should still fit nicely into a normal inline
- * layout flow.
- */
- #icon {
- height: 16px;
- width: 16px;
- }
- iron-icon {
- color: var(--deemphasized-text-color);
- vertical-align: top;
- --iron-icon-height: 20px;
- --iron-icon-width: 20px;
- }
- gr-button {
- --gr-button: {
- padding: 2px;
- }
- }
- </style>
- <div class="text">
- <iron-input
- class="copyText"
- type="text"
- bind-value="[[text]]"
- on-click="_handleInputClick"
- readonly=""
- >
- <input
- id="input"
- is="iron-input"
- class$="[[_computeInputClass(hideInput)]]"
- type="text"
- bind-value="[[text]]"
- on-click="_handleInputClick"
- readonly=""
- />
- </iron-input>
- <gr-button
- id="button"
- link=""
- has-tooltip="[[hasTooltip]]"
- class="copyToClipboard"
- title="[[buttonTitle]]"
- on-click="_copyToClipboard"
- aria-label="Click to copy to clipboard"
- >
- <iron-icon id="icon" icon="gr-icons:content-copy"></iron-icon>
- </gr-button>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.js b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.js
index 55b2483..45847d7 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.js
@@ -17,7 +17,7 @@
import '../../../test/common-test-setup-karma.js';
import './gr-copy-clipboard.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
+import {queryAndAssert} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-copy-clipboard');
@@ -32,18 +32,18 @@
});
test('copy to clipboard', () => {
- const clipboardSpy = sinon.spy(element, '_copyToClipboard');
+ const clipboardSpy = sinon.spy(navigator.clipboard, 'writeText');
const copyBtn = element.shadowRoot
.querySelector('.copyToClipboard');
- MockInteractions.tap(copyBtn);
+ MockInteractions.click(copyBtn);
assert.isTrue(clipboardSpy.called);
});
test('focusOnCopy', () => {
element.focusOnCopy();
- assert.deepEqual(dom(element.root).activeElement,
- element.shadowRoot
- .querySelector('.copyToClipboard'));
+ const activeElement = element.shadowRoot.activeElement;
+ const button = element.shadowRoot.querySelector('.copyToClipboard');
+ assert.deepEqual(activeElement, button);
});
test('_handleInputClick', () => {
@@ -58,16 +58,17 @@
assert.equal(inputElement.selectionEnd, element.text.length - 1);
});
- test('hideInput', () => {
+ test('hideInput', async () => {
// iron-input as parent should never be hidden as copy won't work
// on nested hidden elements
const ironInputElement = element.shadowRoot.querySelector('iron-input');
assert.notEqual(getComputedStyle(ironInputElement).display, 'none');
- assert.notEqual(getComputedStyle(element.$.input).display, 'none');
+ const input = queryAndAssert(element, 'input');
+ assert.notEqual(getComputedStyle(input).display, 'none');
element.hideInput = true;
- flush();
- assert.equal(getComputedStyle(element.$.input).display, 'none');
+ await flush();
+ assert.equal(getComputedStyle(input).display, 'none');
});
test('stop events propagation', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts
index c163924..18a46a0 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts
@@ -127,7 +127,7 @@
<span id="triggerText">[[text]]</span>
<gr-copy-clipboard
hidden="[[!showCopyForTriggerText]]"
- hide-input=""
+ hideInput=""
text="[[text]]"
></gr-copy-clipboard>
</gr-button>
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 7298af7..0193197 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
@@ -15,12 +15,10 @@
* limitations under the License.
*/
import '../gr-linked-text/gr-linked-text';
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {customElement, property} from '@polymer/decorators';
-import {htmlTemplate} from './gr-formatted-text_html';
import {CommentLinks} from '../../../types/common';
import {appContext} from '../../../services/app-context';
+import {GrLitElement} from '../../lit/gr-lit-element';
+import {css, customElement, html, property} from 'lit-element';
const CODE_MARKER_PATTERN = /^(`{1,3})([^`]+?)\1$/;
@@ -36,65 +34,73 @@
'gr-formatted-text': GrFormattedText;
}
}
-
-export interface GrFormattedText {
- $: {
- container: HTMLElement;
- };
-}
-
@customElement('gr-formatted-text')
-export class GrFormattedText extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
- @property({type: String, observer: '_contentChanged'})
+export class GrFormattedText extends GrLitElement {
+ @property({type: String})
content?: string;
@property({type: Object})
config?: CommentLinks;
- @property({type: Boolean})
+ @property({type: Boolean, reflect: true})
noTrailingMargin = false;
private readonly reporting = appContext.reportingService;
- static get observers() {
- return ['_contentOrConfigChanged(content, config)'];
+ static get styles() {
+ return [
+ css`
+ :host {
+ display: block;
+ font-family: var(--font-family);
+ }
+ p,
+ ul,
+ code,
+ blockquote,
+ gr-linked-text.pre {
+ margin: 0 0 var(--spacing-m) 0;
+ }
+ p,
+ ul,
+ code,
+ blockquote {
+ max-width: var(--gr-formatted-text-prose-max-width, none);
+ }
+ :host([noTrailingMargin]) p:last-child,
+ :host([noTrailingMargin]) ul:last-child,
+ :host([noTrailingMargin]) blockquote:last-child,
+ :host([noTrailingMargin]) gr-linked-text.pre:last-child {
+ margin: 0;
+ }
+ code,
+ blockquote {
+ border-left: 1px solid #aaa;
+ padding: 0 var(--spacing-m);
+ }
+ code {
+ display: block;
+ white-space: pre-wrap;
+ color: var(--deemphasized-text-color);
+ }
+ li {
+ list-style-type: disc;
+ margin-left: var(--spacing-xl);
+ }
+ code,
+ gr-linked-text.pre {
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-code);
+ /* usually 16px = 12px + 4px */
+ line-height: calc(var(--font-size-code) + var(--spacing-s));
+ }
+ `,
+ ];
}
- /** @override */
- ready() {
- super.ready();
- if (this.noTrailingMargin) {
- this.classList.add('noTrailingMargin');
- }
- }
-
- _contentChanged(content: string) {
- // In the case where the config may not be set (perhaps due to the
- // request for it still being in flight), set the content anyway to
- // prevent waiting on the config to display the text.
- if (this.config) return;
- this._contentOrConfigChanged(content);
- }
-
- /**
- * Given a source string, update the DOM inside #container.
- */
- _contentOrConfigChanged(content?: string) {
- const container = this.$.container;
-
- // Remove existing content.
- while (container.firstChild) {
- container.removeChild(container.firstChild);
- }
-
- // Add new content.
- for (const node of this._computeNodes(this._computeBlocks(content))) {
- if (node) container.appendChild(node);
- }
+ render() {
+ const nodes = this._computeNodes(this._computeBlocks(this.content));
+ return html`<div id="container">${nodes}</div>`;
}
/**
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts
deleted file mode 100644
index 04e4954..0000000
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts
+++ /dev/null
@@ -1,67 +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 {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style>
- :host {
- display: block;
- font-family: var(--font-family);
- }
- p,
- ul,
- code,
- blockquote,
- gr-linked-text.pre {
- margin: 0 0 var(--spacing-m) 0;
- }
- p,
- ul,
- code,
- blockquote {
- max-width: var(--gr-formatted-text-prose-max-width, none);
- }
- :host(.noTrailingMargin) p:last-child,
- :host(.noTrailingMargin) ul:last-child,
- :host(.noTrailingMargin) blockquote:last-child,
- :host(.noTrailingMargin) gr-linked-text.pre:last-child {
- margin: 0;
- }
- code,
- blockquote {
- border-left: 1px solid #aaa;
- padding: 0 var(--spacing-m);
- }
- code {
- display: block;
- white-space: pre-wrap;
- color: var(--deemphasized-text-color);
- }
- li {
- list-style-type: disc;
- margin-left: var(--spacing-xl);
- }
- code,
- gr-linked-text.pre {
- font-family: var(--monospace-font-family);
- font-size: var(--font-size-code);
- /* usually 16px = 12px + 4px */
- line-height: calc(var(--font-size-code) + var(--spacing-s));
- }
- </style>
- <div id="container"></div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js
index fd5a9ba..3e05f11 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js
@@ -387,20 +387,5 @@
assert.equal(result[3].type, 'code');
assert.equal(result[4].type, 'quote');
});
-
- test('_computeNodes called without config', () => {
- const computeNodesSpy = sinon.spy(element, '_computeNodes');
- element.content = 'some text';
- assert.isTrue(computeNodesSpy.called);
- });
-
- test('_contentOrConfigChanged called with config', () => {
- const contentStub = sinon.stub(element, '_contentChanged');
- const contentConfigStub = sinon.stub(element, '_contentOrConfigChanged');
- element.content = 'some text';
- element.config = {};
- assert.isTrue(contentStub.called);
- assert.isTrue(contentConfigStub.called);
- });
});
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 fc5a4aa..b8f5aff 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
@@ -26,6 +26,7 @@
ValueChangedDetail,
} from '../../../api/change-reply';
import {appContext} from '../../../services/app-context';
+import {HookApi, PluginElement} from '../../../api/hook';
/**
* GrChangeReplyInterface, provides a set of handy methods on reply dialog.
@@ -58,7 +59,7 @@
addReplyTextChangedCallback(handler: ReplyChangedCallback) {
this.reporting.trackApi(this.plugin, 'reply', 'addReplyTextChangedCb');
- const hookApi = this.plugin.hook('reply-text');
+ const hookApi = this.plugin.hook('reply-text') as HookApi<PluginElement>;
const registeredHandler = (e: Event) => {
const ce = e as CustomEvent<ValueChangedDetail>;
handler(ce.detail.value);
@@ -79,7 +80,9 @@
addLabelValuesChangedCallback(handler: LabelsChangedCallback) {
this.reporting.trackApi(this.plugin, 'reply', 'addLabelValuesChangedCb');
- const hookApi = this.plugin.hook('reply-label-scores');
+ const hookApi = this.plugin.hook(
+ 'reply-label-scores'
+ ) as HookApi<PluginElement>;
const registeredHandler = (e: Event) => {
const ce = e as CustomEvent<LabelsChangedDetail>;
handler(ce.detail);
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 0cc95a3..f7475bf 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
@@ -16,7 +16,7 @@
*/
import {PluginApi} from '../../../api/plugin';
import {notUndefined} from '../../../types/types';
-import {HookApi} from '../../../api/hook';
+import {HookApi, PluginElement} from '../../../api/hook';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Callback = (value: any) => void;
@@ -26,7 +26,7 @@
plugin: PluginApi;
pluginUrl?: URL;
type?: string;
- domHook?: HookApi;
+ domHook?: HookApi<PluginElement>;
slot?: string;
}
@@ -36,7 +36,7 @@
slot?: string;
type?: string;
moduleName?: string;
- domHook?: HookApi;
+ domHook?: HookApi<PluginElement>;
}
export class GrPluginEndpoints {
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 150c45a..903fb2c 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
@@ -125,7 +125,7 @@
/**
* Fetch and parse REST API response, if request succeeds.
*/
- send(
+ send<T>(
method: HttpMethod,
url: string,
payload?: RequestPayload,
@@ -167,7 +167,7 @@
Promise.reject(new Error(msg))
);
} else {
- return this.restApi.getResponseObject(response);
+ return this.restApi.getResponseObject(response) as Promise<T>;
}
})
.catch(err => {
@@ -180,29 +180,29 @@
});
}
- get(url: string) {
+ get<T>(url: string) {
this.reporting.trackApi(this.plugin, 'rest', 'get');
- return this.send(HttpMethod.GET, url);
+ return this.send<T>(HttpMethod.GET, url);
}
- post(
+ post<T>(
url: string,
payload?: RequestPayload,
errFn?: ErrorCallback,
contentType?: string
) {
this.reporting.trackApi(this.plugin, 'rest', 'post');
- return this.send(HttpMethod.POST, url, payload, errFn, contentType);
+ return this.send<T>(HttpMethod.POST, url, payload, errFn, contentType);
}
- put(
+ put<T>(
url: string,
payload?: RequestPayload,
errFn?: ErrorCallback,
contentType?: string
) {
this.reporting.trackApi(this.plugin, 'rest', 'put');
- return this.send(HttpMethod.PUT, url, payload, errFn, contentType);
+ return this.send<T>(HttpMethod.PUT, url, payload, errFn, contentType);
}
delete(url: string) {
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 fef471d..7d3911a 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
@@ -41,7 +41,7 @@
import {ChangeActionsPluginApi} from '../../../api/change-actions';
import {ChangeReplyPluginApi} from '../../../api/change-reply';
import {RestPluginApi} from '../../../api/rest';
-import {HookApi, RegisterOptions} from '../../../api/hook';
+import {HookApi, PluginElement, RegisterOptions} from '../../../api/hook';
import {AttributeHelperPluginApi} from '../../../api/attribute-helper';
/**
@@ -108,11 +108,11 @@
/**
* Registers an endpoint for the plugin.
*/
- registerCustomComponent(
+ registerCustomComponent<T extends PluginElement>(
endpointName: string,
moduleName?: string,
options?: RegisterOptions
- ): HookApi {
+ ): HookApi<T> {
this.report.trackApi(this, 'plugin', 'registerCustomComponent');
return this._registerCustomComponent(endpointName, moduleName, options);
}
@@ -123,11 +123,11 @@
* Dynamic plugins are registered by specific prefix, such as
* 'change-list-header'.
*/
- registerDynamicCustomComponent(
+ registerDynamicCustomComponent<T extends PluginElement>(
endpointName: string,
moduleName?: string,
options?: RegisterOptions
- ): HookApi {
+ ): HookApi<T> {
this.report.trackApi(this, 'plugin', 'registerDynamicCustomComponent');
const fullEndpointName = `${endpointName}-${this.getPluginName()}`;
return this._registerCustomComponent(
@@ -138,16 +138,17 @@
);
}
- _registerCustomComponent(
+ _registerCustomComponent<T extends PluginElement>(
endpoint: string,
moduleName?: string,
options?: RegisterOptions,
dynamicEndpoint?: string
- ): HookApi {
- const type =
- options && options.replace ? EndpointType.REPLACE : EndpointType.DECORATE;
- const slot = (options && options.slot) || '';
- const domHook = this.domHooks.getDomHook(endpoint, moduleName);
+ ): HookApi<T> {
+ const type = options?.replace
+ ? EndpointType.REPLACE
+ : EndpointType.DECORATE;
+ const slot = options?.slot ?? '';
+ const domHook = this.domHooks.getDomHook<T>(endpoint, moduleName);
moduleName = moduleName || domHook.getModuleName();
getPluginEndpoints().registerModule(this, {
slot,
@@ -164,7 +165,10 @@
* Returns instance of DOM hook API for endpoint. Creates a placeholder
* element for the first call.
*/
- hook(endpointName: string, options?: RegisterOptions) {
+ hook<T extends PluginElement>(
+ endpointName: string,
+ options?: RegisterOptions
+ ): HookApi<T> {
this.report.trackApi(this, 'plugin', 'hook');
return this.registerCustomComponent(endpointName, undefined, options);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_html.ts b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_html.ts
index 6c7f0d8..e43460d 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_html.ts
@@ -45,17 +45,15 @@
/* Should roughly match the height of .commandContainer without padding. */
line-height: 26px;
}
- .commandContainer gr-copy-clipboard {
- --text-container-style: {
- border: none;
- }
+ .commandContainer gr-copy-clipboard::part(text-container-style) {
+ border: none;
}
</style>
<label>[[label]]</label>
<div class="commandContainer">
<gr-copy-clipboard
text="[[command]]"
- has-tooltip
+ hasTooltip
button-title="[[tooltip]]"
></gr-copy-clipboard>
</div>
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses.ts b/polygerrit-ui/app/node_modules_licenses/licenses.ts
index f04e224..bcdab0e 100644
--- a/polygerrit-ui/app/node_modules_licenses/licenses.ts
+++ b/polygerrit-ui/app/node_modules_licenses/licenses.ts
@@ -405,6 +405,14 @@
packageLicenseFile: "LICENSE",
}
},
+ {
+ name: "immer",
+ license: {
+ name: "immer",
+ type: LicenseTypes.Mit,
+ packageLicenseFile: "LICENSE",
+ }
+ }
];
export default packages;
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index 4e18e47..d26dc97 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -37,6 +37,7 @@
"@webcomponents/webcomponentsjs": "^1.3.3",
"ba-linkify": "file:../../lib/ba-linkify/src/",
"codemirror-minified": "^5.62.0",
+ "immer": "^9.0.5",
"lit-element": "^2.5.1",
"page": "^1.11.6",
"polymer-bridges": "file:../../polymer-bridges/",
diff --git a/polygerrit-ui/app/services/checks/checks-service.ts b/polygerrit-ui/app/services/checks/checks-service.ts
index ca6c6e7..164074b 100644
--- a/polygerrit-ui/app/services/checks/checks-service.ts
+++ b/polygerrit-ui/app/services/checks/checks-service.ts
@@ -171,7 +171,7 @@
patchsetNumber: patchNum,
patchsetSha,
repo: change.project,
- commmitMessage: getCurrentRevision(change)?.commit?.message,
+ commitMessage: getCurrentRevision(change)?.commit?.message,
changeInfo: change as ChangeInfo,
};
return this.fetchResults(pluginName, data, patchset);
diff --git a/polygerrit-ui/app/services/comments/comments-model.ts b/polygerrit-ui/app/services/comments/comments-model.ts
index b0279d4..b26ec9b 100644
--- a/polygerrit-ui/app/services/comments/comments-model.ts
+++ b/polygerrit-ui/app/services/comments/comments-model.ts
@@ -23,7 +23,7 @@
PathToCommentsInfoMap,
RobotCommentInfo,
} from '../../types/common';
-import {DraftInfo} from '../../utils/comment-util';
+import {addPath, DraftInfo} from '../../utils/comment-util';
interface CommentState {
comments: PathToCommentsInfoMap;
@@ -71,7 +71,7 @@
[path: string]: CommentInfo[];
}) {
const nextState = {...privateState$.getValue()};
- nextState.comments = comments || {};
+ nextState.comments = addPath(comments) || {};
privateState$.next(nextState);
}
@@ -79,13 +79,13 @@
[path: string]: RobotCommentInfo[];
}) {
const nextState = {...privateState$.getValue()};
- nextState.robotComments = robotComments || {};
+ nextState.robotComments = addPath(robotComments) || {};
privateState$.next(nextState);
}
export function updateStateDrafts(drafts?: {[path: string]: DraftInfo[]}) {
const nextState = {...privateState$.getValue()};
- nextState.drafts = drafts || {};
+ nextState.drafts = addPath(drafts) || {};
privateState$.next(nextState);
}
@@ -126,10 +126,11 @@
if (!draft.path) throw new Error('draft path undefined');
nextState.drafts = {...nextState.drafts};
const drafts = nextState.drafts;
- const index = drafts[draft.path].findIndex(
+ const index = (drafts[draft.path] || []).findIndex(
d => d.__draftID === draft.__draftID || d.id === draft.id
);
if (index === -1) return;
- drafts[draft.path] = [...drafts[draft.path]].splice(index, 1);
+ drafts[draft.path] = [...drafts[draft.path]];
+ drafts[draft.path].splice(index, 1);
privateState$.next(nextState);
}
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 2cc6f41..0df7d12 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -419,7 +419,10 @@
eventInfo.inBackgroundTab = isInBackgroundTab;
}
- if (this._flagsService.enabledExperiments.length) {
+ if (
+ name === Timing.APP_STARTED &&
+ this._flagsService.enabledExperiments.length
+ ) {
eventInfo.enabledExperiments = JSON.stringify(
this._flagsService.enabledExperiments
);
diff --git a/polygerrit-ui/app/styles/themes/app-theme.ts b/polygerrit-ui/app/styles/themes/app-theme.ts
index d768c96..1996800 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.ts
+++ b/polygerrit-ui/app/styles/themes/app-theme.ts
@@ -377,8 +377,7 @@
/* misc */
--border-radius: 4px;
--reply-overlay-z-index: 1000;
- /* Base 64 encoded 1x1px of #681da8 */
- --line-length-indicator: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2PIlF2xAgAD+AHXfBDdKAAAAABJRU5ErkJggg==');
+ --line-length-indicator-color: #681da8;
/* paper and iron component overrides */
--iron-overlay-backdrop-background-color: black;
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.ts b/polygerrit-ui/app/styles/themes/dark-theme.ts
index dec438d..926b02d 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.ts
+++ b/polygerrit-ui/app/styles/themes/dark-theme.ts
@@ -230,8 +230,7 @@
--syntax-variable-color: #f77669;
/* misc */
- /* Base 64 encoded 1x1px of #d7aefb; */
- --line-length-indicator: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2O4vu73fwAIYAOAtqAXCQAAAABJRU5ErkJggg==');
+ --line-length-indicator-color: #d7aefb;
/* paper and iron component overrides */
--iron-overlay-backdrop-background-color: white;
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 54c9899..0f2608e 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -287,7 +287,7 @@
*/
export interface AccountExternalIdInfo {
identity: string;
- email?: string;
+ email_address?: string;
trusted?: boolean;
can_delete?: boolean;
}
@@ -984,6 +984,7 @@
notify_all_comments?: boolean;
notify_submitted_changes?: boolean;
notify_abandoned_changes?: boolean;
+ _is_local?: boolean; // Added manually
}
/**
* The DeleteDraftCommentsInput entity contains information specifying a set of draft comments that should be deleted
diff --git a/polygerrit-ui/app/types/diff.ts b/polygerrit-ui/app/types/diff.ts
index b29e312..16338335 100644
--- a/polygerrit-ui/app/types/diff.ts
+++ b/polygerrit-ui/app/types/diff.ts
@@ -104,7 +104,6 @@
hide_line_numbers?: boolean;
hide_empty_pane?: boolean;
match_brackets?: boolean;
- line_wrapping?: boolean;
}
export declare type DiffPreferencesInfoKey = keyof DiffPreferencesInfo;
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index 6e37697..5145527 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -26,6 +26,7 @@
CHANGE = 'change',
CHANGED = 'changed',
CHANGE_MESSAGE_DELETED = 'change-message-deleted',
+ COMMIT = 'commit',
DIALOG_CHANGE = 'dialog-change',
DROP = 'drop',
EDITABLE_CONTENT_SAVE = 'editable-content-save',
@@ -58,6 +59,8 @@
/* prettier-ignore */
'changed': ChangedEvent;
'change-message-deleted': ChangeMessageDeletedEvent;
+ /* prettier-ignore */
+ 'commit': CommitEvent;
'dialog-change': DialogChangeEvent;
/* prettier-ignore */
'drop': DropEvent;
@@ -109,6 +112,8 @@
}
export type ChangeMessageDeletedEvent = CustomEvent<ChangeMessageDeletedEventDetail>;
+export type CommitEvent = CustomEvent;
+
// TODO(milutin) - remove once new gr-dialog will do it out of the box
// This informs gr-app-element to remove footer, header from a11y tree
export interface DialogChangeEventDetail {
diff --git a/polygerrit-ui/app/types/types.ts b/polygerrit-ui/app/types/types.ts
index fd41cc9..2d4d412 100644
--- a/polygerrit-ui/app/types/types.ts
+++ b/polygerrit-ui/app/types/types.ts
@@ -189,7 +189,6 @@
showDownloadDialog: boolean;
diffMode: DiffViewMode | null;
numFilesShown: number | null;
- scrollTop?: number;
diffViewMode?: boolean;
}
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index 83a9174..3e85b48 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -361,3 +361,25 @@
if (isDraft(comment)) return comment.__draftID;
throw new Error('Missing id in root comment.');
}
+
+/**
+ * Add path info to every comment as CommentInfo returned
+ * from server does not have that.
+ *
+ * TODO(taoalpha): should consider changing BE to send path
+ * back within CommentInfo
+ */
+export function addPath<T>(
+ comments: {[path: string]: T[]} = {}
+): {[path: string]: Array<T & {path: string}>} {
+ const updatedComments: {[path: string]: Array<T & {path: string}>} = {};
+ for (const filePath of Object.keys(comments)) {
+ const allCommentsForPath = comments[filePath] || [];
+ if (allCommentsForPath.length) {
+ updatedComments[filePath] = allCommentsForPath.map(comment => {
+ return {...comment, path: filePath};
+ });
+ }
+ }
+ return updatedComments;
+}
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index d8657f7..4fb98dd 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -600,6 +600,11 @@
agent-base "6"
debug "4"
+immer@^9.0.5:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.5.tgz#a7154f34fe7064f15f00554cc94c66cc0bf453ec"
+ integrity sha512-2WuIehr2y4lmYz9gaQzetPR2ECniCifk4ORaQbU3g5EalLt+0IVTosEPJ5BoYl/75ky2mivzdRzV8wWgQGOSYQ==
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index e030878..ddfaeb4 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -202,6 +202,10 @@
moduleImportRegexp = regexp.MustCompile("(?m)^((import|export).*'/node_modules/)lit-(element|html).js';$")
data = moduleImportRegexp.ReplaceAll(data, []byte("${1}lit-${3}/lit-${3}.js';"))
+ // 'immer' imports and exports have to be resolved to 'immer/dist/immer.esm.js'.
+ moduleImportRegexp = regexp.MustCompile("(?m)^((import|export).*'/node_modules/)immer.js';$")
+ data = moduleImportRegexp.ReplaceAll(data, []byte("${1}/immer/dist/immer.esm.js';"))
+
if strings.HasSuffix(normalizedContentPath, "/node_modules/page/page.js") {
// Can't import page.js directly, because this is undefined.
// Replace it with window