Merge "Add a "is:pure-revert" predicate"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index acf65a5..3109ec7 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1169,6 +1169,14 @@
Result of checking if one change or commit is a pure/clean revert of
another.
+cache `"soy_sauce_compiled_templates"`::
++
+Caches compiled soy templates. Stores at most only one key-value pair with
+a constant key value and the value is a compiled SoySauce templates. The value
+is reloaded automatically every few seconds if there are reads from the cache.
+If cache is not used for 1 minute, the item is removed (i.e. emails can be send
+with templates which are max 1 minute old).
+
cache `"sshkeys"`::
+
Caches unpacked versions of user SSH keys, so the internal SSH daemon
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 6c25bae..24882cb 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -168,9 +168,8 @@
import com.google.gerrit.server.mail.send.FromAddressGenerator;
import com.google.gerrit.server.mail.send.FromAddressGeneratorProvider;
import com.google.gerrit.server.mail.send.InboundEmailRejectionSender;
-import com.google.gerrit.server.mail.send.MailSoySauceProvider;
+import com.google.gerrit.server.mail.send.MailSoySauceModule;
import com.google.gerrit.server.mail.send.MailSoyTemplateProvider;
-import com.google.gerrit.server.mail.send.MailTemplates;
import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.mime.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.notedb.NoteDbModule;
@@ -227,7 +226,6 @@
import com.google.inject.TypeLiteral;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.multibindings.OptionalBinder;
-import com.google.template.soy.jbcsrc.api.SoySauce;
import java.util.List;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.transport.PostReceiveHook;
@@ -289,6 +287,7 @@
install(new FileInfoJsonModule());
install(ThreadLocalRequestContext.module());
install(new ApprovalModule());
+ install(new MailSoySauceModule());
factory(CapabilityCollection.Factory.class);
factory(ChangeData.AssistedFactory.class);
@@ -330,7 +329,6 @@
bind(ApprovalsUtil.class);
- bind(SoySauce.class).annotatedWith(MailTemplates.class).toProvider(MailSoySauceProvider.class);
bind(FromAddressGenerator.class).toProvider(FromAddressGeneratorProvider.class).in(SINGLETON);
bind(Boolean.class)
.annotatedWith(EnablePeerIPInReflogRecord.class)
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 1990cc0..2581d89 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -618,31 +618,47 @@
Set<String> distinctApprovals = new HashSet<>();
for (PatchSetApproval a : cd.currentApprovals()) {
if (a.value() != 0 && !a.isLegacySubmit()) {
- allApprovals.add(formatLabel(a.label(), a.value(), a.accountId()));
Optional<LabelType> labelType = cd.getLabelTypes().byLabel(a.labelId());
- allApprovals.addAll(getMaxMinAnyLabels(a.label(), a.value(), labelType, a.accountId()));
- if (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));
- }
- if (!cd.currentPatchSet().uploader().equals(a.accountId())) {
- allApprovals.add(
- formatLabel(a.label(), a.value(), ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID));
- allApprovals.addAll(
- getMaxMinAnyLabels(
- a.label(), a.value(), labelType, ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID));
- }
+
+ allApprovals.add(formatLabel(a.label(), a.value(), a.accountId()));
+ allApprovals.addAll(getMagicLabelFormats(a.label(), a.value(), labelType, a.accountId()));
+ allApprovals.addAll(getLabelOwnerFormats(a, cd, labelType));
+ allApprovals.addAll(getLabelNonUploaderFormats(a, cd, labelType));
distinctApprovals.add(formatLabel(a.label(), a.value()));
- distinctApprovals.addAll(getMaxMinAnyLabels(a.label(), a.value(), labelType, null));
+ distinctApprovals.addAll(
+ getMagicLabelFormats(a.label(), a.value(), labelType, /* accountId= */ null));
}
}
allApprovals.addAll(distinctApprovals);
return allApprovals;
}
- private static List<String> getMaxMinAnyLabels(
+ private static List<String> getLabelOwnerFormats(
+ PatchSetApproval a, ChangeData cd, Optional<LabelType> labelType) {
+ List<String> allFormats = new ArrayList<>();
+ if (cd.change().getOwner().equals(a.accountId())) {
+ allFormats.add(formatLabel(a.label(), a.value(), ChangeQueryBuilder.OWNER_ACCOUNT_ID));
+ allFormats.addAll(
+ getMagicLabelFormats(
+ a.label(), a.value(), labelType, ChangeQueryBuilder.OWNER_ACCOUNT_ID));
+ }
+ return allFormats;
+ }
+
+ private static List<String> getLabelNonUploaderFormats(
+ PatchSetApproval a, ChangeData cd, Optional<LabelType> labelType) {
+ List<String> allFormats = new ArrayList<>();
+ if (!cd.currentPatchSet().uploader().equals(a.accountId())) {
+ allFormats.add(formatLabel(a.label(), a.value(), ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID));
+ allFormats.addAll(
+ getMagicLabelFormats(
+ a.label(), a.value(), labelType, ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID));
+ }
+ return allFormats;
+ }
+
+ /** Get magic label formats corresponding to the {MIN, MAX, ANY} label votes. */
+ private static List<String> getMagicLabelFormats(
String label, short labelVal, Optional<LabelType> labelType, @Nullable Account.Id accountId) {
List<String> labels = new ArrayList<>();
if (labelType.isPresent()) {
@@ -741,10 +757,6 @@
+ (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()
+ "="
diff --git a/java/com/google/gerrit/server/mail/send/MailSoySauceProvider.java b/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java
similarity index 94%
rename from java/com/google/gerrit/server/mail/send/MailSoySauceProvider.java
rename to java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java
index aade30f..ad1703d 100644
--- a/java/com/google/gerrit/server/mail/send/MailSoySauceProvider.java
+++ b/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java
@@ -19,7 +19,6 @@
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import com.google.template.soy.SoyFileSet;
@@ -31,9 +30,13 @@
import java.nio.file.Files;
import java.nio.file.Path;
-/** Configures Soy Sauce object for rendering email templates. */
+/**
+ * Configures and loads Soy Sauce object for rendering email templates.
+ *
+ * <p>It reloads templates each time when {@link #load()} is called.
+ */
@Singleton
-public class MailSoySauceProvider implements Provider<SoySauce> {
+class MailSoySauceLoader {
// Note: will fail to construct the tofu object if this array is empty.
private static final String[] TEMPLATES = {
@@ -90,7 +93,7 @@
private final PluginSetContext<MailSoyTemplateProvider> templateProviders;
@Inject
- MailSoySauceProvider(
+ MailSoySauceLoader(
SitePaths site,
SoyAstCache cache,
PluginSetContext<MailSoyTemplateProvider> templateProviders) {
@@ -99,8 +102,7 @@
this.templateProviders = templateProviders;
}
- @Override
- public SoySauce get() throws ProvisionException {
+ public SoySauce load() {
SoyFileSet.Builder builder = SoyFileSet.builder();
builder.setSoyAstCache(cache);
for (String name : TEMPLATES) {
diff --git a/java/com/google/gerrit/server/mail/send/MailSoySauceModule.java b/java/com/google/gerrit/server/mail/send/MailSoySauceModule.java
new file mode 100644
index 0000000..a3cf3e3
--- /dev/null
+++ b/java/com/google/gerrit/server/mail/send/MailSoySauceModule.java
@@ -0,0 +1,89 @@
+package com.google.gerrit.server.mail.send;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.server.CacheRefreshExecutor;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import com.google.template.soy.jbcsrc.api.SoySauce;
+import java.time.Duration;
+import java.util.concurrent.ExecutionException;
+import javax.inject.Provider;
+
+/**
+ * Provides support for soy templates
+ *
+ * <p>Module loads templates with {@link MailSoySauceLoader} and caches compiled templates. The
+ * cache refreshes automatically, so Gerrit does not need to be restarted if templates are changed.
+ */
+public class MailSoySauceModule extends CacheModule {
+ static final String CACHE_NAME = "soy_sauce_compiled_templates";
+ private static final String SOY_LOADING_CACHE_KEY = "KEY";
+
+ @Override
+ protected void configure() {
+ // Cache stores only a single key-value pair (key is SOY_LOADING_CACHE_KEY). We are using
+ // cache only for it refresh/expire logic.
+ cache(CACHE_NAME, String.class, SoySauce.class)
+ // Cache refreshes a value only on the access (if refreshAfterWrite interval is
+ // passed). While the value is refreshed, cache returns old value.
+ // Adding expireAfterWrite interval prevents cache from returning very old template.
+ .refreshAfterWrite(Duration.ofSeconds(5))
+ .expireAfterWrite(Duration.ofMinutes(1))
+ .loader(SoySauceCacheLoader.class);
+ bind(SoySauce.class).annotatedWith(MailTemplates.class).toProvider(SoySauceProvider.class);
+ }
+
+ @Singleton
+ static class SoySauceProvider implements Provider<SoySauce> {
+ private final LoadingCache<String, SoySauce> templateCache;
+
+ @Inject
+ SoySauceProvider(@Named(CACHE_NAME) LoadingCache<String, SoySauce> templateCache) {
+ this.templateCache = templateCache;
+ }
+
+ @Override
+ public SoySauce get() {
+ try {
+ return templateCache.get(SOY_LOADING_CACHE_KEY);
+ } catch (ExecutionException e) {
+ throw new ProvisionException("Can't get SoySauce from the cache", e);
+ }
+ }
+ }
+
+ @Singleton
+ static class SoySauceCacheLoader extends CacheLoader<String, SoySauce> {
+ private final ListeningExecutorService executor;
+ private final MailSoySauceLoader loader;
+
+ @Inject
+ SoySauceCacheLoader(
+ @CacheRefreshExecutor ListeningExecutorService executor, MailSoySauceLoader loader) {
+ this.executor = executor;
+ this.loader = loader;
+ }
+
+ @Override
+ public SoySauce load(String key) throws Exception {
+ checkArgument(
+ SOY_LOADING_CACHE_KEY.equals(key),
+ "Cache can have only one element with a key '%s'",
+ SOY_LOADING_CACHE_KEY);
+ return loader.load();
+ }
+
+ @Override
+ public ListenableFuture<SoySauce> reload(String key, SoySauce soySauce) {
+ return executor.submit(() -> loader.load());
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 0527a91..689698e 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -54,6 +54,7 @@
"//java/com/google/gerrit/proto/testing",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/account/externalids/testing",
+ "//java/com/google/gerrit/server/cache/mem",
"//java/com/google/gerrit/server/cache/serialize",
"//java/com/google/gerrit/server/cache/testing",
"//java/com/google/gerrit/server/cancellation",
diff --git a/javatests/com/google/gerrit/server/mail/send/MailSoySauceProviderTest.java b/javatests/com/google/gerrit/server/mail/send/MailSoySauceLoaderTest.java
similarity index 89%
rename from javatests/com/google/gerrit/server/mail/send/MailSoySauceProviderTest.java
rename to javatests/com/google/gerrit/server/mail/send/MailSoySauceLoaderTest.java
index 2ec5e4d..fbeabe1 100644
--- a/javatests/com/google/gerrit/server/mail/send/MailSoySauceProviderTest.java
+++ b/javatests/com/google/gerrit/server/mail/send/MailSoySauceLoaderTest.java
@@ -25,7 +25,7 @@
import org.junit.Before;
import org.junit.Test;
-public class MailSoySauceProviderTest {
+public class MailSoySauceLoaderTest {
private SitePaths sitePaths;
private DynamicSet<MailSoyTemplateProvider> set;
@@ -38,11 +38,11 @@
@Test
public void soyCompilation() {
- MailSoySauceProvider provider =
- new MailSoySauceProvider(
+ MailSoySauceLoader loader =
+ new MailSoySauceLoader(
sitePaths,
new SoyAstCache(),
new PluginSetContext<>(set, PluginMetrics.DISABLED_INSTANCE));
- assertThat(provider.get()).isNotNull(); // should not throw
+ assertThat(loader.load()).isNotNull(); // should not throw
}
}
diff --git a/javatests/com/google/gerrit/server/mail/send/MailSoySauceModuleTest.java b/javatests/com/google/gerrit/server/mail/send/MailSoySauceModuleTest.java
new file mode 100644
index 0000000..bb443f8
--- /dev/null
+++ b/javatests/com/google/gerrit/server/mail/send/MailSoySauceModuleTest.java
@@ -0,0 +1,60 @@
+package com.google.gerrit.server.mail.send;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
+
+import com.google.common.cache.LoadingCache;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.metrics.DisabledMetricMaker;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.server.CacheRefreshExecutor;
+import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
+import com.google.template.soy.jbcsrc.api.SoySauce;
+import java.nio.file.Paths;
+import javax.inject.Provider;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class MailSoySauceModuleTest {
+ @Test
+ public void soySauceProviderReturnsCachedValue() throws Exception {
+ SitePaths sitePaths = new SitePaths(Paths.get("."));
+ Injector injector =
+ Guice.createInjector(
+ new MailSoySauceModule(),
+ new AbstractModule() {
+ @Override
+ protected void configure() {
+ super.configure();
+ bind(ListeningExecutorService.class)
+ .annotatedWith(CacheRefreshExecutor.class)
+ .toInstance(newDirectExecutorService());
+ bind(SitePaths.class).toInstance(sitePaths);
+ bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(new Config());
+ bind(MetricMaker.class).to(DisabledMetricMaker.class);
+ install(new DefaultMemoryCacheModule());
+ }
+ });
+ Provider<SoySauce> soySauceProvider =
+ injector.getProvider(Key.get(SoySauce.class, MailTemplates.class));
+ LoadingCache<String, SoySauce> cache =
+ injector.getInstance(
+ Key.get(
+ new TypeLiteral<LoadingCache<String, SoySauce>>() {},
+ Names.named(MailSoySauceModule.CACHE_NAME)));
+ assertThat(cache.stats().loadCount()).isEqualTo(0);
+ // Theoretically, this can be flaky, if the delay before the second get takes several seconds.
+ // We assume that tests is fast enough.
+ assertThat(soySauceProvider.get()).isNotNull();
+ assertThat(soySauceProvider.get()).isNotNull();
+ assertThat(cache.stats().loadCount()).isEqualTo(1);
+ }
+}
diff --git a/plugins/package.json b/plugins/package.json
index 4e3c376..e5d245c 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -6,7 +6,7 @@
"@polymer/decorators": "^3.0.0",
"@polymer/polymer": "^3.4.1",
"@gerritcodereview/typescript-api": "3.4.4",
- "lit": "2.0.0-rc.3"
+ "lit": "^2.0.2"
},
"license": "Apache-2.0",
"private": true
diff --git a/plugins/yarn.lock b/plugins/yarn.lock
index 3ff1cc4..4cbe489 100644
--- a/plugins/yarn.lock
+++ b/plugins/yarn.lock
@@ -7,10 +7,10 @@
resolved "https://registry.yarnpkg.com/@gerritcodereview/typescript-api/-/typescript-api-3.4.4.tgz#9f09687038088dd7edd3b4e30d249502eb21bfbc"
integrity sha512-MAiQwntcQ59b92yYDsVIXj3oBbAB4C7HELkLFFbYs4ZjzC43XqqtR9VF0dh5OUC8wzFZttgUiOmGehk9edpPuw==
-"@lit/reactive-element@^1.0.0-rc.2":
- version "1.0.0-rc.2"
- resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.0.0-rc.2.tgz#f24dba16ea571a08dca70f1783bd2ca5ec8de3ee"
- integrity sha512-cujeIl5Ei8FC7UHf4/4Q3bRJOtdTe1vpJV/JEBYCggedmQ+2P8A2oz7eE+Vxi6OJ4nc0X+KZxXnBoH4QrEbmEQ==
+"@lit/reactive-element@^1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.0.1.tgz#853cacd4d78d79059f33f66f8e7b0e5c34bee294"
+ integrity sha512-nSD5AA2AZkKuXuvGs8IK7K5ZczLAogfDd26zT9l6S7WzvqALdVWcW5vMUiTnZyj5SPcNwNNANj0koeV1ieqTFQ==
"@polymer/decorators@^3.0.0":
version "3.0.0"
@@ -26,43 +26,36 @@
dependencies:
"@webcomponents/shadycss" "^1.9.1"
-"@types/trusted-types@^1.0.1":
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-1.0.6.tgz#569b8a08121d3203398290d602d84d73c8dcf5da"
- integrity sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==
+"@types/trusted-types@^2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
+ integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
"@webcomponents/shadycss@^1.9.1":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.11.0.tgz#73e289996c002d8be694cd3be0e83c46ad25e7e0"
integrity sha512-L5O/+UPum8erOleNjKq6k58GVl3fNsEQdSOyh0EUhNmi7tHUyRuCJy1uqJiWydWcLARE5IPsMoPYMZmUGrz1JA==
-lit-element@^3.0.0-rc.2:
- version "3.0.0-rc.2"
- resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.0.0-rc.2.tgz#883d0b6fd7b846226d360699d1b713da5fc7e1b7"
- integrity sha512-2Z7DabJ3b5K+p5073vFjMODoaWqy5PIaI4y6ADKm+fCGc8OnX9fU9dMoUEBZjFpd/bEFR9PBp050tUtBnT9XTQ==
+lit-element@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.0.1.tgz#3c545af17d8a46268bc1dd5623a47486e6ff76f4"
+ integrity sha512-vs9uybH9ORyK49CFjoNGN85HM9h5bmisU4TQ63phe/+GYlwvY/3SIFYKdjV6xNvzz8v2MnVC+9+QOkPqh+Q3Ew==
dependencies:
- "@lit/reactive-element" "^1.0.0-rc.2"
- lit-html "^2.0.0-rc.3"
+ "@lit/reactive-element" "^1.0.0"
+ lit-html "^2.0.0"
-lit-html@^2.0.0-rc.3:
- version "2.0.0-rc.3"
- resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.0.0-rc.3.tgz#1c216e548630e18d3093d97f4e29563abce659af"
- integrity sha512-Y6P8LlAyQuqvzq6l/Nc4z5/P5M/rVLYKQIRxcNwSuGajK0g4kbcBFQqZmgvqKG+ak+dHZjfm2HUw9TF5N/pkCw==
+lit-html@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.0.1.tgz#63241015efa07bc9259b6f96f04abd052d2a1f95"
+ integrity sha512-KF5znvFdXbxTYM/GjpdOOnMsjgRcFGusTnB54ixnCTya5zUR0XqrDRj29ybuLS+jLXv1jji6Y8+g4W7WP8uL4w==
dependencies:
- "@types/trusted-types" "^1.0.1"
+ "@types/trusted-types" "^2.0.2"
-lit-html@^2.0.0-rc.4:
- version "2.0.0-rc.4"
- resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.0.0-rc.4.tgz#1015fa8f1f7c8c5b79999ed0bc11c3b79ff1aab5"
- integrity sha512-WSLGu3vxq7y8q/oOd9I3zxyBELNLLiDk6gAYoKK4PGctI5fbh6lhnO/jVBdy0PV/vTc+cLJCA/occzx3YoNPeg==
+lit@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/lit/-/lit-2.0.2.tgz#5e6f422924e0732258629fb379556b6d23f7179c"
+ integrity sha512-hKA/1YaSB+P+DvKWuR2q1Xzy/iayhNrJ3aveD0OQ9CKn6wUjsdnF/7LavDOJsKP/K5jzW/kXsuduPgRvTFrFJw==
dependencies:
- "@types/trusted-types" "^1.0.1"
-
-lit@2.0.0-rc.3:
- version "2.0.0-rc.3"
- resolved "https://registry.yarnpkg.com/lit/-/lit-2.0.0-rc.3.tgz#8b6a85268aba287c11125dfe57e88e0bc09beaff"
- integrity sha512-UZDLWuspl7saA+WvS0e+TE3NdGGE05hOIwUPTWiibs34c5QupcEzpjB/aElt79V9bELQVNbUUwa0Ow7D1Wuszw==
- dependencies:
- "@lit/reactive-element" "^1.0.0-rc.2"
- lit-element "^3.0.0-rc.2"
- lit-html "^2.0.0-rc.4"
+ "@lit/reactive-element" "^1.0.0"
+ lit-element "^3.0.0"
+ lit-html "^2.0.0"
diff --git a/polygerrit-ui/app/constants/reporting.ts b/polygerrit-ui/app/constants/reporting.ts
index a10bdda..0029f5c 100644
--- a/polygerrit-ui/app/constants/reporting.ts
+++ b/polygerrit-ui/app/constants/reporting.ts
@@ -98,4 +98,6 @@
TOGGLE_SHOW_ALL_BUTTON = 'toggle show all button',
SHOW_TAB = 'show-tab',
ATTENTION_SET_CHIP = 'attention-set-chip',
+ SAVE_COMMENT = 'save-comment',
+ COMMENT_SAVED = 'comment-saved',
}
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
index 0c34a84..b7ea237 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
@@ -18,6 +18,7 @@
import '@polymer/paper-toggle-button/paper-toggle-button';
import '../../../styles/gr-form-styles';
import '../../../styles/gr-menu-page-styles';
+import '../../../styles/gr-paper-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-button/gr-button';
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_html.ts b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_html.ts
index 3559194..a8405df 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_html.ts
@@ -70,6 +70,9 @@
display: block;
}
</style>
+ <style include="gr-paper-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="gr-form-styles">
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
</style>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
index 32812dd..7092c9b 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
@@ -36,6 +36,7 @@
PluginConfigOptionsChangedEventDetail,
PluginOption,
} from './gr-repo-plugin-config-types';
+import {paperStyles} from '../../../styles/gr-paper-styles';
const PLUGIN_CONFIG_CHANGED_EVENT_NAME = 'plugin-config-changed';
@@ -71,6 +72,7 @@
return [
sharedStyles,
formStyles,
+ paperStyles,
subpageStyles,
css`
.inherited {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index 12cf314..68566f0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -16,9 +16,10 @@
*/
import '../../../styles/gr-change-list-styles';
+import '../../../styles/gr-font-styles';
+import '../../../styles/shared-styles';
import '../../shared/gr-cursor-manager/gr-cursor-manager';
import '../gr-change-list-item/gr-change-list-item';
-import '../../../styles/shared-styles';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
import {afterNextRender} from '@polymer/polymer/lib/utils/render-status';
import {PolymerElement} from '@polymer/polymer/polymer-element';
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
index c31da77..77320b9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
@@ -20,6 +20,9 @@
<style include="shared-styles">
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
</style>
+ <style include="gr-font-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="gr-change-list-styles">
#changeList {
border-collapse: collapse;
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
index ad8f72f..e4b466b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
@@ -232,6 +232,9 @@
height: var(--line-height-small);
vertical-align: top;
}
+ .checksChip a iron-icon.launch {
+ color: var(--link-color);
+ }
.checksChip.error {
color: var(--error-foreground);
border-color: var(--error-foreground);
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 ce27a6e..f0a90f4 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
@@ -16,6 +16,7 @@
*/
import '@polymer/paper-tabs/paper-tabs';
import '../../../styles/gr-a11y-styles';
+import '../../../styles/gr-paper-styles';
import '../../../styles/shared-styles';
import '../../diff/gr-comment-api/gr-comment-api';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
@@ -1216,27 +1217,46 @@
basePatchNum: value.basePatchNum,
};
- this.$.fileList.collapseAllDiffs();
this._patchRange = patchRange;
this.scrollCommentId = value.commentId;
const patchKnown =
!patchRange.patchNum ||
(this._allPatchSets ?? []).some(ps => ps.num === patchRange.patchNum);
+ // _allPatchsets does not know value.patchNum so force a reload.
+ const forceReload = value.forceReload || !patchKnown;
- // If the change has already been loaded and the parameter change is only
- // in the patch range, then don't do a full reload.
- if (this._changeNum !== undefined && patchChanged && patchKnown) {
+ // If changeNum is defined that means the change has already been
+ // rendered once before so a full reload is not required.
+ if (this._changeNum !== undefined && !forceReload) {
if (!patchRange.patchNum) {
- patchRange.patchNum = computeLatestPatchNum(this._allPatchSets);
+ this._patchRange = {
+ ...this._patchRange,
+ patchNum: computeLatestPatchNum(this._allPatchSets),
+ };
rightPatchNumChanged = true;
}
- this._reloadPatchNumDependentResources(rightPatchNumChanged).then(() => {
- this._sendShowChangeEvent();
- });
+ if (patchChanged) {
+ // We need to collapse all diffs when params change so that a non
+ // existing diff is not requested. See Issue 125270 for more details.
+ this.$.fileList.collapseAllDiffs();
+ this._reloadPatchNumDependentResources(rightPatchNumChanged).then(
+ () => {
+ this._sendShowChangeEvent();
+ }
+ );
+ }
+
+ // If there is no change in patchset or changeNum, such as when user goes
+ // to the diff view and then comes back to change page then there is no
+ // need to reload anything and we render the change view component as is.
return;
}
+ // We need to collapse all diffs when params change so that a non existing
+ // diff is not requested. See Issue 125270 for more details.
+ this.$.fileList.collapseAllDiffs();
+
this._initialLoadComplete = false;
this._changeNum = value.changeNum;
this.loadData(true).then(() => {
@@ -1252,8 +1272,8 @@
_initActiveTabs(params?: AppElementChangeViewParams) {
let primaryTab = PrimaryTab.FILES;
- if (params && params.queryMap && params.queryMap.has('tab')) {
- primaryTab = params.queryMap.get('tab') as PrimaryTab;
+ if (params?.tab) {
+ primaryTab = params?.tab as PrimaryTab;
} else if (params && 'commentId' in params) {
primaryTab = PrimaryTab.COMMENT_THREADS;
}
@@ -2021,8 +2041,8 @@
*
* @param isLocationChange Reloads the related changes
* when true and ends reporting events that started on location change.
- * @param clearPatchset Reloads the related changes
- * ignoring any patchset choice made.
+ * @param clearPatchset Reloads the change ignoring any patchset
+ * choice made.
* @return A promise that resolves when the core data has loaded.
* Some non-core data loading may still be in-flight when the core data
* promise resolves.
@@ -2030,7 +2050,14 @@
loadData(isLocationChange?: boolean, clearPatchset?: boolean): Promise<void> {
if (this.isChangeObsolete()) return Promise.resolve();
if (clearPatchset && this._change) {
- GerritNav.navigateToChange(this._change);
+ GerritNav.navigateToChange(
+ this._change,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ true
+ );
return Promise.resolve();
}
this._loading = true;
@@ -2474,14 +2501,28 @@
) {
patchNum = this._patchRange.patchNum;
}
- GerritNav.navigateToChange(this._change, patchNum, undefined, true);
+ GerritNav.navigateToChange(
+ this._change,
+ patchNum,
+ undefined,
+ true,
+ undefined,
+ true
+ );
}
_handleStopEditTap() {
assertIsDefined(this._change, '_change');
if (!this._patchRange)
throw new Error('missing required _patchRange property');
- GerritNav.navigateToChange(this._change, this._patchRange.patchNum);
+ GerritNav.navigateToChange(
+ this._change,
+ this._patchRange.patchNum,
+ undefined,
+ undefined,
+ undefined,
+ true
+ );
}
_resetReplyOverlayFocusStops() {
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 f125670..155d817 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
@@ -20,6 +20,9 @@
<style include="gr-a11y-styles">
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
</style>
+ <style include="gr-paper-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="shared-styles">
.container:not(.loading) {
background-color: var(--background-color-tertiary);
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 4b11771..591aa41 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
@@ -557,13 +557,12 @@
test('param change should switch primary tab correctly', async () => {
assert.equal(element._activeTabs[0], PrimaryTab.FILES);
- const queryMap = new Map<string, string>();
- queryMap.set('tab', PrimaryTab.FINDINGS);
// view is required
+ element._changeNum = undefined;
element.params = {
...createAppElementChangeViewParams(),
...element.params,
- queryMap,
+ tab: PrimaryTab.FINDINGS,
};
await flush();
assert.equal(element._activeTabs[0], PrimaryTab.FINDINGS);
@@ -571,13 +570,11 @@
test('invalid param change should not switch primary tab', async () => {
assert.equal(element._activeTabs[0], PrimaryTab.FILES);
- const queryMap = new Map<string, string>();
- queryMap.set('tab', 'random');
// view is required
element.params = {
...createAppElementChangeViewParams(),
...element.params,
- queryMap,
+ tab: 'random',
};
await flush();
assert.equal(element._activeTabs[0], PrimaryTab.FILES);
@@ -1315,12 +1312,12 @@
.callsFake(() => Promise.resolve([undefined, undefined, undefined]));
flush();
const collapseStub = sinon.stub(element.$.fileList, 'collapseAllDiffs');
-
const value: AppElementChangeViewParams = {
...createAppElementChangeViewParams(),
view: GerritView.CHANGE,
patchNum: 1 as RevisionPatchSetNum,
};
+ element._changeNum = undefined;
element.params = value;
await flush();
assert.isTrue(reloadStub.calledOnce);
@@ -1378,7 +1375,7 @@
assert.isTrue(reloadPortedCommentsStub.calledOnce);
});
- test('reload entire page when patchRange doesnt change', async () => {
+ test('do not reload entire page when patchRange doesnt change', async () => {
const reloadStub = sinon
.stub(element, 'loadData')
.callsFake(() => Promise.resolve());
@@ -1386,13 +1383,15 @@
const value: AppElementChangeViewParams =
createAppElementChangeViewParams();
element.params = value;
+ // change already loaded
+ assert.isOk(element._changeNum);
await flush();
- assert.isTrue(reloadStub.calledOnce);
+ assert.isFalse(reloadStub.calledOnce);
element._initialLoadComplete = true;
element.params = {...value};
await flush();
- assert.isTrue(reloadStub.calledTwice);
- assert.isTrue(collapseStub.calledTwice);
+ assert.isFalse(reloadStub.calledTwice);
+ assert.isFalse(collapseStub.calledTwice);
});
test('do not handle new change numbers', async () => {
@@ -2063,7 +2062,7 @@
test('no edit exists in revisions, non-latest patchset', async () => {
const promise = mockPromise();
sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
- assert.equal(args.length, 4);
+ assert.equal(args.length, 6);
assert.equal(args[1], 1 as PatchSetNum); // patchNum
assert.equal(args[3], true); // opt_isEdit
promise.resolve();
@@ -2080,7 +2079,7 @@
test('no edit exists in revisions, latest patchset', async () => {
const promise = mockPromise();
sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
- assert.equal(args.length, 4);
+ assert.equal(args.length, 6);
// No patch should be specified when patchNum == latest.
assert.isNotOk(args[1]); // patchNum
assert.equal(args[3], true); // opt_isEdit
@@ -2104,7 +2103,7 @@
navigateToChangeStub.restore();
const promise = mockPromise();
sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
- assert.equal(args.length, 2);
+ assert.equal(args.length, 6);
assert.equal(args[1], 1 as PatchSetNum); // patchNum
promise.resolve();
});
@@ -2222,6 +2221,8 @@
appContext.reportingService,
'changeFullyLoaded'
);
+ // reset so reload is triggered
+ element._changeNum = undefined;
element.params = {
...createAppElementChangeViewParams(),
changeNum: TEST_NUMERIC_CHANGE_ID,
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
index a496be5..962ccef 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
@@ -33,9 +33,11 @@
LabelValuesMap,
} from '../gr-label-score-row/gr-label-score-row';
import {appContext} from '../../../services/app-context';
-import {labelCompare} from '../../../utils/label-util';
+import {getTriggerVotes, labelCompare} from '../../../utils/label-util';
import {Execution} from '../../../constants/reporting';
import {ChangeStatus} from '../../../constants/constants';
+import {KnownExperimentId} from '../../../services/flags/flags';
+import {fontStyles} from '../../../styles/gr-font-styles';
@customElement('gr-label-scores')
export class GrLabelScores extends LitElement {
@@ -50,8 +52,11 @@
private readonly reporting = appContext.reportingService;
+ private readonly flagsService = appContext.flagsService;
+
static override get styles() {
return [
+ fontStyles,
css`
.scoresTable {
display: table;
@@ -72,26 +77,74 @@
gr-label-score-row.no-access {
display: none;
}
+ .heading-3 {
+ padding-left: var(--spacing-xl);
+ margin-bottom: var(--spacing-m);
+ margin-top: var(--spacing-l);
+ }
+ .heading-3:first-of-type {
+ margin-top: 0;
+ }
`,
];
}
override render() {
+ if (this.flagsService.isEnabled(KnownExperimentId.SUBMIT_REQUIREMENTS_UI)) {
+ return this.renderNewSubmitRequirements();
+ } else {
+ return this.renderOldSubmitRequirements();
+ }
+ }
+
+ private renderOldSubmitRequirements() {
const labels = this._computeLabels();
+ return html`${this.renderLabels(labels)}${this.renderErrorMessages()}`;
+ }
+
+ private renderNewSubmitRequirements() {
+ return html`${this.renderSubmitReqsLabels()}${this.renderTriggerVotes()}
+ ${this.renderErrorMessages()}`;
+ }
+
+ private renderSubmitReqsLabels() {
+ const triggerVotes = getTriggerVotes(this.change);
+ const labels = this._computeLabels().filter(
+ label => !triggerVotes.includes(label.name)
+ );
+ if (!labels.length) return;
+ return html`<h3 class="heading-3">Submit requirements votes</h3>
+ ${this.renderLabels(labels)}`;
+ }
+
+ private renderTriggerVotes() {
+ const triggerVotes = getTriggerVotes(this.change);
+ const labels = this._computeLabels().filter(label =>
+ triggerVotes.includes(label.name)
+ );
+ if (!labels.length) return;
+ return html`<h3 class="heading-3">Trigger Votes</h3>
+ ${this.renderLabels(labels)}`;
+ }
+
+ private renderLabels(labels: Label[]) {
const labelValues = this._computeColumns();
return html`<div class="scoresTable">
- ${labels.map(
- label => html`<gr-label-score-row
- class="${this.computeLabelAccessClass(label.name)}"
- .label="${label}"
- .name="${label.name}"
- .labels="${this.change?.labels}"
- .permittedLabels="${this.permittedLabels}"
- .labelValues="${labelValues}"
- ></gr-label-score-row>`
- )}
- </div>
- <div
+ ${labels.map(
+ label => html`<gr-label-score-row
+ class="${this.computeLabelAccessClass(label.name)}"
+ .label="${label}"
+ .name="${label.name}"
+ .labels="${this.change?.labels}"
+ .permittedLabels="${this.permittedLabels}"
+ .labelValues="${labelValues}"
+ ></gr-label-score-row>`
+ )}
+ </div>`;
+ }
+
+ private renderErrorMessages() {
+ return html`<div
class="mergedMessage"
?hidden=${this.change?.status !== ChangeStatus.MERGED}
>
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 e16c073..c3acfb0 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
@@ -18,6 +18,7 @@
import '../../shared/gr-button/gr-button';
import '../../shared/gr-icons/gr-icons';
import '../gr-message/gr-message';
+import '../../../styles/gr-paper-styles';
import '../../../styles/shared-styles';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-messages-list_html';
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 56fae87..087ee19 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
@@ -51,6 +51,9 @@
border-bottom: 1px solid var(--border-color);
}
</style>
+ <style include="gr-paper-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<div class="header">
<div id="showAllActivityToggleContainer" class="container">
<template
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
index bce4024..7493e2f 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
@@ -691,18 +691,13 @@
css`
.title {
color: var(--deemphasized-text-color);
- padding-left: var(--metadata-horizontal-padding);
- }
- h4 {
display: flex;
align-self: flex-end;
+ margin-left: 20px;
}
gr-button {
display: flex;
}
- h4 {
- margin-left: 20px;
- }
gr-button iron-icon {
color: inherit;
--iron-icon-height: 18px;
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 2f3e1e2..6406e3c 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -29,11 +29,11 @@
SubmitRequirementResultInfo,
SubmitRequirementStatus,
} from '../../../api/rest-api';
-import {unique} from '../../../utils/common-util';
import {
extractAssociatedLabels,
getAllUniqueApprovals,
getRequirements,
+ getTriggerVotes,
hasNeutralStatus,
hasVotes,
iconForStatus,
@@ -159,25 +159,8 @@
</tr>
</thead>
<tbody>
- ${submit_requirements.map(
- requirement => html`<tr
- id="requirement-${charsOnly(requirement.name)}"
- >
- <td>${this.renderStatus(requirement.status)}</td>
- <td class="name">
- <gr-limited-text
- class="name"
- limit="25"
- .text="${requirement.name}"
- ></gr-limited-text>
- </td>
- <td>
- <div class="votes-cell">
- ${this.renderVotes(requirement)}
- ${this.renderChecks(requirement)}
- </div>
- </td>
- </tr>`
+ ${submit_requirements.map(requirement =>
+ this.renderRequirement(requirement)
)}
</tbody>
</table>
@@ -192,7 +175,38 @@
></gr-submit-requirement-hovercard>
`
)}
- ${this.renderTriggerVotes(submit_requirements)}`;
+ ${this.renderTriggerVotes()}`;
+ }
+
+ renderRequirement(requirement: SubmitRequirementResultInfo) {
+ return html`
+ <tr id="requirement-${charsOnly(requirement.name)}">
+ <td>${this.renderStatus(requirement.status)}</td>
+ <td class="name">
+ <gr-limited-text
+ class="name"
+ limit="25"
+ .text="${requirement.name}"
+ ></gr-limited-text>
+ </td>
+ <td>
+ <gr-endpoint-decorator
+ class="votes-cell"
+ name="${`submit-requirement-${charsOnly(requirement.name)}`}"
+ >
+ <gr-endpoint-param
+ name="change"
+ .value=${this.change}
+ ></gr-endpoint-param>
+ <gr-endpoint-param
+ name="requirement"
+ .value=${requirement}
+ ></gr-endpoint-param>
+ ${this.renderVotes(requirement)}${this.renderChecks(requirement)}
+ </gr-endpoint-decorator>
+ </td>
+ </tr>
+ `;
}
renderStatus(status: SubmitRequirementStatus) {
@@ -267,15 +281,11 @@
return;
}
- renderTriggerVotes(submitReqs: SubmitRequirementResultInfo[]) {
+ renderTriggerVotes() {
const labels = this.change?.labels ?? {};
- const allLabels = Object.keys(labels);
- const labelAssociatedWithSubmitReqs = submitReqs
- .flatMap(req => extractAssociatedLabels(req))
- .filter(unique);
- const triggerVotes = allLabels
- .filter(label => !labelAssociatedWithSubmitReqs.includes(label))
- .filter(label => hasVotes(labels[label]));
+ const triggerVotes = getTriggerVotes(this.change).filter(label =>
+ hasVotes(labels[label])
+ );
if (!triggerVotes.length) return;
return html`<h3 class="metadata-title heading-3">Trigger Votes</h3>
<section class="trigger-votes">
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
index b8f2630..6b4006c 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
@@ -256,11 +256,9 @@
edit?: boolean;
host?: string;
messageHash?: string;
- queryMap?: Map<string, string> | URLSearchParams;
commentId?: UrlEncodedCommentId;
-
- // TODO(TS): querystring isn't set anywhere, try to remove
- querystring?: string;
+ forceReload?: boolean;
+ tab?: string;
}
export interface GenerateUrlRepoViewParameters {
@@ -612,7 +610,8 @@
patchNum?: PatchSetNum,
basePatchNum?: BasePatchSetNum,
isEdit?: boolean,
- messageHash?: string
+ messageHash?: string,
+ forceReload?: boolean
) {
if (basePatchNum === ParentPatchSetNum) {
basePatchNum = undefined;
@@ -628,6 +627,7 @@
edit: isEdit,
host: change.internalHost || undefined,
messageHash,
+ forceReload,
});
},
@@ -649,17 +649,28 @@
* @param redirect redirect to a change - if true, the current
* location (i.e. page which makes redirect) is not added to a history.
* I.e. back/forward buttons skip current location
- *
+ * @param forceReload Some views are smart about how to handle the reload
+ * of the view. In certain cases we want to force the view to reload
+ * and re-render everything.
*/
+ // TODO(dhruvsri): move the arguments into one options object
navigateToChange(
change: Pick<ChangeInfo, '_number' | 'project' | 'internalHost'>,
patchNum?: PatchSetNum,
basePatchNum?: BasePatchSetNum,
isEdit?: boolean,
- redirect?: boolean
+ redirect?: boolean,
+ forceReload?: boolean
) {
this._navigate(
- this.getUrlForChange(change, patchNum, basePatchNum, isEdit),
+ this.getUrlForChange(
+ change,
+ patchNum,
+ basePatchNum,
+ isEdit,
+ undefined,
+ forceReload
+ ),
redirect
);
},
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 f0cff85..65ac9df 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -530,17 +530,22 @@
range = '/' + range;
}
let suffix = `${range}`;
- if (params.querystring) {
- suffix += '?' + params.querystring;
- } else if (params.edit) {
- suffix += ',edit';
+ let queryString = '';
+ if (params.forceReload) {
+ queryString = 'forceReload=true';
}
- if (params.messageHash) {
- suffix += params.messageHash;
+ if (params.edit) {
+ suffix += ',edit';
}
if (params.commentId) {
suffix = suffix + `/comments/${params.commentId}`;
}
+ if (queryString) {
+ suffix += '?' + queryString;
+ }
+ if (params.messageHash) {
+ suffix += params.messageHash;
+ }
if (params.project) {
const encodedProject = encodeURL(params.project, true);
return `/c/${encodedProject}/+/${params.changeNum}${suffix}`;
@@ -1563,9 +1568,20 @@
basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
patchNum: convertToPatchSetNum(ctx.params[6]),
view: GerritView.CHANGE,
- queryMap: ctx.queryMap,
};
+ if (ctx.queryMap.has('forceReload')) {
+ params.forceReload = true;
+ history.replaceState(
+ null,
+ '',
+ location.href.replace(/[?&]forceReload=true/, '')
+ );
+ }
+
+ const tab = ctx.queryMap.get('tab');
+ if (tab) params.tab = tab;
+
this.reporting.setRepoName(params.project);
this.reporting.setChangeId(changeNum);
this._redirectOrNavigate(params);
@@ -1661,13 +1677,24 @@
// Parameter order is based on the regex group number matched.
const project = ctx.params[0] as RepoName;
const changeNum = Number(ctx.params[1]) as NumericChangeId;
- this._redirectOrNavigate({
+ const params: GenerateUrlChangeViewParameters = {
project,
changeNum,
patchNum: convertToPatchSetNum(ctx.params[3]),
view: GerritView.CHANGE,
edit: true,
- });
+ tab: ctx.queryMap.get('tab') ?? '',
+ };
+ if (ctx.queryMap.has('forceReload')) {
+ params.forceReload = true;
+ history.replaceState(
+ null,
+ '',
+ location.href.replace(/[?&]forceReload=true/, '')
+ );
+ }
+ this._redirectOrNavigate(params);
+
this.reporting.setRepoName(project);
this.reporting.setChangeId(changeNum);
}
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 b91bf0c..7f1a40b 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
@@ -312,28 +312,14 @@
changeNum: '1234',
project: 'test',
};
- const paramsWithQuery = {
- view: GerritView.CHANGE,
- changeNum: '1234',
- project: 'test',
- querystring: 'revert&foo=bar',
- };
assert.equal(element._generateUrl(params), '/c/test/+/1234');
- assert.equal(element._generateUrl(paramsWithQuery),
- '/c/test/+/1234?revert&foo=bar');
params.patchNum = 10;
assert.equal(element._generateUrl(params), '/c/test/+/1234/10');
- paramsWithQuery.patchNum = 10;
- assert.equal(element._generateUrl(paramsWithQuery),
- '/c/test/+/1234/10?revert&foo=bar');
params.basePatchNum = 5;
assert.equal(element._generateUrl(params), '/c/test/+/1234/5..10');
- paramsWithQuery.basePatchNum = 5;
- assert.equal(element._generateUrl(paramsWithQuery),
- '/c/test/+/1234/5..10?revert&foo=bar');
params.messageHash = '#123';
assert.equal(element._generateUrl(params), '/c/test/+/1234/5..10#123');
@@ -1382,7 +1368,6 @@
changeNum: 1234,
basePatchNum: 4,
patchNum: 7,
- queryMap: new Map(),
});
assert.isFalse(redirectStub.called);
assert.isTrue(normalizeRangeStub.called);
@@ -1549,6 +1534,7 @@
null,
3, // 3 Patch num
],
+ queryMap: new Map(),
};
const appParams = {
project: 'foo/bar',
@@ -1556,6 +1542,7 @@
view: GerritView.CHANGE,
patchNum: 3,
edit: true,
+ tab: '',
};
element._handleChangeEditRoute(ctx);
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 fc22a58..7e7e507 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
@@ -70,11 +70,11 @@
* elements of that which uses the gr-comment-api.
*/
constructor(
- comments: PathToCommentsInfoMap | undefined,
- robotComments: {[path: string]: RobotCommentInfo[]} | undefined,
- drafts: {[path: string]: DraftInfo[]} | undefined,
- portedComments: PathToCommentsInfoMap | undefined,
- portedDrafts: PathToCommentsInfoMap | undefined
+ comments?: PathToCommentsInfoMap,
+ robotComments?: {[path: string]: RobotCommentInfo[]},
+ drafts?: {[path: string]: DraftInfo[]},
+ portedComments?: PathToCommentsInfoMap,
+ portedDrafts?: PathToCommentsInfoMap
) {
this._comments = addPath(comments);
this._robotComments = addPath(robotComments);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
index de7d007..54b2450f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
@@ -198,7 +198,6 @@
element,
} = this.findTokenAncestor(e?.target);
if (!newHighlight || newHighlight === this.currentHighlight) return;
- if (this.countOccurrences(newHighlight) <= 1) return;
this.hoveredElement = element;
this.updateTokenTask = debounce(
this.updateTokenTask,
@@ -247,13 +246,6 @@
return this.findTokenAncestor(el.parentElement);
}
- countOccurrences(token: string | undefined) {
- if (!token) return 0;
- const linesLeft = this.tokenToLinesLeft.get(token);
- const linesRight = this.tokenToLinesRight.get(token);
- return (linesLeft?.size ?? 0) + (linesRight?.size ?? 0);
- }
-
private updateTokenHighlight(
newHighlight: string | undefined,
newLineNumber: number,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
index 2993d35..a0670b8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
@@ -290,6 +290,34 @@
assert.deepEqual(tokenHighlightingCalls[1].details, undefined);
});
+ test('triggers listener on token with single occurrence', async () => {
+ const clock = sinon.useFakeTimers();
+ const line1 = createLine('a tokenWithSingleOccurence');
+ const line2 = createLine('can be highlighted', 2);
+ annotate(line1);
+ annotate(line2, Side.RIGHT, 2);
+ const tokenNode = queryAndAssert(line1, '.tk-tokenWithSingleOccurence');
+ assert.isTrue(tokenNode.classList.contains('token'));
+ dispatchMouseEvent(
+ 'mouseover',
+ MockInteractions.middleOfNode(tokenNode),
+ tokenNode
+ );
+ assert.equal(tokenHighlightingCalls.length, 0);
+ clock.tick(HOVER_DELAY_MS);
+ assert.equal(tokenHighlightingCalls.length, 1);
+ assert.deepEqual(tokenHighlightingCalls[0].details, {
+ token: 'tokenWithSingleOccurence',
+ side: Side.RIGHT,
+ element: tokenNode,
+ range: {start_line: 1, start_column: 3, end_line: 1, end_column: 26},
+ });
+
+ MockInteractions.click(container);
+ assert.equal(tokenHighlightingCalls.length, 2);
+ assert.deepEqual(tokenHighlightingCalls[1].details, undefined);
+ });
+
test('clicking clears highlight', async () => {
const clock = sinon.useFakeTimers();
const line1 = createLine('two words');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
index 7efd2f8..6003a2f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -950,7 +950,7 @@
// Safari is not binding newly created comment-thread
// with the slot somehow, replace itself will rebind it
// @see Issue 11182
- if (lastEl && lastEl.replaceWith) {
+ if (isSafari() && lastEl && lastEl.replaceWith) {
lastEl.replaceWith(lastEl);
}
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
index 8e18473..501f688 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
@@ -14,13 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import '../../../styles/gr-a11y-styles';
-import '../../../styles/shared-styles';
import '../../shared/gr-dropdown-list/gr-dropdown-list';
import '../../shared/gr-select/gr-select';
-import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-patch-range-select_html';
import {convertToString, pluralize} from '../../../utils/string-util';
import {appContext} from '../../../services/app-context';
import {
@@ -33,7 +28,6 @@
PatchSet,
convertToPatchSetNum,
} from '../../../utils/patch-set-util';
-import {customElement, property, observe} from '@polymer/decorators';
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
import {hasOwnProperty} from '../../../utils/common-util';
import {
@@ -44,7 +38,6 @@
Timestamp,
} from '../../../types/common';
import {RevisionInfo as RevisionInfoClass} from '../../shared/revision-info/revision-info';
-import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
import {ChangeComments} from '../gr-comment-api/gr-comment-api';
import {
DropdownItem,
@@ -52,6 +45,11 @@
GrDropdownList,
} from '../../shared/gr-dropdown-list/gr-dropdown-list';
import {GeneratedWebLink} from '../../core/gr-navigation/gr-navigation';
+import {EditRevisionInfo} from '../../../types/types';
+import {a11yStyles} from '../../../styles/gr-a11y-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, PropertyValues, css, html} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators';
// Maximum length for patch set descriptions.
const PATCH_DESC_MAX_LENGTH = 500;
@@ -68,12 +66,6 @@
meta_b: GeneratedWebLink[];
}
-export interface GrPatchRangeSelect {
- $: {
- patchNumDropdown: GrDropdownList;
- };
-}
-
declare global {
interface HTMLElementEventMap {
'value-change': DropDownValueChangeEvent;
@@ -92,30 +84,13 @@
* @property {string} basePatchNum
*/
@customElement('gr-patch-range-select')
-export class GrPatchRangeSelect extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
+export class GrPatchRangeSelect extends LitElement {
+ @query('#patchNumDropdown')
+ patchNumDropdown?: GrDropdownList;
@property({type: Array})
availablePatches?: PatchSet[];
- @property({
- type: Object,
- computed:
- '_computeBaseDropdownContent(availablePatches, patchNum,' +
- '_sortedRevisions, changeComments, revisionInfo)',
- })
- _baseDropdownContent?: DropdownItem[];
-
- @property({
- type: Object,
- computed:
- '_computePatchDropdownContent(availablePatches,' +
- 'basePatchNum, _sortedRevisions, changeComments)',
- })
- _patchDropdownContent?: DropdownItem[];
-
@property({type: String})
changeNum?: string;
@@ -138,13 +113,106 @@
revisionInfo?: RevisionInfoClass;
@property({type: Array})
- _sortedRevisions?: RevisionInfo[];
+ @state()
+ protected sortedRevisions?: RevisionInfo[];
private readonly reporting: ReportingService = appContext.reportingService;
- constructor() {
- super();
- this.reporting = appContext.reportingService;
+ static override get styles() {
+ return [
+ a11yStyles,
+ sharedStyles,
+ css`
+ :host {
+ align-items: center;
+ display: flex;
+ }
+ select {
+ max-width: 15em;
+ }
+ .arrow {
+ color: var(--deemphasized-text-color);
+ margin: 0 var(--spacing-m);
+ }
+ gr-dropdown-list {
+ --trigger-style-text-color: var(--deemphasized-text-color);
+ --trigger-style-font-family: var(--font-family);
+ }
+ @media screen and (max-width: 50em) {
+ .filesWeblinks {
+ display: none;
+ }
+ gr-dropdown-list {
+ --native-select-style: {
+ max-width: 5.25em;
+ }
+ }
+ }
+ `,
+ ];
+ }
+
+ private renderWeblinks(fileLink?: GeneratedWebLink[]) {
+ if (!fileLink) return;
+
+ return html`<span class="filesWeblinks">
+ ${fileLink.map(
+ weblink => html`
+ <a target="_blank" rel="noopener" href="${weblink.url}">
+ ${weblink.name}
+ </a>
+ `
+ )}</span
+ > `;
+ }
+
+ override render() {
+ return html`
+ <h3 class="assistive-tech-only">Patchset Range Selection</h3>
+ <span class="patchRange" aria-label="patch range starts with">
+ <gr-dropdown-list
+ id="basePatchDropdown"
+ .value="${convertToString(this.basePatchNum)}"
+ .items="${this._computeBaseDropdownContent(
+ this.availablePatches,
+ this.patchNum,
+ this.sortedRevisions,
+ this.changeComments,
+ this.revisionInfo
+ )}"
+ @value-change=${this._handlePatchChange}
+ >
+ </gr-dropdown-list>
+ </span>
+ ${this.renderWeblinks(this.filesWeblinks?.meta_a)}
+ <span aria-hidden="true" class="arrow">→</span>
+ <span class="patchRange" aria-label="patch range ends with">
+ <gr-dropdown-list
+ id="patchNumDropdown"
+ .value="${convertToString(this.patchNum)}"
+ .items="${this._computePatchDropdownContent(
+ this.availablePatches,
+ this.basePatchNum,
+ this.sortedRevisions,
+ this.changeComments
+ )}"
+ @value-change=${this._handlePatchChange}
+ >
+ </gr-dropdown-list>
+ ${this.renderWeblinks(this.filesWeblinks?.meta_b)}
+ </span>
+ `;
+ }
+
+ override updated(changedProperties: PropertyValues) {
+ if (changedProperties.has('revisions')) {
+ this._updateSortedRevisions(this.revisions);
+ }
+ }
+
+ _updateSortedRevisions(revisions?: RevisionInfo[]) {
+ if (!revisions) return;
+ this.sortedRevisions = sortRevisions(Object.values(revisions));
}
_getShaForPatch(patch: PatchSet) {
@@ -154,19 +222,19 @@
_computeBaseDropdownContent(
availablePatches?: PatchSet[],
patchNum?: PatchSetNum,
- _sortedRevisions?: RevisionInfo[],
+ sortedRevisions?: (RevisionInfo | EditRevisionInfo)[],
changeComments?: ChangeComments,
revisionInfo?: RevisionInfoClass
- ): DropdownItem[] | undefined {
+ ): DropdownItem[] {
// Polymer 2: check for undefined
if (
availablePatches === undefined ||
patchNum === undefined ||
- _sortedRevisions === undefined ||
+ sortedRevisions === undefined ||
changeComments === undefined ||
revisionInfo === undefined
) {
- return undefined;
+ return [];
}
const parentCounts = revisionInfo.getParentCountMap();
@@ -182,7 +250,7 @@
const entry: DropdownItem = this._createDropdownEntry(
basePatchNum,
'Patchset ',
- _sortedRevisions,
+ sortedRevisions,
changeComments,
this._getShaForPatch(basePatch)
);
@@ -191,7 +259,7 @@
disabled: this._computeLeftDisabled(
basePatch.num,
patchNum,
- _sortedRevisions
+ sortedRevisions
),
});
}
@@ -217,7 +285,7 @@
_computeMobileText(
patchNum: PatchSetNum,
changeComments: ChangeComments,
- revisions: RevisionInfo[]
+ revisions: (RevisionInfo | EditRevisionInfo)[]
) {
return (
`${patchNum}` +
@@ -229,17 +297,17 @@
_computePatchDropdownContent(
availablePatches?: PatchSet[],
basePatchNum?: BasePatchSetNum,
- _sortedRevisions?: RevisionInfo[],
+ sortedRevisions?: (RevisionInfo | EditRevisionInfo)[],
changeComments?: ChangeComments
- ): DropdownItem[] | undefined {
+ ): DropdownItem[] {
// Polymer 2: check for undefined
if (
availablePatches === undefined ||
basePatchNum === undefined ||
- _sortedRevisions === undefined ||
+ sortedRevisions === undefined ||
changeComments === undefined
) {
- return undefined;
+ return [];
}
const dropdownContent: DropdownItem[] = [];
@@ -248,7 +316,7 @@
const entry = this._createDropdownEntry(
patchNum,
patchNum === 'edit' ? '' : 'Patchset ',
- _sortedRevisions,
+ sortedRevisions,
changeComments,
this._getShaForPatch(patch)
);
@@ -257,7 +325,7 @@
disabled: this._computeRightDisabled(
basePatchNum,
patchNum,
- _sortedRevisions
+ sortedRevisions
),
});
}
@@ -280,7 +348,7 @@
_createDropdownEntry(
patchNum: PatchSetNum,
prefix: string,
- sortedRevisions: RevisionInfo[],
+ sortedRevisions: (RevisionInfo | EditRevisionInfo)[],
changeComments: ChangeComments,
sha: string
) {
@@ -305,15 +373,6 @@
return entry;
}
- @observe('revisions.*')
- _updateSortedRevisions(
- revisionsRecord: PolymerDeepPropertyChange<RevisionInfo[], RevisionInfo[]>
- ) {
- const revisions = revisionsRecord.base;
- if (!revisions) return;
- this._sortedRevisions = sortRevisions(Object.values(revisions));
- }
-
/**
* The basePatchNum should always be <= patchNum -- because sortedRevisions
* is sorted in reverse order (higher patchset nums first), invalid base
@@ -325,7 +384,7 @@
_computeLeftDisabled(
basePatchNum: PatchSetNum,
patchNum: PatchSetNum,
- sortedRevisions: RevisionInfo[]
+ sortedRevisions: (RevisionInfo | EditRevisionInfo)[]
): boolean {
return (
findSortedIndex(basePatchNum, sortedRevisions) <=
@@ -350,7 +409,7 @@
_computeRightDisabled(
basePatchNum: PatchSetNum,
patchNum: PatchSetNum,
- sortedRevisions: RevisionInfo[]
+ sortedRevisions: (RevisionInfo | EditRevisionInfo)[]
): boolean {
if (basePatchNum === ParentPatchSetNum) {
return false;
@@ -410,7 +469,7 @@
}
_computePatchSetDescription(
- revisions: RevisionInfo[],
+ revisions: (RevisionInfo | EditRevisionInfo)[],
patchNum: PatchSetNum,
addFrontSpace?: boolean
) {
@@ -422,7 +481,7 @@
}
_computePatchSetDate(
- revisions: RevisionInfo[],
+ revisions: (RevisionInfo | EditRevisionInfo)[],
patchNum: PatchSetNum
): Timestamp | undefined {
const rev = getRevisionByPatchNum(revisions, patchNum);
@@ -438,10 +497,10 @@
patchNum: this.patchNum,
basePatchNum: this.basePatchNum,
};
- const target = (dom(e) as EventApi).localTarget;
+ const target = e.target;
const patchSetValue = convertToPatchSetNum(e.detail.value)!;
const latestPatchNum = computeLatestPatchNum(this.availablePatches);
- if (target === this.$.patchNumDropdown) {
+ if (target === this.patchNumDropdown) {
if (detail.patchNum === e.detail.value) return;
this.reporting.reportInteraction('right-patchset-changed', {
previous: detail.patchNum,
@@ -468,8 +527,4 @@
new CustomEvent('patch-range-change', {detail, bubbles: false})
);
}
-
- convertToString(value?: unknown) {
- return convertToString(value);
- }
}
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
deleted file mode 100644
index b268863..0000000
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
+++ /dev/null
@@ -1,82 +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 include="gr-a11y-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="shared-styles">
- :host {
- align-items: center;
- display: flex;
- }
- select {
- max-width: 15em;
- }
- .arrow {
- color: var(--deemphasized-text-color);
- margin: 0 var(--spacing-m);
- }
- gr-dropdown-list {
- --trigger-style-text-color: var(--deemphasized-text-color);
- --trigger-style-font-family: var(--font-family);
- }
- @media screen and (max-width: 50em) {
- .filesWeblinks {
- display: none;
- }
- gr-dropdown-list {
- --native-select-style: {
- max-width: 5.25em;
- }
- }
- }
- </style>
- <h3 class="assistive-tech-only">Patchset Range Selection</h3>
- <span class="patchRange" aria-label="patch range starts with">
- <gr-dropdown-list
- id="basePatchDropdown"
- value="[[convertToString(basePatchNum)]]"
- on-value-change="_handlePatchChange"
- items="[[_baseDropdownContent]]"
- >
- </gr-dropdown-list>
- </span>
- <span is="dom-if" if="[[filesWeblinks.meta_a]]" class="filesWeblinks">
- <template is="dom-repeat" items="[[filesWeblinks.meta_a]]" as="weblink">
- <a target="_blank" rel="noopener" href$="[[weblink.url]]"
- >[[weblink.name]]</a
- >
- </template>
- </span>
- <span aria-hidden="true" class="arrow">→</span>
- <span class="patchRange" aria-label="patch range ends with">
- <gr-dropdown-list
- id="patchNumDropdown"
- value="[[convertToString(patchNum)]]"
- on-value-change="_handlePatchChange"
- items="[[_patchDropdownContent]]"
- >
- </gr-dropdown-list>
- <span is="dom-if" if="[[filesWeblinks.meta_b]]" class="filesWeblinks">
- <template is="dom-repeat" items="[[filesWeblinks.meta_b]]" as="weblink">
- <a target="_blank" href$="[[weblink.url]]">[[weblink.name]]</a>
- </template>
- </span>
- </span>
-`;
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
deleted file mode 100644
index 28ebbac..0000000
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
+++ /dev/null
@@ -1,395 +0,0 @@
-/**
- * @license
- * Copyright (C) 2015 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-comment-api/gr-comment-api.js';
-import '../../shared/revision-info/revision-info.js';
-import './gr-patch-range-select.js';
-import '../../../test/mocks/comment-api.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {RevisionInfo} from '../../shared/revision-info/revision-info.js';
-import {createCommentApiMockWithTemplateElement} from '../../../test/mocks/comment-api';
-import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-import {ChangeComments} from '../gr-comment-api/gr-comment-api.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-import {EditPatchSetNum} from '../../../types/common.js';
-import {SpecialFilePath} from '../../../constants/constants.js';
-
-const commentApiMockElement = createCommentApiMockWithTemplateElement(
- 'gr-patch-range-select-comment-api-mock', html`
- <gr-patch-range-select id="patchRange" auto
- change-comments="[[_changeComments]]"></gr-patch-range-select>
- <gr-comment-api id="commentAPI"></gr-comment-api>
-`);
-
-const basicFixture = fixtureFromElement(commentApiMockElement.is);
-
-suite('gr-patch-range-select tests', () => {
- let element;
-
- let commentApiWrapper;
-
- function getInfo(revisions) {
- const revisionObj = {};
- for (let i = 0; i < revisions.length; i++) {
- revisionObj[i] = revisions[i];
- }
- return new RevisionInfo({revisions: revisionObj});
- }
-
- setup(() => {
- stubRestApi('getDiffComments').returns(Promise.resolve({}));
- stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
- stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
-
- // Element must be wrapped in an element with direct access to the
- // comment API.
- commentApiWrapper = basicFixture.instantiate();
- element = commentApiWrapper.$.patchRange;
-
- // Stub methods on the changeComments object after changeComments has
- // been initialized.
- element.changeComments = new ChangeComments();
- });
-
- test('enabled/disabled options', () => {
- const patchRange = {
- basePatchNum: 'PARENT',
- patchNum: 3,
- };
- const sortedRevisions = [
- {_number: 3},
- {_number: EditPatchSetNum, basePatchNum: 2},
- {_number: 2},
- {_number: 1},
- ];
- for (const patchNum of ['1', '2', '3']) {
- assert.isFalse(element._computeRightDisabled(patchRange.basePatchNum,
- patchNum, sortedRevisions));
- }
- for (const basePatchNum of ['1', '2']) {
- assert.isFalse(element._computeLeftDisabled(basePatchNum,
- patchRange.patchNum, sortedRevisions));
- }
- assert.isTrue(element._computeLeftDisabled('3', patchRange.patchNum));
-
- patchRange.basePatchNum = EditPatchSetNum;
- assert.isTrue(element._computeLeftDisabled('3', patchRange.patchNum,
- sortedRevisions));
- assert.isTrue(element._computeRightDisabled(patchRange.basePatchNum, '1',
- sortedRevisions));
- assert.isTrue(element._computeRightDisabled(patchRange.basePatchNum, '2',
- sortedRevisions));
- assert.isFalse(element._computeRightDisabled(patchRange.basePatchNum, '3',
- sortedRevisions));
- assert.isTrue(element._computeRightDisabled(patchRange.basePatchNum,
- EditPatchSetNum, sortedRevisions));
- });
-
- test('_computeBaseDropdownContent', () => {
- const availablePatches = [
- {num: 'edit', sha: '1'},
- {num: 3, sha: '2'},
- {num: 2, sha: '3'},
- {num: 1, sha: '4'},
- ];
- const revisions = [
- {
- commit: {parents: []},
- _number: 2,
- description: 'description',
- },
- {commit: {parents: []}},
- {commit: {parents: []}},
- {commit: {parents: []}},
- ];
- element.revisionInfo = getInfo(revisions);
- const patchNum = 1;
- const sortedRevisions = [
- {_number: 3, created: 'Mon, 01 Jan 2001 00:00:00 GMT'},
- {_number: EditPatchSetNum, basePatchNum: 2},
- {_number: 2, description: 'description'},
- {_number: 1},
- ];
- const expectedResult = [
- {
- disabled: true,
- triggerText: 'Patchset edit',
- text: 'Patchset edit | 1',
- mobileText: 'edit',
- bottomText: '',
- value: 'edit',
- },
- {
- disabled: true,
- triggerText: 'Patchset 3',
- text: 'Patchset 3 | 2',
- mobileText: '3',
- bottomText: '',
- value: 3,
- date: 'Mon, 01 Jan 2001 00:00:00 GMT',
- },
- {
- disabled: true,
- triggerText: 'Patchset 2',
- text: 'Patchset 2 | 3',
- mobileText: '2 description',
- bottomText: 'description',
- value: 2,
- },
- {
- disabled: true,
- triggerText: 'Patchset 1',
- text: 'Patchset 1 | 4',
- mobileText: '1',
- bottomText: '',
- value: 1,
- },
- {
- text: 'Base',
- value: 'PARENT',
- },
- ];
- assert.deepEqual(element._computeBaseDropdownContent(availablePatches,
- patchNum, sortedRevisions, element.changeComments,
- element.revisionInfo),
- expectedResult);
- });
-
- test('_computeBaseDropdownContent called when patchNum updates', () => {
- element.revisions = [
- {commit: {parents: []}},
- {commit: {parents: []}},
- {commit: {parents: []}},
- {commit: {parents: []}},
- ];
- element.revisionInfo = getInfo(element.revisions);
- element.availablePatches = [
- {num: 1, sha: '1'},
- {num: 2, sha: '2'},
- {num: 3, sha: '3'},
- {num: 'edit', sha: '4'},
- ];
- element.patchNum = 2;
- element.basePatchNum = 'PARENT';
- flush();
-
- sinon.stub(element, '_computeBaseDropdownContent');
-
- // Should be recomputed for each available patch
- element.set('patchNum', 1);
- assert.equal(element._computeBaseDropdownContent.callCount, 1);
- });
-
- test('_computeBaseDropdownContent called when changeComments update',
- async () => {
- element.revisions = [
- {commit: {parents: []}},
- {commit: {parents: []}},
- {commit: {parents: []}},
- {commit: {parents: []}},
- ];
- element.revisionInfo = getInfo(element.revisions);
- element.availablePatches = [
- {num: 'edit', sha: '1'},
- {num: 3, sha: '2'},
- {num: 2, sha: '3'},
- {num: 1, sha: '4'},
- ];
- element.patchNum = 2;
- element.basePatchNum = 'PARENT';
- await flush();
-
- // Should be recomputed for each available patch
- sinon.stub(element, '_computeBaseDropdownContent');
- assert.equal(element._computeBaseDropdownContent.callCount, 0);
- element.changeComments = new ChangeComments();
- await flush();
- assert.equal(element._computeBaseDropdownContent.callCount, 1);
- });
-
- test('_computePatchDropdownContent called when basePatchNum updates', () => {
- element.revisions = [
- {commit: {parents: []}},
- {commit: {parents: []}},
- {commit: {parents: []}},
- {commit: {parents: []}},
- ];
- element.revisionInfo = getInfo(element.revisions);
- element.availablePatches = [
- {num: 1, sha: '1'},
- {num: 2, sha: '2'},
- {num: 3, sha: '3'},
- {num: 'edit', sha: '4'},
- ];
- element.patchNum = 2;
- element.basePatchNum = 'PARENT';
- flush();
-
- // Should be recomputed for each available patch
- sinon.stub(element, '_computePatchDropdownContent');
- element.set('basePatchNum', 1);
- assert.equal(element._computePatchDropdownContent.callCount, 1);
- });
-
- test('_computePatchDropdownContent', () => {
- const availablePatches = [
- {num: 'edit', sha: '1'},
- {num: 3, sha: '2'},
- {num: 2, sha: '3'},
- {num: 1, sha: '4'},
- ];
- const basePatchNum = 1;
- const sortedRevisions = [
- {_number: 3, created: 'Mon, 01 Jan 2001 00:00:00 GMT'},
- {_number: EditPatchSetNum, basePatchNum: 2},
- {_number: 2, description: 'description'},
- {_number: 1},
- ];
-
- const expectedResult = [
- {
- disabled: false,
- triggerText: 'edit',
- text: 'edit | 1',
- mobileText: 'edit',
- bottomText: '',
- value: 'edit',
- },
- {
- disabled: false,
- triggerText: 'Patchset 3',
- text: 'Patchset 3 | 2',
- mobileText: '3',
- bottomText: '',
- value: 3,
- date: 'Mon, 01 Jan 2001 00:00:00 GMT',
- },
- {
- disabled: false,
- triggerText: 'Patchset 2',
- text: 'Patchset 2 | 3',
- mobileText: '2 description',
- bottomText: 'description',
- value: 2,
- },
- {
- disabled: true,
- triggerText: 'Patchset 1',
- text: 'Patchset 1 | 4',
- mobileText: '1',
- bottomText: '',
- value: 1,
- },
- ];
-
- assert.deepEqual(element._computePatchDropdownContent(availablePatches,
- basePatchNum, sortedRevisions, element.changeComments),
- expectedResult);
- });
-
- test('filesWeblinks', () => {
- element.filesWeblinks = {
- meta_a: [
- {
- name: 'foo',
- url: 'f.oo',
- },
- ],
- meta_b: [
- {
- name: 'bar',
- url: 'ba.r',
- },
- ],
- };
- flush();
- const domApi = dom(element.root);
- assert.equal(
- domApi.querySelector('a[href="f.oo"]').textContent, 'foo');
- assert.equal(
- domApi.querySelector('a[href="ba.r"]').textContent, 'bar');
- });
-
- test('_computePatchSetCommentsString', () => {
- // Test string with unresolved comments.
- const comments = {
- foo: [{
- id: '27dcee4d_f7b77cfa',
- message: 'test',
- patch_set: 1,
- unresolved: true,
- updated: '2017-10-11 20:48:40.000000000',
- }],
- bar: [
- {
- id: '27dcee4d_f7b77cfa',
- message: 'test',
- patch_set: 1,
- updated: '2017-10-12 20:48:40.000000000',
- },
- {
- id: '27dcee4d_f7b77cfa',
- message: 'test',
- patch_set: 1,
- updated: '2017-10-13 20:48:40.000000000',
- },
- ],
- abc: [],
- // Patchset level comment does not contribute to the count
- [SpecialFilePath.PATCHSET_LEVEL_COMMENTS]: [{
- id: '27dcee4d_f7b77cfa',
- message: 'test',
- patch_set: 1,
- unresolved: true,
- updated: '2017-10-11 20:48:40.000000000',
- }],
- };
- element.changeComments = new ChangeComments(comments);
-
- assert.equal(element._computePatchSetCommentsString(
- element.changeComments, 1), ' (3 comments, 1 unresolved)');
-
- // Test string with no unresolved comments.
- delete element.changeComments._comments['foo'];
- assert.equal(element._computePatchSetCommentsString(
- element.changeComments, 1), ' (2 comments)');
-
- // Test string with no comments.
- delete element.changeComments._comments['bar'];
- assert.equal(element._computePatchSetCommentsString(
- element.changeComments, 1), '');
- });
-
- test('patch-range-change fires', () => {
- const handler = sinon.stub();
- element.basePatchNum = 1;
- element.patchNum = 3;
- element.addEventListener('patch-range-change', handler);
-
- element.$.basePatchDropdown._handleValueChange(2, [{value: 2}]);
- assert.isTrue(handler.calledOnce);
- assert.deepEqual(handler.lastCall.args[0].detail,
- {basePatchNum: 2, patchNum: 3});
-
- // BasePatchNum should not have changed, due to one-way data binding.
- element.$.patchNumDropdown._handleValueChange('edit', [{value: 'edit'}]);
- assert.deepEqual(handler.lastCall.args[0].detail,
- {basePatchNum: 1, patchNum: 'edit'});
- });
-});
-
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
new file mode 100644
index 0000000..a47b685
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
@@ -0,0 +1,491 @@
+/**
+ * @license
+ * Copyright (C) 2015 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-comment-api/gr-comment-api';
+import '../../shared/revision-info/revision-info';
+import './gr-patch-range-select';
+import {GrPatchRangeSelect} from './gr-patch-range-select';
+import '../../../test/mocks/comment-api';
+import {RevisionInfo as RevisionInfoClass} from '../../shared/revision-info/revision-info';
+import {ChangeComments} from '../gr-comment-api/gr-comment-api';
+import {stubRestApi} from '../../../test/test-utils';
+import {
+ BasePatchSetNum,
+ EditPatchSetNum,
+ PatchSetNum,
+ RevisionInfo,
+ Timestamp,
+ UrlEncodedCommentId,
+ PathToCommentsInfoMap,
+} from '../../../types/common';
+import {EditRevisionInfo, ParsedChangeInfo} from '../../../types/types';
+import {SpecialFilePath} from '../../../constants/constants';
+import {
+ createEditRevision,
+ createRevision,
+} from '../../../test/test-data-generators';
+import {PatchSet} from '../../../utils/patch-set-util';
+import {
+ DropdownItem,
+ GrDropdownList,
+} from '../../shared/gr-dropdown-list/gr-dropdown-list';
+import {queryAndAssert} from '../../../test/test-utils';
+
+const basicFixture = fixtureFromElement('gr-patch-range-select');
+
+type RevIdToRevisionInfo = {
+ [revisionId: string]: RevisionInfo | EditRevisionInfo;
+};
+
+suite('gr-patch-range-select tests', () => {
+ let element: GrPatchRangeSelect;
+
+ function getInfo(revisions: RevisionInfo[]) {
+ const revisionObj: Partial<RevIdToRevisionInfo> = {};
+ for (let i = 0; i < revisions.length; i++) {
+ revisionObj[i] = revisions[i];
+ }
+ return new RevisionInfoClass({revisions: revisionObj} as ParsedChangeInfo);
+ }
+
+ setup(async () => {
+ stubRestApi('getDiffComments').returns(Promise.resolve({}));
+ stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
+ stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
+
+ // Element must be wrapped in an element with direct access to the
+ // comment API.
+ element = basicFixture.instantiate();
+
+ // Stub methods on the changeComments object after changeComments has
+ // been initialized.
+ element.changeComments = new ChangeComments();
+ await element.updateComplete;
+ });
+
+ test('enabled/disabled options', () => {
+ const patchRange = {
+ basePatchNum: 'PARENT' as PatchSetNum,
+ patchNum: 3 as PatchSetNum,
+ };
+ const sortedRevisions = [
+ createRevision(3) as RevisionInfo,
+ createEditRevision(2) as EditRevisionInfo,
+ createRevision(2) as RevisionInfo,
+ createRevision(1) as RevisionInfo,
+ ];
+ for (const patchNum of [1, 2, 3]) {
+ assert.isFalse(
+ element._computeRightDisabled(
+ patchRange.basePatchNum,
+ patchNum as PatchSetNum,
+ sortedRevisions
+ )
+ );
+ }
+ for (const basePatchNum of [1, 2]) {
+ assert.isFalse(
+ element._computeLeftDisabled(
+ basePatchNum as PatchSetNum,
+ patchRange.patchNum,
+ sortedRevisions
+ )
+ );
+ }
+ assert.isTrue(
+ element._computeLeftDisabled(3 as PatchSetNum, patchRange.patchNum, [])
+ );
+
+ patchRange.basePatchNum = EditPatchSetNum;
+ assert.isTrue(
+ element._computeLeftDisabled(
+ 3 as PatchSetNum,
+ patchRange.patchNum,
+ sortedRevisions
+ )
+ );
+ assert.isTrue(
+ element._computeRightDisabled(
+ patchRange.basePatchNum,
+ 1 as PatchSetNum,
+ sortedRevisions
+ )
+ );
+ assert.isTrue(
+ element._computeRightDisabled(
+ patchRange.basePatchNum,
+ 2 as PatchSetNum,
+ sortedRevisions
+ )
+ );
+ assert.isFalse(
+ element._computeRightDisabled(
+ patchRange.basePatchNum,
+ 3 as PatchSetNum,
+ sortedRevisions
+ )
+ );
+ assert.isTrue(
+ element._computeRightDisabled(
+ patchRange.basePatchNum,
+ EditPatchSetNum,
+ sortedRevisions
+ )
+ );
+ });
+
+ test('_computeBaseDropdownContent', () => {
+ const availablePatches = [
+ {num: 'edit', sha: '1'} as PatchSet,
+ {num: 3, sha: '2'} as PatchSet,
+ {num: 2, sha: '3'} as PatchSet,
+ {num: 1, sha: '4'} as PatchSet,
+ ];
+ const revisions: RevisionInfo[] = [
+ createRevision(2),
+ createRevision(3),
+ createRevision(1),
+ createRevision(4),
+ ];
+ element.revisionInfo = getInfo(revisions);
+ const sortedRevisions = [
+ createRevision(3) as RevisionInfo,
+ createEditRevision(2) as EditRevisionInfo,
+ createRevision(2) as RevisionInfo,
+ createRevision(1) as RevisionInfo,
+ ];
+ const expectedResult: DropdownItem[] = [
+ {
+ disabled: true,
+ triggerText: 'Patchset edit',
+ text: 'Patchset edit | 1',
+ mobileText: 'edit',
+ bottomText: '',
+ value: 'edit',
+ },
+ {
+ disabled: true,
+ triggerText: 'Patchset 3',
+ text: 'Patchset 3 | 2',
+ mobileText: '3',
+ bottomText: '',
+ value: 3,
+ date: '2020-02-01 01:02:03.000000000' as Timestamp,
+ } as DropdownItem,
+ {
+ disabled: true,
+ triggerText: 'Patchset 2',
+ text: 'Patchset 2 | 3',
+ mobileText: '2',
+ bottomText: '',
+ value: 2,
+ date: '2020-02-01 01:02:03.000000000' as Timestamp,
+ } as DropdownItem,
+ {
+ disabled: true,
+ triggerText: 'Patchset 1',
+ text: 'Patchset 1 | 4',
+ mobileText: '1',
+ bottomText: '',
+ value: 1,
+ date: '2020-02-01 01:02:03.000000000' as Timestamp,
+ } as DropdownItem,
+ {
+ text: 'Base',
+ value: 'PARENT',
+ } as DropdownItem,
+ ];
+ assert.deepEqual(
+ element._computeBaseDropdownContent(
+ availablePatches,
+ 1 as PatchSetNum,
+ sortedRevisions,
+ element.changeComments,
+ element.revisionInfo
+ ),
+ expectedResult
+ );
+ });
+
+ test('_computeBaseDropdownContent called when patchNum updates', async () => {
+ element.revisions = [
+ createRevision(2),
+ createRevision(3),
+ createRevision(1),
+ createRevision(4),
+ ];
+ element.revisionInfo = getInfo(element.revisions);
+ element.availablePatches = [
+ {num: 1, sha: '1'} as PatchSet,
+ {num: 2, sha: '2'} as PatchSet,
+ {num: 3, sha: '3'} as PatchSet,
+ {num: 'edit', sha: '4'} as PatchSet,
+ ];
+ element.patchNum = 2 as PatchSetNum;
+ element.basePatchNum = 'PARENT' as BasePatchSetNum;
+ await element.updateComplete;
+
+ const baseDropDownStub = sinon.stub(element, '_computeBaseDropdownContent');
+
+ // Should be recomputed for each available patch
+ element.patchNum = 1 as PatchSetNum;
+ await element.updateComplete;
+ assert.equal(baseDropDownStub.callCount, 1);
+ });
+
+ test('_computeBaseDropdownContent called when changeComments update', async () => {
+ element.revisions = [
+ createRevision(2),
+ createRevision(3),
+ createRevision(1),
+ createRevision(4),
+ ];
+ element.revisionInfo = getInfo(element.revisions);
+ element.availablePatches = [
+ {num: 3, sha: '2'} as PatchSet,
+ {num: 2, sha: '3'} as PatchSet,
+ {num: 1, sha: '4'} as PatchSet,
+ ];
+ element.patchNum = 2 as PatchSetNum;
+ element.basePatchNum = 'PARENT' as BasePatchSetNum;
+ await element.updateComplete;
+
+ // Should be recomputed for each available patch
+ const baseDropDownStub = sinon.stub(element, '_computeBaseDropdownContent');
+ assert.equal(baseDropDownStub.callCount, 0);
+ element.changeComments = new ChangeComments();
+ await element.updateComplete;
+ assert.equal(baseDropDownStub.callCount, 1);
+ });
+
+ test('_computePatchDropdownContent called when basePatchNum updates', async () => {
+ element.revisions = [
+ createRevision(2),
+ createRevision(3),
+ createRevision(1),
+ createRevision(4),
+ ];
+ element.revisionInfo = getInfo(element.revisions);
+ element.availablePatches = [
+ {num: 1, sha: '1'} as PatchSet,
+ {num: 2, sha: '2'} as PatchSet,
+ {num: 3, sha: '3'} as PatchSet,
+ {num: 'edit', sha: '4'} as PatchSet,
+ ];
+ element.patchNum = 2 as PatchSetNum;
+ element.basePatchNum = 'PARENT' as BasePatchSetNum;
+ await element.updateComplete;
+
+ // Should be recomputed for each available patch
+ const baseDropDownStub = sinon.stub(
+ element,
+ '_computePatchDropdownContent'
+ );
+ element.basePatchNum = 1 as BasePatchSetNum;
+ await element.updateComplete;
+ assert.equal(baseDropDownStub.callCount, 1);
+ });
+
+ test('_computePatchDropdownContent', () => {
+ const availablePatches: PatchSet[] = [
+ {num: 'edit', sha: '1'} as PatchSet,
+ {num: 3, sha: '2'} as PatchSet,
+ {num: 2, sha: '3'} as PatchSet,
+ {num: 1, sha: '4'} as PatchSet,
+ ];
+ const basePatchNum = 1;
+ const sortedRevisions = [
+ createRevision(3) as RevisionInfo,
+ createEditRevision(2) as EditRevisionInfo,
+ createRevision(2, 'description') as RevisionInfo,
+ createRevision(1) as RevisionInfo,
+ ];
+
+ const expectedResult: DropdownItem[] = [
+ {
+ disabled: false,
+ triggerText: 'edit',
+ text: 'edit | 1',
+ mobileText: 'edit',
+ bottomText: '',
+ value: 'edit',
+ },
+ {
+ disabled: false,
+ triggerText: 'Patchset 3',
+ text: 'Patchset 3 | 2',
+ mobileText: '3',
+ bottomText: '',
+ value: 3,
+ date: '2020-02-01 01:02:03.000000000' as Timestamp,
+ } as DropdownItem,
+ {
+ disabled: false,
+ triggerText: 'Patchset 2',
+ text: 'Patchset 2 | 3',
+ mobileText: '2 description',
+ bottomText: 'description',
+ value: 2,
+ date: '2020-02-01 01:02:03.000000000' as Timestamp,
+ } as DropdownItem,
+ {
+ disabled: true,
+ triggerText: 'Patchset 1',
+ text: 'Patchset 1 | 4',
+ mobileText: '1',
+ bottomText: '',
+ value: 1,
+ date: '2020-02-01 01:02:03.000000000' as Timestamp,
+ } as DropdownItem,
+ ];
+
+ assert.deepEqual(
+ element._computePatchDropdownContent(
+ availablePatches,
+ basePatchNum as BasePatchSetNum,
+ sortedRevisions,
+ element.changeComments
+ ),
+ expectedResult
+ );
+ });
+
+ test('filesWeblinks', async () => {
+ element.filesWeblinks = {
+ meta_a: [
+ {
+ name: 'foo',
+ url: 'f.oo',
+ },
+ ],
+ meta_b: [
+ {
+ name: 'bar',
+ url: 'ba.r',
+ },
+ ],
+ };
+ await element.updateComplete;
+ assert.equal(
+ queryAndAssert(element, 'a[href="f.oo"]').textContent!.trim(),
+ 'foo'
+ );
+ assert.equal(
+ queryAndAssert(element, 'a[href="ba.r"]').textContent!.trim(),
+ 'bar'
+ );
+ });
+
+ test('_computePatchSetCommentsString', () => {
+ // Test string with unresolved comments.
+ const comments: PathToCommentsInfoMap = {
+ foo: [
+ {
+ id: '27dcee4d_f7b77cfa' as UrlEncodedCommentId,
+ message: 'test',
+ patch_set: 1 as PatchSetNum,
+ unresolved: true,
+ updated: '2017-10-11 20:48:40.000000000' as Timestamp,
+ },
+ ],
+ bar: [
+ {
+ id: '27dcee4d_f7b77cfa' as UrlEncodedCommentId,
+ message: 'test',
+ patch_set: 1 as PatchSetNum,
+ updated: '2017-10-12 20:48:40.000000000' as Timestamp,
+ },
+ {
+ id: '27dcee4d_f7b77cfa' as UrlEncodedCommentId,
+ message: 'test',
+ patch_set: 1 as PatchSetNum,
+ updated: '2017-10-13 20:48:40.000000000' as Timestamp,
+ },
+ ],
+ abc: [],
+ // Patchset level comment does not contribute to the count
+ [SpecialFilePath.PATCHSET_LEVEL_COMMENTS]: [
+ {
+ id: '27dcee4d_f7b77cfa' as UrlEncodedCommentId,
+ message: 'test',
+ patch_set: 1 as PatchSetNum,
+ unresolved: true,
+ updated: '2017-10-11 20:48:40.000000000' as Timestamp,
+ },
+ ],
+ };
+ element.changeComments = new ChangeComments(comments);
+
+ assert.equal(
+ element._computePatchSetCommentsString(
+ element.changeComments,
+ 1 as PatchSetNum
+ ),
+ ' (3 comments, 1 unresolved)'
+ );
+
+ // Test string with no unresolved comments.
+ delete comments['foo'];
+ element.changeComments = new ChangeComments(comments);
+ assert.equal(
+ element._computePatchSetCommentsString(
+ element.changeComments,
+ 1 as PatchSetNum
+ ),
+ ' (2 comments)'
+ );
+
+ // Test string with no comments.
+ delete comments['bar'];
+ element.changeComments = new ChangeComments(comments);
+ assert.equal(
+ element._computePatchSetCommentsString(
+ element.changeComments,
+ 1 as PatchSetNum
+ ),
+ ''
+ );
+ });
+
+ test('patch-range-change fires', () => {
+ const handler = sinon.stub();
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 3 as PatchSetNum;
+ element.addEventListener('patch-range-change', handler);
+
+ queryAndAssert<GrDropdownList>(
+ element,
+ '#basePatchDropdown'
+ )._handleValueChange('2', [{text: '', value: '2'}]);
+ assert.isTrue(handler.calledOnce);
+ assert.deepEqual(handler.lastCall.args[0].detail, {
+ basePatchNum: 2,
+ patchNum: 3,
+ });
+
+ // BasePatchNum should not have changed, due to one-way data binding.
+ queryAndAssert<GrDropdownList>(
+ element,
+ '#patchNumDropdown'
+ )._handleValueChange('edit', [{text: '', value: 'edit'}]);
+ assert.deepEqual(handler.lastCall.args[0].detail, {
+ basePatchNum: 1,
+ patchNum: 'edit',
+ });
+ });
+});
diff --git a/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts b/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
index dcf7236..8ce8ce2 100644
--- a/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
+++ b/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
@@ -55,7 +55,7 @@
override render() {
const icon = this.icon ?? '';
return html` <div class="row">
- <iron-icon class="icon" .icon=${icon}></iron-icon>
+ <iron-icon class="icon" .icon=${icon} aria-hidden="true"></iron-icon>
<slot></slot>
</div>`;
}
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index 24ebd67..a295588 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -259,7 +259,14 @@
_viewEditInChangeView() {
if (this._change)
- GerritNav.navigateToChange(this._change, undefined, undefined, true);
+ GerritNav.navigateToChange(
+ this._change,
+ undefined,
+ undefined,
+ true,
+ undefined,
+ true
+ );
}
_getFileData(
diff --git a/polygerrit-ui/app/elements/gr-app-types.ts b/polygerrit-ui/app/elements/gr-app-types.ts
index 6c8bdb9..39070bd 100644
--- a/polygerrit-ui/app/elements/gr-app-types.ts
+++ b/polygerrit-ui/app/elements/gr-app-types.ts
@@ -124,8 +124,9 @@
edit?: boolean;
patchNum?: RevisionPatchSetNum;
basePatchNum?: BasePatchSetNum;
- queryMap?: Map<string, string> | URLSearchParams;
commentId?: UrlEncodedCommentId;
+ forceReload?: boolean;
+ tab?: string;
}
export interface AppElementJustRegisteredParams {
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 1ab469f..25e9de8 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -20,6 +20,7 @@
import '../../../styles/gr-form-styles';
import '../../../styles/gr-menu-page-styles';
import '../../../styles/gr-page-nav-styles';
+import '../../../styles/gr-paper-styles';
import '../../../styles/shared-styles';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
import '../gr-change-table-editor/gr-change-table-editor';
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_html.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_html.ts
index 81e4fc4..c1ebcac 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_html.ts
@@ -20,6 +20,9 @@
<style include="gr-font-styles">
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
</style>
+ <style include="gr-paper-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="shared-styles">
:host {
color: var(--primary-text-color);
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index 6b2e5c4..246dcd9 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -41,7 +41,7 @@
SpecialFilePath,
} from '../../../constants/constants';
import {computeDisplayPath} from '../../../utils/path-list-util';
-import {computed, customElement, observe, property} from '@polymer/decorators';
+import {customElement, observe, property} from '@polymer/decorators';
import {
AccountDetailInfo,
CommentRange,
@@ -201,6 +201,9 @@
@property({type: Array})
layers: DiffLayer[] = [];
+ @property({type: Object, computed: 'computeDiff(comments, path)'})
+ _diff?: DiffInfo;
+
/** Called in disconnectedCallback. */
private cleanups: (() => void)[] = [];
@@ -261,15 +264,19 @@
this._setInitialExpandedState();
}
- @computed('comments', 'path')
- get _diff() {
- if (this.comments === undefined || this.path === undefined) return;
- if (!this.comments[0]?.context_lines?.length) return;
+ computeDiff(comments?: UIComment[], path?: string) {
+ if (comments === undefined || path === undefined) return undefined;
+ if (!comments[0]?.context_lines?.length) return undefined;
const diff = computeDiffFromContext(
- this.comments[0].context_lines,
- this.path,
- this.comments[0].source_content_type
+ comments[0].context_lines,
+ path,
+ comments[0].source_content_type
);
+ // Do we really have to re-compute (and re-render) the diff?
+ if (this._diff && JSON.stringify(this._diff) === JSON.stringify(diff)) {
+ return this._diff;
+ }
+
if (!anyLineTooLong(diff)) {
this.syntaxLayer.init(diff);
waitForEventOnce(this, 'render').then(() => {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 154a045..4f6702d 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -38,11 +38,11 @@
import {GrOverlay} from '../gr-overlay/gr-overlay';
import {
AccountDetailInfo,
- NumericChangeId,
+ BasePatchSetNum,
ConfigInfo,
+ NumericChangeId,
PatchSetNum,
RepoName,
- BasePatchSetNum,
} from '../../../types/common';
import {GrButton} from '../gr-button/gr-button';
import {GrConfirmDeleteCommentDialog} from '../gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog';
@@ -60,6 +60,7 @@
import {debounce, DelayedTask} from '../../../utils/async-util';
import {StorageLocation} from '../../../services/storage/gr-storage';
import {addShortcut, Key, Modifier} from '../../../utils/dom-util';
+import {Interaction} from '../../../constants/reporting';
const STORAGE_DEBOUNCE_INTERVAL = 400;
const TOAST_DEBOUNCE_INTERVAL = 200;
@@ -489,6 +490,8 @@
return this._discardDraft();
}
+ const details = this.commentDetailsForReporting();
+ this.reporting.reportInteraction(Interaction.SAVE_COMMENT, details);
this._xhrPromise = this._saveDraft(comment)
.then(response => {
this.disabled = false;
@@ -508,6 +511,8 @@
}
if (!resComment.patch_set) resComment.patch_set = this.patchNum;
this.comment = resComment;
+ const details = this.commentDetailsForReporting();
+ this.reporting.reportInteraction(Interaction.COMMENT_SAVED, details);
this._fireSave();
return obj;
});
@@ -520,6 +525,17 @@
return this._xhrPromise;
}
+ private commentDetailsForReporting() {
+ return {
+ id: this.comment?.id,
+ message_length: this.comment?.message?.length,
+ in_reply_to: this.comment?.in_reply_to,
+ unresolved: this.comment?.unresolved,
+ path_length: this.comment?.path?.length,
+ line: this.comment?.range?.start_line ?? this.comment?.line,
+ };
+ }
+
_eraseDraftCommentFromStorage() {
// Prevents a race condition in which removing the draft comment occurs
// prior to it being saved.
@@ -765,7 +781,7 @@
const timer = this.reporting.getTimer(timingLabel);
this.set('comment.__editing', false);
return this.save().then(() => {
- timer.end();
+ timer.end({id: this.comment?.id});
});
}
@@ -849,7 +865,7 @@
if (!response.ok) {
this.discarding = false;
}
- timer.end();
+ timer.end({id: this.comment?.id});
this._fireDiscard();
return response;
})
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
index 8322682..cac3d59 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
@@ -17,6 +17,7 @@
import '@polymer/paper-tabs/paper-tab';
import '@polymer/paper-tabs/paper-tabs';
import '../gr-shell-command/gr-shell-command';
+import '../../../styles/gr-paper-styles';
import '../../../styles/shared-styles';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-download-commands_html';
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts
index 5a75c13..f9c08ba 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts
@@ -17,6 +17,9 @@
import {html} from '@polymer/polymer/lib/utils/html-tag';
export const htmlTemplate = html`
+ <style include="gr-paper-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="shared-styles">
paper-tabs {
height: 3rem;
diff --git a/polygerrit-ui/app/mixins/hovercard-mixin/hovercard-mixin_test.ts b/polygerrit-ui/app/mixins/hovercard-mixin/hovercard-mixin_test.ts
index dd86b38..e6b63e6 100644
--- a/polygerrit-ui/app/mixins/hovercard-mixin/hovercard-mixin_test.ts
+++ b/polygerrit-ui/app/mixins/hovercard-mixin/hovercard-mixin_test.ts
@@ -47,7 +47,7 @@
let element: HovercardMixinTest;
let button: HTMLElement;
- let testPromise: MockPromise;
+ let testPromise: MockPromise<void>;
setup(() => {
testPromise = mockPromise();
diff --git a/polygerrit-ui/app/styles/gr-paper-styles.ts b/polygerrit-ui/app/styles/gr-paper-styles.ts
new file mode 100644
index 0000000..1ef7124
--- /dev/null
+++ b/polygerrit-ui/app/styles/gr-paper-styles.ts
@@ -0,0 +1,60 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {css} from 'lit';
+
+export const paperStyles = css`
+ paper-toggle-button {
+ --paper-toggle-button-checked-bar-color: var(--link-color);
+ --paper-toggle-button-checked-button-color: var(--link-color);
+ }
+ paper-tabs {
+ font-size: var(--font-size-h3);
+ font-weight: var(--font-weight-h3);
+ line-height: var(--line-height-h3);
+ --paper-font-common-base: {
+ font-family: var(--header-font-family);
+ -webkit-font-smoothing: initial;
+ }
+ --paper-tab-content: {
+ margin-bottom: var(--spacing-s);
+ }
+ --paper-tab-content-focused: {
+ /* paper-tabs uses 700 here, which can look awkward */
+ font-weight: var(--font-weight-h3);
+ background: var(--gray-background-focus);
+ }
+ --paper-tab-content-unselected: {
+ /* paper-tabs uses 0.8 here, but we want to control the color directly */
+ opacity: 1;
+ color: var(--deemphasized-text-color);
+ }
+ }
+ paper-tab:focus {
+ padding-left: 0px;
+ padding-right: 0px;
+ }
+`;
+
+const $_documentContainer = document.createElement('template');
+$_documentContainer.innerHTML = `<dom-module id="gr-paper-styles">
+ <template>
+ <style>
+ ${paperStyles.cssText}
+ </style>
+ </template>
+</dom-module>`;
+document.head.appendChild($_documentContainer.content);
diff --git a/polygerrit-ui/app/styles/shared-styles.ts b/polygerrit-ui/app/styles/shared-styles.ts
index 98f6eb2..e99cf27 100644
--- a/polygerrit-ui/app/styles/shared-styles.ts
+++ b/polygerrit-ui/app/styles/shared-styles.ts
@@ -189,36 +189,6 @@
.separator.transparent {
border-color: transparent;
}
- paper-toggle-button {
- --paper-toggle-button-checked-bar-color: var(--link-color);
- --paper-toggle-button-checked-button-color: var(--link-color);
- }
- paper-tabs {
- font-size: var(--font-size-h3);
- font-weight: var(--font-weight-h3);
- line-height: var(--line-height-h3);
- --paper-font-common-base: {
- font-family: var(--header-font-family);
- -webkit-font-smoothing: initial;
- }
- --paper-tab-content: {
- margin-bottom: var(--spacing-s);
- }
- --paper-tab-content-focused: {
- /* paper-tabs uses 700 here, which can look awkward */
- font-weight: var(--font-weight-h3);
- background: var(--gray-background-focus);
- }
- --paper-tab-content-unselected: {
- /* paper-tabs uses 0.8 here, but we want to control the color directly */
- opacity: 1;
- color: var(--deemphasized-text-color);
- }
- }
- paper-tab:focus {
- padding-left: 0px;
- padding-right: 0px;
- }
iron-autogrow-textarea {
/** This is needed for firefox */
--iron-autogrow-textarea_-_white-space: pre-wrap;
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index 351cc13..dd56ce2 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -230,7 +230,10 @@
};
}
-export function createRevision(patchSetNum = 1): RevisionInfo {
+export function createRevision(
+ patchSetNum = 1,
+ description = ''
+): RevisionInfo {
return {
_number: patchSetNum as PatchSetNum,
commit: createCommit(),
@@ -238,13 +241,14 @@
kind: RevisionKind.REWORK,
ref: 'refs/changes/5/6/1' as GitRef,
uploader: createAccountWithId(),
+ description,
};
}
-export function createEditRevision(): EditRevisionInfo {
+export function createEditRevision(basePatchNum = 1): EditRevisionInfo {
return {
_number: EditPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
+ basePatchNum: basePatchNum as BasePatchSetNum,
commit: createCommit(),
};
}
@@ -270,7 +274,7 @@
[revisionId: string]: RevisionInfo;
} {
const revisions: {[revisionId: string]: RevisionInfo} = {};
- const revisionDate = TEST_CHANGE_CREATED;
+ let revisionDate = TEST_CHANGE_CREATED;
const revisionIdStart = 1; // The same as getCurrentRevision
for (let i = 0; i < count; i++) {
const revisionId = (i + revisionIdStart).toString(16);
@@ -281,6 +285,7 @@
};
revisions[revisionId] = revision;
// advance 1 day
+ revisionDate = new Date(revisionDate);
revisionDate.setDate(revisionDate.getDate() + 1);
}
return revisions;
@@ -294,12 +299,13 @@
export function createChangeMessages(count: number): ChangeMessageInfo[] {
const messageIdStart = 1000;
const messages: ChangeMessageInfo[] = [];
- const messageDate = TEST_CHANGE_CREATED;
+ let messageDate = TEST_CHANGE_CREATED;
for (let i = 0; i < count; i++) {
messages.push({
...createChangeMessageInfo((i + messageIdStart).toString(16)),
date: dateToTimestamp(messageDate),
});
+ messageDate = new Date(messageDate);
messageDate.setDate(messageDate.getDate() + 1);
}
return messages;
diff --git a/polygerrit-ui/app/test/test-utils.ts b/polygerrit-ui/app/test/test-utils.ts
index ed8d792..4a513f8 100644
--- a/polygerrit-ui/app/test/test-utils.ts
+++ b/polygerrit-ui/app/test/test-utils.ts
@@ -29,18 +29,18 @@
import {queryAndAssert, query} from '../utils/common-util';
export {query, queryAll, queryAndAssert} from '../utils/common-util';
-export interface MockPromise extends Promise<unknown> {
- resolve: (value?: unknown) => void;
+export interface MockPromise<T> extends Promise<T> {
+ resolve: (value?: T) => void;
}
-export const mockPromise = () => {
- let res: (value?: unknown) => void;
- const promise: MockPromise = new Promise(resolve => {
+export function mockPromise<T = unknown>(): MockPromise<T> {
+ let res: (value?: T) => void;
+ const promise: MockPromise<T> = new Promise<T | undefined>(resolve => {
res = resolve;
- }) as MockPromise;
+ }) as MockPromise<T>;
promise.resolve = res!;
return promise;
-};
+}
export function isHidden(el: Element | undefined | null) {
if (!el) return true;
diff --git a/polygerrit-ui/app/utils/dom-util_test.ts b/polygerrit-ui/app/utils/dom-util_test.ts
index 9dd5be2..e139805 100644
--- a/polygerrit-ui/app/utils/dom-util_test.ts
+++ b/polygerrit-ui/app/utils/dom-util_test.ts
@@ -28,22 +28,28 @@
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {html} from '@polymer/polymer/lib/utils/html-tag';
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
-import {queryAndAssert} from '../test/test-utils';
+import {mockPromise, queryAndAssert} from '../test/test-utils';
-async function keyEventOn(
+/**
+ * You might think that instead of passing in the callback with assertions as a
+ * parameter that you could as well just `await keyEventOn()` and *then* run
+ * your assertions. But at that point the event is not "hot" anymore, so most
+ * likely you want to assert stuff about the event within the callback
+ * parameter.
+ */
+function keyEventOn(
el: HTMLElement,
callback: (e: KeyboardEvent) => void,
keyCode = 75,
key = 'k'
): Promise<KeyboardEvent> {
- let resolve: (e: KeyboardEvent) => void;
- const promise = new Promise<KeyboardEvent>(r => (resolve = r));
+ const promise = mockPromise<KeyboardEvent>();
el.addEventListener('keydown', (e: KeyboardEvent) => {
callback(e);
- resolve(e);
+ promise.resolve(e);
});
MockInteractions.keyDownOn(el, keyCode, null, key);
- return await promise;
+ return promise;
}
class TestEle extends PolymerElement {
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index 0fd4ca4..1a48a7b 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -287,3 +287,14 @@
);
return priorityRequirementList.concat(nonPriorityRequirements);
}
+
+export function getTriggerVotes(change?: ParsedChangeInfo | ChangeInfo) {
+ const allLabels = Object.keys(change?.labels ?? {});
+ const submitReqs = getRequirements(change);
+ const labelAssociatedWithSubmitReqs = submitReqs
+ .flatMap(req => extractAssociatedLabels(req))
+ .filter(unique);
+ return allLabels.filter(
+ label => !labelAssociatedWithSubmitReqs.includes(label)
+ );
+}
diff --git a/polygerrit-ui/app/utils/label-util_test.ts b/polygerrit-ui/app/utils/label-util_test.ts
index 6af883b..142c607 100644
--- a/polygerrit-ui/app/utils/label-util_test.ts
+++ b/polygerrit-ui/app/utils/label-util_test.ts
@@ -25,6 +25,7 @@
getVotingRange,
getVotingRangeOrDefault,
getRequirements,
+ getTriggerVotes,
hasNeutralStatus,
labelCompare,
LabelStatus,
@@ -42,6 +43,7 @@
createChange,
createSubmitRequirementExpressionInfo,
createSubmitRequirementResultInfo,
+ createDetailedLabelInfo,
} from '../test/test-data-generators';
import {
SubmitRequirementResultInfo,
@@ -319,4 +321,36 @@
assert.deepEqual(getRequirements(change), [requirement]);
});
});
+
+ suite('getTriggerVotes()', () => {
+ test('no requirements', () => {
+ const triggerVote = 'Trigger-Vote';
+ const change = {
+ ...createChange(),
+ labels: {
+ [triggerVote]: createDetailedLabelInfo(),
+ },
+ };
+ assert.deepEqual(getTriggerVotes(change), [triggerVote]);
+ });
+ test('no trigger votes, all labels associated with sub requirement', () => {
+ const triggerVote = 'Trigger-Vote';
+ const change = {
+ ...createChange(),
+ submit_requirements: [
+ {
+ ...createSubmitRequirementResultInfo(),
+ submittability_expression_result: {
+ ...createSubmitRequirementExpressionInfo(),
+ expression: `label:${triggerVote}=MAX`,
+ },
+ },
+ ],
+ labels: {
+ [triggerVote]: createDetailedLabelInfo(),
+ },
+ };
+ assert.deepEqual(getTriggerVotes(change), []);
+ });
+ });
});
diff --git a/polygerrit-ui/app/utils/patch-set-util.ts b/polygerrit-ui/app/utils/patch-set-util.ts
index ce5e5a4..ee4ed8b 100644
--- a/polygerrit-ui/app/utils/patch-set-util.ts
+++ b/polygerrit-ui/app/utils/patch-set-util.ts
@@ -95,7 +95,7 @@
* @return The correspondent revision obj from {revisions}
*/
export function getRevisionByPatchNum(
- revisions: RevisionInfo[],
+ revisions: (RevisionInfo | EditRevisionInfo)[],
patchNum: PatchSetNum
) {
for (const rev of revisions) {
@@ -309,10 +309,11 @@
*/
export function findSortedIndex(
patchNum: PatchSetNum,
- revisions: RevisionInfo[]
+ revisions: (RevisionInfo | EditRevisionInfo)[]
) {
revisions = revisions || [];
- const findNum = (rev: RevisionInfo) => `${rev._number}` === `${patchNum}`;
+ const findNum = (rev: RevisionInfo | EditRevisionInfo) =>
+ `${rev._number}` === `${patchNum}`;
return revisions.findIndex(findNum);
}
diff --git a/polygerrit-ui/package.json b/polygerrit-ui/package.json
index 793703e..25561bb 100644
--- a/polygerrit-ui/package.json
+++ b/polygerrit-ui/package.json
@@ -14,7 +14,7 @@
"@polymer/test-fixture": "^4.0.2",
"accessibility-developer-tools": "^2.12.0",
"chai": "^4.3.4",
- "karma": "^6.3.4",
+ "karma": "^6.3.6",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
diff --git a/polygerrit-ui/yarn.lock b/polygerrit-ui/yarn.lock
index 7c7ef45..e9c54e9 100644
--- a/polygerrit-ui/yarn.lock
+++ b/polygerrit-ui/yarn.lock
@@ -1094,7 +1094,7 @@
resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.4.tgz#de48cf01c79c9f1560bcfd8ae43217ab028657f8"
integrity sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==
-"@types/cookie@^0.4.0":
+"@types/cookie@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==
@@ -1109,7 +1109,7 @@
"@types/keygrip" "*"
"@types/node" "*"
-"@types/cors@^2.8.8":
+"@types/cors@^2.8.12":
version "2.8.12"
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==
@@ -1498,10 +1498,10 @@
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-base64-arraybuffer@0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
- integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=
+base64-arraybuffer@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz#87bd13525626db4a9838e00a508c2b73efcf348c"
+ integrity sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==
base64id@2.0.0, base64id@~2.0.0:
version "2.0.0"
@@ -1939,7 +1939,7 @@
dependencies:
ms "^2.1.1"
-debug@^4.1.0, debug@^4.1.1, debug@~4.3.1:
+debug@^4.1.0, debug@^4.1.1, debug@~4.3.1, debug@~4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
@@ -2085,25 +2085,28 @@
dependencies:
once "^1.4.0"
-engine.io-parser@~4.0.0:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.2.tgz#e41d0b3fb66f7bf4a3671d2038a154024edb501e"
- integrity sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==
+engine.io-parser@~5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.1.tgz#6695fc0f1e6d76ad4a48300ff80db5f6b3654939"
+ integrity sha512-j4p3WwJrG2k92VISM0op7wiq60vO92MlF3CRGxhKHy9ywG1/Dkc72g0dXeDQ+//hrcDn8gqQzoEkdO9FN0d9AA==
dependencies:
- base64-arraybuffer "0.1.4"
+ base64-arraybuffer "~1.0.1"
-engine.io@~4.1.0:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-4.1.1.tgz#9a8f8a5ac5a5ea316183c489bf7f5b6cf91ace5b"
- integrity sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==
+engine.io@~6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.0.0.tgz#2b993fcd73e6b3a6abb52b40b803651cd5747cf0"
+ integrity sha512-Ui7yl3JajEIaACg8MOUwWvuuwU7jepZqX3BKs1ho7NQRuP4LhN4XIykXhp8bEy+x/DhA0LBZZXYSCkZDqrwMMg==
dependencies:
+ "@types/cookie" "^0.4.1"
+ "@types/cors" "^2.8.12"
+ "@types/node" ">=10.0.0"
accepts "~1.3.4"
base64id "2.0.0"
cookie "~0.4.1"
cors "~2.8.5"
debug "~4.3.1"
- engine.io-parser "~4.0.0"
- ws "~7.4.2"
+ engine.io-parser "~5.0.0"
+ ws "~8.2.3"
ent@~2.2.0:
version "2.2.0"
@@ -2833,10 +2836,10 @@
dependencies:
minimist "^1.2.3"
-karma@^6.3.4:
- version "6.3.4"
- resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.4.tgz#359899d3aab3d6b918ea0f57046fd2a6b68565e6"
- integrity sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q==
+karma@^6.3.6:
+ version "6.3.6"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.6.tgz#6f64cdd558c7d0c9da6fcdece156089582694611"
+ integrity sha512-xsiu3D6AjCv6Uq0YKXJgC6TvXX2WloQ5+XtHXmC1lwiLVG617DDV3W2DdM4BxCMKHlmz6l3qESZHFQGHAKvrew==
dependencies:
body-parser "^1.19.0"
braces "^3.0.2"
@@ -2856,10 +2859,10 @@
qjobs "^1.2.0"
range-parser "^1.2.1"
rimraf "^3.0.2"
- socket.io "^3.1.0"
+ socket.io "^4.2.0"
source-map "^0.6.1"
tmp "^0.2.1"
- ua-parser-js "^0.7.28"
+ ua-parser-js "^0.7.30"
yargs "^16.1.1"
keygrip@~1.1.0:
@@ -3741,12 +3744,12 @@
nise "^5.0.1"
supports-color "^7.1.0"
-socket.io-adapter@~2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz#edc5dc36602f2985918d631c1399215e97a1b527"
- integrity sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==
+socket.io-adapter@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.3.2.tgz#039cd7c71a52abad984a6d57da2c0b7ecdd3c289"
+ integrity sha512-PBZpxUPYjmoogY0aoaTmo1643JelsaS1CiAwNjRVdrI0X9Seuc19Y2Wife8k88avW6haG8cznvwbubAZwH4Mtg==
-socket.io-parser@~4.0.3:
+socket.io-parser@~4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0"
integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==
@@ -3755,20 +3758,17 @@
component-emitter "~1.3.0"
debug "~4.3.1"
-socket.io@^3.1.0:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.1.2.tgz#06e27caa1c4fc9617547acfbb5da9bc1747da39a"
- integrity sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==
+socket.io@^4.2.0:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.3.1.tgz#c0aa14f3f916a8ab713e83a5bd20c16600245763"
+ integrity sha512-HC5w5Olv2XZ0XJ4gOLGzzHEuOCfj3G0SmoW3jLHYYh34EVsIr3EkW9h6kgfW+K3TFEcmYy8JcPWe//KUkBp5jA==
dependencies:
- "@types/cookie" "^0.4.0"
- "@types/cors" "^2.8.8"
- "@types/node" ">=10.0.0"
accepts "~1.3.4"
base64id "~2.0.0"
- debug "~4.3.1"
- engine.io "~4.1.0"
- socket.io-adapter "~2.1.0"
- socket.io-parser "~4.0.3"
+ debug "~4.3.2"
+ engine.io "~6.0.0"
+ socket.io-adapter "~2.3.2"
+ socket.io-parser "~4.0.4"
source-map-support@^0.5.19, source-map-support@~0.5.12:
version "0.5.19"
@@ -4056,10 +4056,10 @@
resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066"
integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==
-ua-parser-js@^0.7.28:
- version "0.7.28"
- resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
- integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
+ua-parser-js@^0.7.30:
+ version "0.7.30"
+ resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.30.tgz#4cf5170e8b55ac553fe8b38df3a82f0669671f0b"
+ integrity sha512-uXEtSresNUlXQ1QL4/3dQORcGv7+J2ookOG2ybA/ga9+HYEXueT2o+8dUJQkpedsyTyCJ6jCCirRcKtdtx1kbg==
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
@@ -4218,10 +4218,10 @@
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-ws@~7.4.2:
- version "7.4.6"
- resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
- integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
+ws@~8.2.3:
+ version "8.2.3"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
+ integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
y18n@^5.0.5:
version "5.0.8"