Merge "ChangeEmail: Fix calculation of insertions and change buckets"
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index 70352dc..e6494e6 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -199,6 +199,14 @@
=== Change
+* `change/count_rebases`: Total number of rebases
+** `on_behalf_of_uploader`:
+ Whether the rebase was done on behalf of the uploader.
+** `rebase_chain`:
+ Whether a chain was rebased.
+* `change/submitted_with_rebaser_approval`: Number of rebased changes that were
+ submitted with a Code-Review approval of the rebaser that would not have been
+ submittable if the rebase was not done on behalf of the uploader.
* `change/submit_rule_evaluation`: Latency for evaluating submit rules on a
change.
* `change/submit_type_evaluation`: Latency for evaluating the submit type on a
diff --git a/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java b/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
index 32a8fdf..9120069 100644
--- a/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
@@ -99,6 +99,15 @@
return new EqualsLabelPredicate(args, label, value, account, count);
}
+ public String getLabel() {
+ return magicLabelVote.label();
+ }
+
+ public boolean ignoresUploaderApprovals() {
+ return account.equals(ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID)
+ || account.equals(ChangeQueryBuilder.NON_CONTRIBUTOR_ACCOUNT_ID);
+ }
+
@Nullable
protected static LabelType type(LabelTypes types, String toFind) {
if (types.byLabel(toFind).isPresent()) {
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index fd51fbc..5368c75 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -84,6 +84,7 @@
private final PatchSetUtil patchSetUtil;
private final IdentifiedUser.GenericFactory userFactory;
private final ChangeResource.Factory changeResourceFactory;
+ private final RebaseMetrics rebaseMetrics;
@Inject
public Rebase(
@@ -96,7 +97,8 @@
ProjectCache projectCache,
PatchSetUtil patchSetUtil,
IdentifiedUser.GenericFactory userFactory,
- ChangeResource.Factory changeResourceFactory) {
+ ChangeResource.Factory changeResourceFactory,
+ RebaseMetrics rebaseMetrics) {
this.serverIdent = serverIdent;
this.updateFactory = updateFactory;
this.repoManager = repoManager;
@@ -107,6 +109,7 @@
this.patchSetUtil = patchSetUtil;
this.userFactory = userFactory;
this.changeResourceFactory = changeResourceFactory;
+ this.rebaseMetrics = rebaseMetrics;
}
@Override
@@ -147,6 +150,8 @@
bu.addOp(change.getId(), rebaseOp);
bu.execute();
+ rebaseMetrics.countRebase(input.onBehalfOfUploader);
+
ChangeInfo changeInfo = json.create(OPTIONS).format(change.getProject(), change.getId());
changeInfo.containsGitConflicts =
!rebaseOp.getRebasedCommit().getFilesWithGitConflicts().isEmpty() ? true : null;
diff --git a/java/com/google/gerrit/server/restapi/change/RebaseChain.java b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
index 34a2623..1949c89 100644
--- a/java/com/google/gerrit/server/restapi/change/RebaseChain.java
+++ b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
@@ -86,6 +86,7 @@
private final ProjectCache projectCache;
private final PatchSetUtil patchSetUtil;
private final ChangeJson.Factory json;
+ private final RebaseMetrics rebaseMetrics;
@Inject
RebaseChain(
@@ -99,7 +100,8 @@
ChangeNotes.Factory notesFactory,
ProjectCache projectCache,
PatchSetUtil patchSetUtil,
- ChangeJson.Factory json) {
+ ChangeJson.Factory json,
+ RebaseMetrics rebaseMetrics) {
this.repoManager = repoManager;
this.getRelatedChangesUtil = getRelatedChangesUtil;
this.changeDataFactory = changeDataFactory;
@@ -111,6 +113,7 @@
this.projectCache = projectCache;
this.patchSetUtil = patchSetUtil;
this.json = json;
+ this.rebaseMetrics = rebaseMetrics;
}
@Override
@@ -194,6 +197,8 @@
}
}
+ rebaseMetrics.countRebaseChain(input.onBehalfOfUploader);
+
RebaseChainInfo res = new RebaseChainInfo();
res.rebasedChanges = new ArrayList<>();
ChangeJson changeJson = json.create(OPTIONS);
diff --git a/java/com/google/gerrit/server/restapi/change/RebaseMetrics.java b/java/com/google/gerrit/server/restapi/change/RebaseMetrics.java
new file mode 100644
index 0000000..114a112
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/change/RebaseMetrics.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.restapi.change;
+
+import com.google.gerrit.metrics.Counter2;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Field;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/** Metrics for the rebase REST endpoints ({@link Rebase} and {@link RebaseChain}). */
+@Singleton
+public class RebaseMetrics {
+ private final Counter2<Boolean, Boolean> countRebases;
+
+ @Inject
+ public RebaseMetrics(MetricMaker metricMaker) {
+ this.countRebases =
+ metricMaker.newCounter(
+ "change/count_rebases",
+ new Description("Total number of rebases").setRate(),
+ Field.ofBoolean("on_behalf_of_uploader", (metadataBuilder, isOnBehalfOfUploader) -> {})
+ .description("Whether the rebase was done on behalf of the uploader.")
+ .build(),
+ Field.ofBoolean("rebase_chain", (metadataBuilder, isRebaseChain) -> {})
+ .description("Whether a chain was rebased.")
+ .build());
+ }
+
+ public void countRebase(boolean isOnBehalfOfUploader) {
+ countRebase(isOnBehalfOfUploader, /* isRebaseChain= */ false);
+ }
+
+ public void countRebaseChain(boolean isOnBehalfOfUploader) {
+ countRebase(isOnBehalfOfUploader, /* isRebaseChain= */ true);
+ }
+
+ private void countRebase(boolean isOnBehalfOfUploader, boolean isRebaseChain) {
+ countRebases.increment(/* field1= */ isOnBehalfOfUploader, /* field2= */ isRebaseChain);
+ }
+}
diff --git a/java/com/google/gerrit/server/submit/MergeMetrics.java b/java/com/google/gerrit/server/submit/MergeMetrics.java
new file mode 100644
index 0000000..9eb8061
--- /dev/null
+++ b/java/com/google/gerrit/server/submit/MergeMetrics.java
@@ -0,0 +1,134 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.submit;
+
+import com.google.gerrit.entities.SubmitRequirement;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.metrics.Counter0;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.MagicLabelPredicate;
+import com.google.gerrit.server.query.change.SubmitRequirementChangeQueryBuilder;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/** Metrics are recorded when a change is merged (aka submitted). */
+public class MergeMetrics {
+ private final Provider<SubmitRequirementChangeQueryBuilder> submitRequirementChangequeryBuilder;
+
+ // TODO: This metric is for measuring the impact of allowing users to rebase changes on behalf of
+ // the uploader. Once this feature has been rolled out and its impact as been measured, we may
+ // remove this metric.
+ private final Counter0 countChangesThatWereSubmittedWithRebaserApproval;
+
+ @Inject
+ public MergeMetrics(
+ Provider<SubmitRequirementChangeQueryBuilder> submitRequirementChangequeryBuilder,
+ MetricMaker metricMaker) {
+ this.submitRequirementChangequeryBuilder = submitRequirementChangequeryBuilder;
+
+ this.countChangesThatWereSubmittedWithRebaserApproval =
+ metricMaker.newCounter(
+ "change/submitted_with_rebaser_approval",
+ new Description(
+ "Number of rebased changes that were submitted with a Code-Review approval of"
+ + " the rebaser that would not have been submittable if the rebase was not"
+ + " done on behalf of the uploader.")
+ .setRate());
+ }
+
+ public void countChangesThatWereSubmittedWithRebaserApproval(ChangeData cd) {
+ if (isRebaseOnBehalfOfUploader(cd)
+ && hasCodeReviewApprovalOfRealUploader(cd)
+ && ignoresCodeReviewApprovalsOfUploader(cd)) {
+ // 1. The patch set that is being submitted was created by rebasing on behalf of the uploader.
+ // The uploader of the patch set is the original uploader on whom's behalf the rebase was
+ // done. The real uploader is the user that did the rebase on behalf of the uploader (e.g. by
+ // clicking on the rebase button).
+ //
+ // 2. The change has Code-Review approvals of the real uploader (aka the rebaser).
+ //
+ // 3. Code-Review approvals of the uploader are ignored.
+ //
+ // If instead of a rebase on behalf of the uploader a normal rebase would have been done the
+ // rebaser would have been the uploader of the patch set. In this case the Code-Review
+ // approval of the rebaser would not have counted since Code-Review approvals of the uploader
+ // are ignored.
+ //
+ // In this case we assume that the change would not be submittable if a normal rebase had been
+ // done. This is not always correct (e.g. if there are approvals of multiple reviewers) but
+ // it's good enough for the metric.
+ countChangesThatWereSubmittedWithRebaserApproval.increment();
+ }
+ }
+
+ private boolean isRebaseOnBehalfOfUploader(ChangeData cd) {
+ // If the uploader differs from the real uploader the upload of the patch set has been
+ // impersonated. Impersonating the uploader is only allowed on rebase by rebasing on behalf of
+ // the uploader. Hence if the current patch set has different accounts as uploader and real
+ // uploader we can assume that it was created by rebase on behalf of the uploader.
+ return !cd.currentPatchSet().uploader().equals(cd.currentPatchSet().realUploader());
+ }
+
+ private boolean hasCodeReviewApprovalOfRealUploader(ChangeData cd) {
+ return cd.currentApprovals().stream()
+ .anyMatch(psa -> psa.accountId().equals(cd.currentPatchSet().realUploader()));
+ }
+
+ private boolean ignoresCodeReviewApprovalsOfUploader(ChangeData cd) {
+ for (SubmitRequirement submitRequirement : cd.submitRequirements().keySet()) {
+ try {
+ Predicate<ChangeData> predicate =
+ submitRequirementChangequeryBuilder
+ .get()
+ .parse(submitRequirement.submittabilityExpression().expressionString());
+ return ignoresCodeReviewApprovalsOfUploader(predicate);
+ } catch (QueryParseException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private boolean ignoresCodeReviewApprovalsOfUploader(Predicate<ChangeData> predicate) {
+ if (predicate.getChildCount() == 0) {
+ // Submit requirements may require a Code-Review approval but ignore approvals by the
+ // uploader. This is done by using a label predicate with 'user=non_uploader' or
+ // 'user=non_contributor', e.g. 'label:Code-Review=+2,user=non_uploader'. After the submit
+ // requirement expression has been parsed these label predicates are represented by
+ // MagicLabelPredicate in the predicate tree. Hence to know whether Code-Review approvals of
+ // the uploader are ignored, we must check if there is any MagicLabelPredicate for the
+ // Code-Review label that ignores approvals of the uploader (aka has user set to non_uploader
+ // or non_contributor).
+ if (predicate instanceof MagicLabelPredicate) {
+ MagicLabelPredicate magicLabelPredicate = (MagicLabelPredicate) predicate;
+ if (magicLabelPredicate.getLabel().equalsIgnoreCase("Code-Review")
+ && magicLabelPredicate.ignoresUploaderApprovals()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ for (Predicate<ChangeData> childPredicate : predicate.getChildren()) {
+ if (ignoresCodeReviewApprovalsOfUploader(childPredicate)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 1d3ec73..d299614 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -246,6 +246,7 @@
private final RetryHelper retryHelper;
private final ChangeData.Factory changeDataFactory;
private final StoreSubmitRequirementsOp.Factory storeSubmitRequirementsOpFactory;
+ private final MergeMetrics mergeMetrics;
// Changes that were updated by this MergeOp.
private final Map<Change.Id, Change> updatedChanges;
@@ -280,7 +281,8 @@
TopicMetrics topicMetrics,
RetryHelper retryHelper,
ChangeData.Factory changeDataFactory,
- StoreSubmitRequirementsOp.Factory storeSubmitRequirementsOpFactory) {
+ StoreSubmitRequirementsOp.Factory storeSubmitRequirementsOpFactory,
+ MergeMetrics mergeMetrics) {
this.cmUtil = cmUtil;
this.batchUpdateFactory = batchUpdateFactory;
this.internalUserFactory = internalUserFactory;
@@ -298,6 +300,7 @@
this.changeDataFactory = changeDataFactory;
this.updatedChanges = new HashMap<>();
this.storeSubmitRequirementsOpFactory = storeSubmitRequirementsOpFactory;
+ this.mergeMetrics = mergeMetrics;
}
@Override
@@ -376,6 +379,7 @@
commitStatus.problem(cd.getId(), "Change " + cd.getId() + " is work in progress");
} else {
checkSubmitRequirements(cd);
+ mergeMetrics.countChangesThatWereSubmittedWithRebaserApproval(cd);
}
} catch (ResourceConflictException e) {
commitStatus.problem(cd.getId(), e.getMessage());
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
index b1fb575..3531234 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
@@ -35,6 +35,7 @@
import com.google.gerrit.acceptance.ExtensionRegistry;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.TestMetricMaker;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.RawInputUtil;
@@ -89,6 +90,7 @@
@Inject protected RequestScopeOperations requestScopeOperations;
@Inject protected ProjectOperations projectOperations;
@Inject protected ExtensionRegistry extensionRegistry;
+ @Inject protected TestMetricMaker testMetricMaker;
@FunctionalInterface
protected interface RebaseCall {
@@ -771,6 +773,28 @@
assertThat(gApi.changes().id(id3.get()).revision(ri3._number).related().changes).isEmpty();
}
+
+ @Test
+ public void testCountRebasesMetric() throws Exception {
+ // Create two changes both with the same parent
+ PushOneCommit.Result r = createChange();
+ testRepo.reset("HEAD~1");
+ PushOneCommit.Result r2 = createChange();
+
+ // Approve and submit the first change
+ RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+ revision.review(ReviewInput.approve());
+ revision.submit();
+
+ // Rebase the second change
+ testMetricMaker.reset();
+ rebaseCallWithInput.call(r2.getChangeId(), new RebaseInput());
+ // field1 is on_behalf_of_uploader, field2 is rebase_chain
+ assertThat(testMetricMaker.getCount("change/count_rebases", false, false)).isEqualTo(1);
+ assertThat(testMetricMaker.getCount("change/count_rebases", true, false)).isEqualTo(0);
+ assertThat(testMetricMaker.getCount("change/count_rebases", true, true)).isEqualTo(0);
+ assertThat(testMetricMaker.getCount("change/count_rebases", false, true)).isEqualTo(0);
+ }
}
public static class RebaseViaRevisionApi extends Rebase {
@@ -1125,6 +1149,36 @@
assertThat(thrown).hasMessageThat().contains("recursion not allowed");
}
+ @Test
+ public void testCountRebasesMetric() throws Exception {
+ // Create changes with the following hierarchy:
+ // * HEAD
+ // * r1
+ // * r2
+ // * r3
+ // * r4
+ PushOneCommit.Result r = createChange();
+ testRepo.reset("HEAD~1");
+ PushOneCommit.Result r2 = createChange();
+ PushOneCommit.Result r3 = createChange();
+ PushOneCommit.Result r4 = createChange();
+
+ // Approve and submit the first change
+ RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+ revision.review(ReviewInput.approve());
+ revision.submit();
+
+ // Rebase the chain.
+ testMetricMaker.reset();
+ verifyRebaseChainResponse(
+ gApi.changes().id(r4.getChangeId()).rebaseChain(), false, r2, r3, r4);
+ // field1 is on_behalf_of_uploader, field2 is rebase_chain
+ assertThat(testMetricMaker.getCount("change/count_rebases", false, true)).isEqualTo(1);
+ assertThat(testMetricMaker.getCount("change/count_rebases", false, false)).isEqualTo(0);
+ assertThat(testMetricMaker.getCount("change/count_rebases", true, true)).isEqualTo(0);
+ assertThat(testMetricMaker.getCount("change/count_rebases", true, false)).isEqualTo(0);
+ }
+
private void verifyRebaseChainResponse(
Response<RebaseChainInfo> res,
boolean shouldHaveConflicts,
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java
index 7030804..5a5402b 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java
@@ -27,6 +27,7 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.ExtensionRegistry;
import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.TestMetricMaker;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
@@ -70,6 +71,7 @@
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private ExtensionRegistry extensionRegistry;
+ @Inject private TestMetricMaker testMetricMaker;
@Test
public void cannotRebaseOnBehalfOfUploaderWithAllowConflicts() throws Exception {
@@ -1021,6 +1023,95 @@
assertThat(gApi.changes().id(changeToBeRebased.get()).get().submittable).isFalse();
}
+ @Test
+ public void testSubmittedWithRebaserApprovalMetric() throws Exception {
+ // Require a Code-Review approval from a non-uploader for submit.
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ u.getConfig()
+ .upsertSubmitRequirement(
+ SubmitRequirement.builder()
+ .setName(TestLabels.codeReview().getName())
+ .setSubmittabilityExpression(
+ SubmitRequirementExpression.create(
+ String.format(
+ "label:%s=MAX,user=non_uploader", TestLabels.codeReview().getName())))
+ .setAllowOverrideInChildProjects(false)
+ .build());
+ u.save();
+ }
+
+ allowPermissionToAllUsers(Permission.REBASE);
+
+ String uploaderEmail = "uploader@example.com";
+ Account.Id uploader = accountOperations.newAccount().preferredEmail(uploaderEmail).create();
+ Account.Id approver = admin.id();
+ Account.Id rebaser = accountOperations.newAccount().create();
+
+ // Create two changes both with the same parent
+ requestScopeOperations.setApiUser(uploader);
+ Change.Id changeToBeTheNewBase =
+ changeOperations.newChange().project(project).owner(uploader).create();
+ Change.Id changeToBeRebased =
+ changeOperations.newChange().project(project).owner(uploader).create();
+
+ // Approve and submit the change that will be the new base for the change that will be rebased.
+ requestScopeOperations.setApiUser(approver);
+ gApi.changes().id(changeToBeTheNewBase.get()).current().review(ReviewInput.approve());
+ testMetricMaker.reset();
+ gApi.changes().id(changeToBeTheNewBase.get()).current().submit();
+ assertThat(testMetricMaker.getCount("change/submitted_with_rebaser_approval")).isEqualTo(0);
+
+ // Rebase it on behalf of the uploader
+ requestScopeOperations.setApiUser(rebaser);
+ RebaseInput rebaseInput = new RebaseInput();
+ rebaseInput.onBehalfOfUploader = true;
+ gApi.changes().id(changeToBeRebased.get()).rebase(rebaseInput);
+
+ // Approve the change as the rebaser.
+ allowVotingOnCodeReviewToAllUsers();
+ gApi.changes().id(changeToBeRebased.get()).current().review(ReviewInput.approve());
+
+ // The change is submittable because the approval is from a user (the rebaser) that is not the
+ // uploader.
+ allowPermissionToAllUsers(Permission.SUBMIT);
+ testMetricMaker.reset();
+ gApi.changes().id(changeToBeRebased.get()).current().submit();
+ assertThat(testMetricMaker.getCount("change/submitted_with_rebaser_approval")).isEqualTo(1);
+ }
+
+ @Test
+ public void testCountRebasesMetric() throws Exception {
+ allowPermissionToAllUsers(Permission.REBASE);
+
+ Account.Id uploader = accountOperations.newAccount().create();
+ Account.Id approver = admin.id();
+ Account.Id rebaser = accountOperations.newAccount().create();
+
+ // Create two changes both with the same parent
+ requestScopeOperations.setApiUser(uploader);
+ Change.Id changeToBeTheNewBase =
+ changeOperations.newChange().project(project).owner(uploader).create();
+ Change.Id changeToBeRebased =
+ changeOperations.newChange().project(project).owner(uploader).create();
+
+ // Approve and submit the change that will be the new base for the change that will be rebased.
+ requestScopeOperations.setApiUser(approver);
+ gApi.changes().id(changeToBeTheNewBase.get()).current().review(ReviewInput.approve());
+ gApi.changes().id(changeToBeTheNewBase.get()).current().submit();
+
+ // Rebase it on behalf of the uploader
+ testMetricMaker.reset();
+ requestScopeOperations.setApiUser(rebaser);
+ RebaseInput rebaseInput = new RebaseInput();
+ rebaseInput.onBehalfOfUploader = true;
+ gApi.changes().id(changeToBeRebased.get()).rebase(rebaseInput);
+ // field1 is on_behalf_of_uploader, field2 is rebase_chain
+ assertThat(testMetricMaker.getCount("change/count_rebases", true, false)).isEqualTo(1);
+ assertThat(testMetricMaker.getCount("change/count_rebases", false, false)).isEqualTo(0);
+ assertThat(testMetricMaker.getCount("change/count_rebases", true, true)).isEqualTo(0);
+ assertThat(testMetricMaker.getCount("change/count_rebases", false, true)).isEqualTo(0);
+ }
+
private void allowPermissionToAllUsers(String permission) {
allowPermission(permission, REGISTERED_USERS);
}
diff --git a/modules/jgit b/modules/jgit
index 66b871b..596c445 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit 66b871b777c1a58337e80dd03db68bb76b145a93
+Subproject commit 596c445af22ed9b6e0b7e35de44c127fcb8ecf7d
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index e3b3ad4..19e9d99 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -336,6 +336,12 @@
lineNum: LineNumber;
}
+export declare interface LineSelectedEventDetail {
+ number: LineNumber;
+ side: Side;
+ path?: string;
+}
+
// TODO: Currently unused and not fired.
export declare interface RenderProgressEventDetail {
linesRendered: number;
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts
index 42ec988..bb59c0c 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts
@@ -7,6 +7,7 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {css, html, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';
+import {fireEventNoBubble} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
@@ -68,22 +69,12 @@
_handleConfirmTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('confirm', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'confirm');
}
_handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('cancel', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'cancel');
}
}
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
index 3254b5c..13a8cd0 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
@@ -23,7 +23,7 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, css, html} from 'lit';
import {customElement, property, query, state} from 'lit/decorators.js';
-import {BindValueChangeEvent} from '../../../types/events';
+import {BindValueChangeEvent, ValueChangedEvent} from '../../../types/events';
import {fireEvent} from '../../../utils/event-util';
import {subscribe} from '../../lit/subscription-controller';
import {configModelToken} from '../../../models/config/config-model';
@@ -125,7 +125,7 @@
.text=${this.branch}
.query=${this.query}
placeholder="Destination branch"
- @text-changed=${(e: CustomEvent) => {
+ @text-changed=${(e: ValueChangedEvent<BranchName>) => {
this.branch = e.detail.value;
}}
>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
index ed57830..3548130 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
@@ -25,6 +25,7 @@
import {createRepoUrl} from '../../../models/views/repo';
import {resolve} from '../../../models/dependency';
import {navigationToken} from '../../core/gr-navigation/gr-navigation';
+import {ValueChangedEvent} from '../../../types/events';
declare global {
interface HTMLElementTagNameMap {
@@ -232,20 +233,20 @@
return groups;
}
- private handleRightsTextChanged(e: CustomEvent) {
+ private handleRightsTextChanged(e: ValueChangedEvent) {
this.repoConfig.parent = e.detail.value as RepoName;
this.requestUpdate();
}
- private handleOwnerTextChanged(e: CustomEvent) {
+ private handleOwnerTextChanged(e: ValueChangedEvent) {
this.repoOwner = e.detail.value;
}
- private handleOwnerValueChanged(e: CustomEvent) {
+ private handleOwnerValueChanged(e: ValueChangedEvent) {
this.repoOwnerId = e.detail.value as GroupId;
}
- private handleNameBindValueChanged(e: CustomEvent) {
+ private handleNameBindValueChanged(e: ValueChangedEvent) {
this.repoConfig.name = e.detail.value as RepoName;
// nameChanged needs to be set before the event is fired,
// because when the event is fired, gr-repo-list gets
@@ -255,16 +256,18 @@
this.requestUpdate();
}
- private handleBranchNameBindValueChanged(e: CustomEvent) {
+ private handleBranchNameBindValueChanged(e: ValueChangedEvent) {
this.defaultBranch = e.detail.value as BranchName;
}
- private handleCreateEmptyCommitBindValueChanged(e: CustomEvent) {
+ private handleCreateEmptyCommitBindValueChanged(
+ e: ValueChangedEvent<boolean>
+ ) {
this.repoConfig.create_empty_commit = e.detail.value;
this.requestUpdate();
}
- private handlePermissionsOnlyBindValueChanged(e: CustomEvent) {
+ private handlePermissionsOnlyBindValueChanged(e: ValueChangedEvent<boolean>) {
this.repoConfig.permissions_only = e.detail.value;
this.requestUpdate();
}
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
index af9977b..2fcec51 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
@@ -43,6 +43,7 @@
import {resolve} from '../../../models/dependency';
import {modalStyles} from '../../../styles/gr-modal-styles';
import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {ValueChangedEvent} from '../../../types/events';
const SAVING_ERROR_TEXT =
'Group may not exist, or you may not have ' + 'permission to add it';
@@ -546,22 +547,22 @@
});
}
- private handleGroupMemberTextChanged(e: CustomEvent) {
+ private handleGroupMemberTextChanged(e: ValueChangedEvent) {
if (this.loading) return;
this.groupMemberSearchName = e.detail.value;
}
- private handleGroupMemberValueChanged(e: CustomEvent) {
+ private handleGroupMemberValueChanged(e: ValueChangedEvent<number>) {
if (this.loading) return;
this.groupMemberSearchId = e.detail.value;
}
- private handleIncludedGroupTextChanged(e: CustomEvent) {
+ private handleIncludedGroupTextChanged(e: ValueChangedEvent) {
if (this.loading) return;
this.includedGroupSearchName = e.detail.value;
}
- private handleIncludedGroupValueChanged(e: CustomEvent) {
+ private handleIncludedGroupValueChanged(e: ValueChangedEvent) {
if (this.loading) return;
this.includedGroupSearchId = e.detail.value;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
index ba2b3fd..1ec83efa8 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
@@ -13,11 +13,11 @@
AutocompleteQuery,
} from '../../shared/gr-autocomplete/gr-autocomplete';
import {GroupId, GroupInfo, GroupName} from '../../../types/common';
-import {firePageError, fireTitleChange} from '../../../utils/event-util';
+import {fire, firePageError, fireTitleChange} from '../../../utils/event-util';
import {getAppContext} from '../../../services/app-context';
import {ErrorCallback} from '../../../api/rest';
import {convertToString} from '../../../utils/string-util';
-import {BindValueChangeEvent} from '../../../types/events';
+import {BindValueChangeEvent, ValueChangedEvent} from '../../../types/events';
import {fontStyles} from '../../../styles/gr-font-styles';
import {formStyles} from '../../../styles/gr-form-styles';
import {sharedStyles} from '../../../styles/shared-styles';
@@ -48,16 +48,13 @@
interface HTMLElementTagNameMap {
'gr-group': GrGroup;
}
+ interface HTMLElementEventMap {
+ 'name-changed': CustomEvent<GroupNameChangedDetail>;
+ }
}
@customElement('gr-group')
export class GrGroup extends LitElement {
- /**
- * Fired when the group name changes.
- *
- * @event name-changed
- */
-
private readonly query: AutocompleteQuery;
@property({type: String})
@@ -373,13 +370,7 @@
name: groupName,
external: !this.groupIsInternal,
};
- this.dispatchEvent(
- new CustomEvent('name-changed', {
- detail,
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'name-changed', detail);
this.requestUpdate();
}
@@ -455,25 +446,25 @@
return id.match(INTERNAL_GROUP_REGEX) ? id : decodeURIComponent(id);
}
- private handleNameTextChanged(e: CustomEvent) {
+ private handleNameTextChanged(e: ValueChangedEvent) {
if (!this.groupConfig || this.loading) return;
this.groupConfig.name = e.detail.value as GroupName;
this.requestUpdate();
}
- private handleOwnerTextChanged(e: CustomEvent) {
+ private handleOwnerTextChanged(e: ValueChangedEvent) {
if (!this.groupConfig || this.loading) return;
this.groupConfig.owner = e.detail.value;
this.requestUpdate();
}
- private handleOwnerValueChanged(e: CustomEvent) {
+ private handleOwnerValueChanged(e: ValueChangedEvent) {
if (this.loading) return;
this.groupConfigOwner = e.detail.value;
this.requestUpdate();
}
- private handleDescriptionTextChanged(e: CustomEvent) {
+ private handleDescriptionTextChanged(e: ValueChangedEvent) {
if (!this.groupConfig || this.loading) return;
this.groupConfig.description = e.detail.value;
this.requestUpdate();
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 07b7c87..46e8ae0 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
@@ -565,6 +565,11 @@
e.preventDefault();
}
+ // TODO: Do not use generic `CustomEvent`.
+ // There is something fishy going on here though.
+ // `e.detail.value` is of type `Rule`, but `splice()` expects a `number`.
+ // Did not look closer, but this seems to be broken. Should `e.detail.value`
+ // be replaced by `1` maybe??
private handleRuleChanged(e: CustomEvent, index: number) {
this.rules!.splice(index, e.detail.value);
this.handleRulesChanged();
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts
index 54a83ee..5afaa9d 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts
@@ -15,21 +15,19 @@
import {LitElement, html, css} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {BindValueChangeEvent} from '../../../types/events';
+import {fireNoBubbleNoCompose} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
'gr-plugin-config-array-editor': GrPluginConfigArrayEditor;
}
+ interface HTMLElementEventMap {
+ 'plugin-config-option-changed': CustomEvent<PluginConfigOptionsChangedEventDetail>;
+ }
}
@customElement('gr-plugin-config-array-editor')
export class GrPluginConfigArrayEditor extends LitElement {
- /**
- * Fired when the plugin config option changes.
- *
- * @event plugin-config-option-changed
- */
-
// private but used in test
@state() newValue = '';
@@ -175,9 +173,7 @@
info: {...info, values},
notifyPath: `${_key}.values`,
};
- this.dispatchEvent(
- new CustomEvent('plugin-config-option-changed', {detail})
- );
+ fireNoBubbleNoCompose(this, 'plugin-config-option-changed', detail);
}
private handleBindValueChangedNewValue(e: BindValueChangeEvent) {
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 f8dcd32..2772519 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
@@ -25,8 +25,7 @@
PluginOption,
} from './gr-repo-plugin-config-types';
import {paperStyles} from '../../../styles/gr-paper-styles';
-
-const PLUGIN_CONFIG_CHANGED_EVENT_NAME = 'plugin-config-changed';
+import {fire} from '../../../utils/event-util';
export interface ConfigChangeInfo {
_key: string; // parameterName of PluginParameterToConfigParameterInfoMap
@@ -255,14 +254,7 @@
name,
config: {...config, [_key]: info},
};
-
- this.dispatchEvent(
- new CustomEvent(PLUGIN_CONFIG_CHANGED_EVENT_NAME, {
- detail,
- bubbles: true,
- composed: true,
- })
- );
+ fire(this, 'plugin-config-changed', detail);
}
/**
@@ -277,4 +269,7 @@
interface HTMLElementTagNameMap {
'gr-repo-plugin-config': GrRepoPluginConfig;
}
+ interface HTMLElementEventMap {
+ 'plugin-config-changed': CustomEvent<PluginConfigChangeDetail>;
+ }
}
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
index 5f0a171..82a4eb5 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
@@ -8,12 +8,12 @@
import '../../shared/gr-select/gr-select';
import {encodeURL, getBaseUrl} from '../../../utils/url-util';
import {AccessPermissionId} from '../../../utils/access-util';
-import {fireEvent} from '../../../utils/event-util';
+import {fire, fireEvent} from '../../../utils/event-util';
import {formStyles} from '../../../styles/gr-form-styles';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, html, css} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
-import {BindValueChangeEvent} from '../../../types/events';
+import {BindValueChangeEvent, ValueChangedEvent} from '../../../types/events';
import {ifDefined} from 'lit/directives/if-defined.js';
import {EditablePermissionRuleInfo} from '../gr-repo-access/gr-repo-access-interfaces';
import {PermissionAction} from '../../../constants/constants';
@@ -81,6 +81,9 @@
interface HTMLElementTagNameMap {
'gr-rule-editor': GrRuleEditor;
}
+ interface HTMLElementEventMap {
+ 'rule-changed': ValueChangedEvent<Rule | undefined>;
+ }
}
@customElement('gr-rule-editor')
@@ -537,13 +540,6 @@
private handleRuleChange() {
this.requestUpdate('rule');
-
- this.dispatchEvent(
- new CustomEvent('rule-changed', {
- detail: {value: this.rule},
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'rule-changed', {value: this.rule});
}
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow.ts
index f8fcad8..ea61bfc 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow.ts
@@ -159,7 +159,7 @@
.horizontalAlign=${'auto'}
.verticalAlign=${'auto'}
.verticalOffset=${24}
- @opened-changed=${(e: CustomEvent) =>
+ @opened-changed=${(e: ValueChangedEvent<boolean>) =>
(this.isDropdownOpen = e.detail.value)}
>
${when(
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow.ts
index 7752476..4a01412 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow.ts
@@ -159,7 +159,7 @@
.horizontalAlign=${'auto'}
.verticalAlign=${'auto'}
.verticalOffset=${24}
- @opened-changed=${(e: CustomEvent) =>
+ @opened-changed=${(e: ValueChangedEvent<boolean>) =>
(this.isDropdownOpen = e.detail.value)}
>
${when(
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts
index 561fffd..90fff4d 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts
@@ -12,6 +12,7 @@
import {assertIsDefined} from '../../../utils/common-util';
import {BindValueChangeEvent} from '../../../types/events';
import {modalStyles} from '../../../styles/gr-modal-styles';
+import {fireNoBubbleNoCompose} from '../../../utils/event-util';
export interface CreateDestinationConfirmDetail {
repo?: RepoName;
@@ -88,7 +89,7 @@
// 'confirm' event here, so let's stop propagation of the bare event.
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(new CustomEvent('confirm', {detail, bubbles: false}));
+ fireNoBubbleNoCompose(this, 'confirm', detail);
};
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index e47b450..259c8be 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -76,7 +76,9 @@
import {
fire,
fireAlert,
+ fireError,
fireEvent,
+ fireEventNoBubbleNoCompose,
fireReload,
} from '../../../utils/event-util';
import {
@@ -84,7 +86,7 @@
getVotingRange,
StandardLabels,
} from '../../../utils/label-util';
-import {EventType, ShowAlertEventDetail} from '../../../types/events';
+import {EventType} from '../../../types/events';
import {
ActionPriority,
ActionType,
@@ -334,18 +336,6 @@
* @event custom-tap - naming pattern: <action key>-tap
*/
- /**
- * Fires to show an alert when a send is attempted on the non-latest patch.
- *
- * @event show-alert
- */
-
- /**
- * Fires when a change action fails.
- *
- * @event show-error
- */
-
@query('#mainContent') mainContent?: Element;
@query('#actionsModal') actionsModal?: HTMLDialogElement;
@@ -1912,13 +1902,7 @@
) {
if (!response) {
return Promise.resolve(() => {
- this.dispatchEvent(
- new CustomEvent('show-error', {
- detail: {message: `Could not perform action '${action.__key}'`},
- composed: true,
- bubbles: true,
- })
- );
+ fireError(this, `Could not perform action '${action.__key}'`);
});
}
if (action && action.__key === RevisionActions.CHERRYPICK) {
@@ -1936,13 +1920,7 @@
}
}
return response.text().then(errText => {
- this.dispatchEvent(
- new CustomEvent('show-error', {
- detail: {message: `Could not perform action: ${errText}`},
- composed: true,
- bubbles: true,
- })
- );
+ fireError(this, `Could not perform action: ${errText}`);
if (!errText.startsWith('Change is already up to date')) {
throw Error(errText);
}
@@ -1973,19 +1951,13 @@
.fetchChangeUpdates(change)
.then(result => {
if (!result.isLatest) {
- this.dispatchEvent(
- new CustomEvent<ShowAlertEventDetail>(EventType.SHOW_ALERT, {
- detail: {
- message:
- 'Cannot set label: a newer patch has been ' +
- 'uploaded to this change.',
- action: 'Reload',
- callback: () => fireReload(this, true),
- },
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, EventType.SHOW_ALERT, {
+ message:
+ 'Cannot set label: a newer patch has been ' +
+ 'uploaded to this change.',
+ action: 'Reload',
+ callback: () => fireReload(this, true),
+ });
// Because this is not a network error, call the cleanup function
// but not the error handler.
@@ -2241,11 +2213,11 @@
}
private handleEditTap() {
- this.dispatchEvent(new CustomEvent('edit-tap', {bubbles: false}));
+ fireEventNoBubbleNoCompose(this, 'edit-tap');
}
private handleStopEditTap() {
- this.dispatchEvent(new CustomEvent('stop-edit-tap', {bubbles: false}));
+ fireEventNoBubbleNoCompose(this, 'stop-edit-tap');
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index b9a04bd..01399e9 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -947,7 +947,7 @@
return createSearchUrl({hashtag, statuses: ['open', 'merged']});
}
- private async handleTopicRemoved(e: CustomEvent) {
+ private async handleTopicRemoved(e: Event) {
assertIsDefined(this.change, 'change');
const target = e.composedPath()[0] as GrLinkedChip;
target.disabled = true;
@@ -962,7 +962,7 @@
}
// private but used in test
- async handleHashtagRemoved(e: CustomEvent) {
+ async handleHashtagRemoved(e: Event) {
e.preventDefault();
assertIsDefined(this.change, 'change');
const target = e.target as GrLinkedChip;
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 2798bb9..3afe055 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
@@ -117,8 +117,9 @@
import {
EditableContentSaveEvent,
EventType,
+ FileActionTapEvent,
OpenFixPreviewEvent,
- ShowAlertEventDetail,
+ ShowReplyDialogEvent,
SwitchTabEvent,
TabState,
ValueChangedEvent,
@@ -127,6 +128,7 @@
import {GrMessagesList} from '../gr-messages-list/gr-messages-list';
import {GrThreadList} from '../gr-thread-list/gr-thread-list';
import {
+ fire,
fireAlert,
fireDialogChange,
fireEvent,
@@ -1544,7 +1546,6 @@
id="fileList"
.change=${this.change}
.changeNum=${this.changeNum}
- .patchRange=${this.patchRange}
.editMode=${this.getEditMode()}
@files-shown-changed=${(e: CustomEvent<{length: number}>) => {
this.shownFileCount = e.detail.length;
@@ -2009,7 +2010,7 @@
}
// Private but used in tests.
- handleShowReplyDialog(e: CustomEvent<{value: {ccsOnly: boolean}}>) {
+ handleShowReplyDialog(e: ShowReplyDialogEvent) {
let target = FocusTarget.REVIEWERS;
if (e.detail.value && e.detail.value.ccsOnly) {
target = FocusTarget.CCS;
@@ -3055,20 +3056,14 @@
}
this.cancelUpdateCheckTimer();
- this.dispatchEvent(
- new CustomEvent<ShowAlertEventDetail>(EventType.SHOW_ALERT, {
- detail: {
- message: toastMessage,
- // Persist this alert.
- dismissOnNavigation: true,
- showDismiss: true,
- action: 'Reload',
- callback: () => fireReload(this, true),
- },
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, EventType.SHOW_ALERT, {
+ message: toastMessage,
+ // Persist this alert.
+ dismissOnNavigation: true,
+ showDismiss: true,
+ action: 'Reload',
+ callback: () => fireReload(this, true),
+ });
});
}, this.serverConfig.change.update_delay * 1000);
}
@@ -3097,7 +3092,7 @@
return classes.join(' ');
}
- private handleFileActionTap(e: CustomEvent<{path: string; action: string}>) {
+ private handleFileActionTap(e: FileActionTapEvent) {
e.preventDefault();
assertIsDefined(this.fileListHeader);
const controls =
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 ac4c1f9..34d11a3 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
@@ -1772,7 +1772,7 @@
const openStub = sinon.stub(element, 'openReplyDialog');
const e = new CustomEvent('show-reply-dialog', {
- detail: {value: {ccsOnly: false}},
+ detail: {value: {reviewersOnly: true, ccsOnly: false}},
});
element.handleShowReplyDialog(e);
assert(
@@ -1781,7 +1781,7 @@
);
assert.equal(openStub.callCount, 1);
- e.detail.value = {ccsOnly: true};
+ e.detail.value = {reviewersOnly: false, ccsOnly: true};
element.handleShowReplyDialog(e);
assert(
openStub.lastCall.calledWithExactly(FocusTarget.CCS),
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
index 85746df..cbe3430 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
@@ -14,6 +14,7 @@
import {BindValueChangeEvent} from '../../../types/events';
import {ShortcutController} from '../../lit/shortcut-controller';
import {ChangeActionDialog} from '../../../types/common';
+import {fireEventNoBubble, fireNoBubble} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
@@ -132,25 +133,14 @@
// private but used in test
confirm() {
- this.dispatchEvent(
- new CustomEvent('confirm', {
- detail: {reason: this.message},
- composed: true,
- bubbles: false,
- })
- );
+ fireNoBubble(this, 'confirm', {reason: this.message});
}
// private but used in test
handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('cancel', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'cancel');
}
private handleBindValueChanged(e: BindValueChangeEvent) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts
index 02156df..34a3161 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts
@@ -7,6 +7,7 @@
import {customElement} from 'lit/decorators.js';
import {sharedStyles} from '../../../styles/shared-styles';
import {ChangeActionDialog} from '../../../types/common';
+import {fireEventNoBubble} from '../../../utils/event-util';
import '../../shared/gr-dialog/gr-dialog';
@customElement('gr-confirm-cherrypick-conflict-dialog')
@@ -66,23 +67,13 @@
handleConfirmTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('confirm', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'confirm');
}
handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('cancel', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'cancel');
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index 5f3b824..8ce0f51 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -30,7 +30,7 @@
ChangeStatus,
ProgressStatus,
} from '../../../constants/constants';
-import {fireEvent} from '../../../utils/event-util';
+import {fireEvent, fireEventNoBubble} from '../../../utils/event-util';
import {css, html, LitElement, PropertyValues} from 'lit';
import {sharedStyles} from '../../../styles/shared-styles';
import {choose} from 'lit/directives/choose.js';
@@ -605,23 +605,13 @@
return;
}
// Cherry pick single change
- this.dispatchEvent(
- new CustomEvent('confirm', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'confirm');
}
private handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('cancel', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'cancel');
}
resetFocus() {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
index 3f84189..db14694 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
@@ -15,6 +15,7 @@
import {ValueChangedEvent} from '../../../types/events';
import {ShortcutController} from '../../lit/shortcut-controller';
import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {fireEventNoBubble} from '../../../utils/event-util';
const SUGGESTIONS_LIMIT = 15;
@@ -142,23 +143,13 @@
private handleConfirmTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('confirm', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'confirm');
}
private handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('cancel', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'cancel');
}
private getProjectBranchesSuggestions(input: string) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
index ad2ba8f..3150fc8 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
@@ -22,6 +22,10 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {ValueChangedEvent} from '../../../types/events';
import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {
+ fireEventNoBubbleNoCompose,
+ fireNoBubbleNoCompose,
+} from '../../../utils/event-util';
export interface RebaseChange {
name: string;
@@ -351,14 +355,14 @@
allowConflicts: this.rebaseAllowConflicts.checked,
rebaseChain: !!this.rebaseChain?.checked,
};
- this.dispatchEvent(new CustomEvent('confirm', {detail}));
+ fireNoBubbleNoCompose(this, 'confirm', detail);
this.text = '';
}
private handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(new CustomEvent('cancel'));
+ fireEventNoBubbleNoCompose(this, 'cancel');
this.text = '';
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
index 1b5f171..bf1c5ca 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
@@ -21,6 +21,7 @@
import {commentsModelToken} from '../../../models/comments/comments-model';
import {changeModelToken} from '../../../models/change/change-model';
import {resolve} from '../../../models/dependency';
+import {fireEventNoBubbleNoCompose} from '../../../utils/event-util';
@customElement('gr-confirm-submit-dialog')
export class GrConfirmSubmitDialog
@@ -193,13 +194,13 @@
private handleConfirmTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(new CustomEvent('confirm', {bubbles: false}));
+ fireEventNoBubbleNoCompose(this, 'confirm');
}
private handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(new CustomEvent('cancel', {bubbles: false}));
+ fireEventNoBubbleNoCompose(this, 'cancel');
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index f73dab9..4403a8f 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -25,7 +25,7 @@
import {DiffPreferencesInfo} from '../../../types/diff';
import {GrDiffModeSelector} from '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
import {GrButton} from '../../shared/gr-button/gr-button';
-import {fireEvent} from '../../../utils/event-util';
+import {fireEvent, fireEventNoBubbleNoCompose} from '../../../utils/event-util';
import {css, html, LitElement} from 'lit';
import {sharedStyles} from '../../../styles/shared-styles';
import {when} from 'lit/directives/when.js';
@@ -41,6 +41,7 @@
import {createChangeUrl} from '../../../models/views/change';
import {userModelToken} from '../../../models/user/user-model';
import {changeModelToken} from '../../../models/change/change-model';
+import {PatchRangeChangeEvent} from '../../diff/gr-patch-range-select/gr-patch-range-select';
@customElement('gr-file-list-header')
export class GrFileListHeader extends LitElement {
@@ -403,7 +404,7 @@
return shownFileCount <= maxFilesForBulkActions;
}
- handlePatchChange(e: CustomEvent) {
+ handlePatchChange(e: PatchRangeChangeEvent) {
const {basePatchNum, patchNum} = e.detail;
if (
(basePatchNum === this.basePatchNum && patchNum === this.patchNum) ||
@@ -424,9 +425,7 @@
private handleDownloadTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('open-download-dialog', {bubbles: false})
- );
+ fireEventNoBubbleNoCompose(this, 'open-download-dialog');
}
private computeEditModeClass(editMode?: boolean) {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 6eaf7ae..ad75253 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -42,6 +42,7 @@
NumericChangeId,
PARENT,
PatchRange,
+ RevisionPatchSetNum,
} from '../../../types/common';
import {DiffPreferencesInfo} from '../../../types/diff';
import {GrDiffHost} from '../../diff/gr-diff-host/gr-diff-host';
@@ -173,19 +174,24 @@
}
@customElement('gr-file-list')
export class GrFileList extends LitElement {
- /**
- * @event files-expanded-changed
- * @event files-shown-changed
- * @event diff-prefs-changed
- */
@query('#diffPreferencesDialog')
diffPreferencesDialog?: GrDiffPreferencesDialog;
- @property({type: Object})
- patchRange?: PatchRange;
+ get patchRange(): PatchRange | undefined {
+ if (!this.patchNum) return undefined;
+ return {
+ patchNum: this.patchNum,
+ basePatchNum: this.basePatchNum,
+ };
+ }
- @property({type: String})
- patchNum?: string;
+ // Private but used in tests.
+ @state()
+ patchNum?: RevisionPatchSetNum;
+
+ // Private but used in tests.
+ @state()
+ basePatchNum: BasePatchSetNum = PARENT;
@property({type: Number})
changeNum?: NumericChangeId;
@@ -811,6 +817,16 @@
this.reviewed = reviewedFiles ?? [];
}
);
+ subscribe(
+ this,
+ () => this.getChangeModel().patchNum$,
+ x => (this.patchNum = x)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().basePatchNum$,
+ x => (this.basePatchNum = x)
+ );
}
override willUpdate(changedProperties: PropertyValues): void {
@@ -1136,7 +1152,7 @@
const hasExtendedStatus = this.filesLeftBase.length > 0;
// no file means "header row"
if (!file) {
- const psNum = this.patchRange?.patchNum;
+ const psNum = this.patchNum;
return hasExtendedStatus
? this.renderDivWithTooltip(`${psNum}`, `Patchset ${psNum}`)
: nothing;
@@ -1154,8 +1170,8 @@
const status = fileIsReverted
? FileInfoStatus.REVERTED
: file?.status ?? FileInfoStatus.MODIFIED;
- const left = `patchset ${this.patchRange?.basePatchNum}`;
- const right = `patchset ${this.patchRange?.patchNum}`;
+ const left = `patchset ${this.basePatchNum}`;
+ const right = `patchset ${this.patchNum}`;
const postfix = ` between ${left} and ${right}`;
return html`<gr-file-status
@@ -1175,7 +1191,7 @@
></gr-icon>
`;
// no path means "header row"
- const psNum = this.patchRange?.basePatchNum;
+ const psNum = this.basePatchNum;
if (!path) {
return html`
${this.renderDivWithTooltip(`${psNum}`, `Patchset ${psNum}`)} ${arrow}
@@ -1187,7 +1203,7 @@
const status = file.status ?? FileInfoStatus.MODIFIED;
const left = 'base';
- const right = `patchset ${this.patchRange?.basePatchNum}`;
+ const right = `patchset ${this.basePatchNum}`;
const postfix = ` between ${left} and ${right}`;
return html`
@@ -1664,16 +1680,16 @@
if (
this.change &&
this.changeNum &&
- this.patchRange?.patchNum &&
- new RevisionInfo(this.change).isMergeCommit(this.patchRange.patchNum) &&
- this.patchRange.basePatchNum === PARENT &&
- this.patchRange.patchNum !== EDIT
+ this.patchNum &&
+ new RevisionInfo(this.change).isMergeCommit(this.patchNum) &&
+ this.basePatchNum === PARENT &&
+ this.patchNum !== EDIT
) {
const allFilesByPath = await this.restApiService.getChangeOrEditFiles(
this.changeNum,
{
basePatchNum: -1 as BasePatchSetNum, // -1 is first (target) parent
- patchNum: this.patchRange.patchNum,
+ patchNum: this.patchNum,
}
);
if (!allFilesByPath) return;
@@ -2252,13 +2268,7 @@
const previousNumFilesShown = this.shownFiles ? this.shownFiles.length : 0;
const filesShown = this.files.slice(0, this.numFilesShown);
- this.dispatchEvent(
- new CustomEvent('files-shown-changed', {
- detail: {length: filesShown.length},
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'files-shown-changed', {length: filesShown.length});
// Start the timer for the rendering work here because this is where the
// shownFiles property is being set, and shownFiles is used in the
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
index c79be96..8fb5622 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
@@ -102,10 +102,8 @@
ignore_whitespace: 'IGNORE_NONE',
};
element.numFilesShown = 200;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
saveStub = sinon
.stub(element, '_saveReviewedState')
.callsFake(() => Promise.resolve());
@@ -365,7 +363,7 @@
test('renders file status column header', async () => {
element.files = createFiles(1, {lines_inserted: 9});
element.filesLeftBase = createFiles(1, {lines_inserted: 9});
- element.patchRange!.basePatchNum = 1 as PatchSetNumber;
+ element.basePatchNum = 1 as PatchSetNumber;
await element.updateComplete;
const fileRows = queryAll<HTMLDivElement>(element, '.header-row');
const statusCol = queryAndAssert(fileRows?.[0], '.status');
@@ -695,22 +693,9 @@
test('comment filtering', () => {
element.changeComments = createChangeComments();
- const parentTo1 = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
- const parentTo2 = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
-
- const _1To2 = {
- basePatchNum: 1 as BasePatchSetNum,
- patchNum: 2 as RevisionPatchSetNum,
- };
-
- element.patchRange = parentTo1;
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: '/COMMIT_MSG',
@@ -719,7 +704,9 @@
}),
'2c'
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: '/COMMIT_MSG',
@@ -728,7 +715,9 @@
}),
'3c'
);
- element.patchRange = parentTo1;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsString({
__path: 'unresolved.file',
@@ -737,7 +726,9 @@
}),
'1 draft'
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsString({
__path: 'unresolved.file',
@@ -746,7 +737,9 @@
}),
'1 draft'
);
- element.patchRange = parentTo1;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: 'unresolved.file',
@@ -755,7 +748,9 @@
}),
'1d'
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: 'unresolved.file',
@@ -764,7 +759,9 @@
}),
'1d'
);
- element.patchRange = parentTo1;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: 'myfile.txt',
@@ -773,7 +770,9 @@
}),
'1c'
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: 'myfile.txt',
@@ -782,16 +781,9 @@
}),
'3c'
);
- element.patchRange = parentTo1;
- assert.equal(
- element.computeDraftsString({
- __path: 'myfile.txt',
- size: 0,
- size_delta: 0,
- }),
- ''
- );
- element.patchRange = _1To2;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsString({
__path: 'myfile.txt',
@@ -801,7 +793,19 @@
''
);
- element.patchRange = parentTo1;
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
+ assert.equal(
+ element.computeDraftsString({
+ __path: 'myfile.txt',
+ size: 0,
+ size_delta: 0,
+ }),
+ ''
+ );
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: 'myfile.txt',
@@ -810,7 +814,9 @@
}),
''
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: 'myfile.txt',
@@ -819,7 +825,9 @@
}),
''
);
- element.patchRange = parentTo1;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: 'file_added_in_rev2.txt',
@@ -828,7 +836,9 @@
}),
''
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: 'file_added_in_rev2.txt',
@@ -837,7 +847,9 @@
}),
''
);
- element.patchRange = parentTo1;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsString({
__path: 'file_added_in_rev2.txt',
@@ -846,7 +858,9 @@
}),
''
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsString({
__path: 'file_added_in_rev2.txt',
@@ -855,7 +869,9 @@
}),
''
);
- element.patchRange = parentTo1;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: 'file_added_in_rev2.txt',
@@ -864,7 +880,9 @@
}),
''
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: 'file_added_in_rev2.txt',
@@ -873,7 +891,9 @@
}),
''
);
- element.patchRange = parentTo2;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: '/COMMIT_MSG',
@@ -882,7 +902,9 @@
}),
'1c'
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: '/COMMIT_MSG',
@@ -891,7 +913,9 @@
}),
'3c'
);
- element.patchRange = parentTo1;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsString({
__path: '/COMMIT_MSG',
@@ -900,7 +924,9 @@
}),
'2 drafts'
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsString({
__path: '/COMMIT_MSG',
@@ -909,7 +935,9 @@
}),
'2 drafts'
);
- element.patchRange = parentTo1;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: '/COMMIT_MSG',
@@ -918,7 +946,9 @@
}),
'2d'
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: '/COMMIT_MSG',
@@ -927,7 +957,9 @@
}),
'2d'
);
- element.patchRange = parentTo2;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: 'myfile.txt',
@@ -936,7 +968,9 @@
}),
'2c'
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeCommentsStringMobile({
__path: 'myfile.txt',
@@ -945,7 +979,9 @@
}),
'3c'
);
- element.patchRange = parentTo2;
+
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: 'myfile.txt',
@@ -954,7 +990,9 @@
}),
''
);
- element.patchRange = _1To2;
+
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
assert.equal(
element.computeDraftsStringMobile({
__path: 'myfile.txt',
@@ -973,10 +1011,8 @@
normalize({}, 'myfile.txt'),
];
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
element.change = {
_number: 42 as NumericChangeId,
project: 'test-project',
@@ -1197,10 +1233,8 @@
normalize({}, 'myfile.txt'),
];
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
element.fileCursor.setCursorAtIndex(0);
const reviewSpy = sinon.spy(element, 'reviewFile');
@@ -1263,10 +1297,8 @@
normalize({}, 'f2.txt'),
];
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
await element.updateComplete;
const clickSpy = sinon.spy(element, 'handleFileListClick');
@@ -1303,10 +1335,8 @@
normalize({}, 'f2.txt'),
];
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
element.editMode = true;
await element.updateComplete;
@@ -1324,10 +1354,8 @@
test('checkbox shows/hides diff inline', async () => {
element.files = [normalize({}, 'myfile.txt')];
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
element.fileCursor.setCursorAtIndex(0);
sinon.stub(element, 'expandedFilesChanged');
await element.updateComplete;
@@ -1353,10 +1381,8 @@
test('diff mode correctly toggles the diffs', async () => {
element.files = [normalize({}, 'myfile.txt')];
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
const updateDiffPrefSpy = sinon.spy(element, 'updateDiffPreferences');
element.fileCursor.setCursorAtIndex(0);
await element.updateComplete;
@@ -1383,10 +1409,8 @@
test('tapping row ignores links', async () => {
element.files = [normalize({}, '/COMMIT_MSG')];
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
sinon.stub(element, 'expandedFilesChanged');
await element.updateComplete;
const commitMsgFile = queryAll<HTMLAnchorElement>(
@@ -1680,10 +1704,8 @@
};
element.changeNum = changeWithMultipleParents._number;
element.change = changeWithMultipleParents;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
await element.updateComplete;
await waitEventLoop();
});
@@ -1744,10 +1766,8 @@
});
test('not shown for non-Auto Merge base parents', async () => {
- element.patchRange = {
- basePatchNum: 1 as BasePatchSetNum,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = 2 as RevisionPatchSetNum;
await element.updateCleanlyMergedPaths();
await element.updateComplete;
@@ -1756,10 +1776,8 @@
});
test('not shown in edit mode', async () => {
- element.patchRange = {
- basePatchNum: 1 as BasePatchSetNum,
- patchNum: EDIT,
- };
+ element.basePatchNum = 1 as BasePatchSetNum;
+ element.patchNum = EDIT;
await element.updateCleanlyMergedPaths();
await element.updateComplete;
@@ -1776,10 +1794,8 @@
_number: 1 as NumericChangeId,
project: 'gerrit' as RepoName,
};
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
const path = 'index.php';
element.editMode = false;
assert.equal(element.computeDiffURL(path), '/c/gerrit/+/1/1/index.php');
@@ -1791,10 +1807,8 @@
_number: 1 as NumericChangeId,
project: 'gerrit' as RepoName,
};
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
element.editMode = false;
const path = '/COMMIT_MSG';
assert.equal(element.computeDiffURL(path), '/c/gerrit/+/1/1//COMMIT_MSG');
@@ -1806,10 +1820,8 @@
_number: 1 as NumericChangeId,
project: 'gerrit' as RepoName,
};
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
element.editMode = true;
const path = 'index.php';
assert.equal(
@@ -1824,10 +1836,8 @@
_number: 1 as NumericChangeId,
project: 'gerrit' as RepoName,
};
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 1 as RevisionPatchSetNum;
element.editMode = true;
const path = '/COMMIT_MSG';
assert.equal(
@@ -2113,10 +2123,8 @@
];
element.reviewed = ['/COMMIT_MSG', 'myfile.txt'];
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 2 as RevisionPatchSetNum,
- };
+ element.basePatchNum = PARENT;
+ element.patchNum = 2 as RevisionPatchSetNum;
sinon
.stub(window, 'fetch')
.callsFake(() => Promise.resolve(new Response()));
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
index fcfe209..c6eaef7 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
@@ -12,6 +12,7 @@
import {LitElement, PropertyValues, html, css} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {BindValueChangeEvent} from '../../../types/events';
+import {fireEventNoBubble} from '../../../utils/event-util';
interface DisplayGroup {
title: string;
@@ -197,12 +198,7 @@
private handleCloseTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('close', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'close');
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts
index 50c5caf..c669209 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts
@@ -18,21 +18,20 @@
import {assertIsDefined, hasOwnProperty} from '../../../utils/common-util';
import {Label} from '../../../utils/label-util';
import {LabelNameToValuesMap} from '../../../api/rest-api';
+import {fire} from '../../../utils/event-util';
+import {LabelsChangedDetail} from '../../../api/change-reply';
declare global {
interface HTMLElementTagNameMap {
'gr-label-score-row': GrLabelScoreRow;
}
+ interface HTMLElementEventMap {
+ 'labels-changed': CustomEvent<LabelsChangedDetail>;
+ }
}
@customElement('gr-label-score-row')
export class GrLabelScoreRow extends LitElement {
- /**
- * Fired when any label is changed.
- *
- * @event labels-changed
- */
-
@query('#labelSelector')
labelSelector?: IronSelectorElement;
@@ -365,13 +364,7 @@
this.selectedValueText = selectedItem.getAttribute('title') || '';
const name = selectedItem.dataset['name'];
const value = selectedItem.dataset['value'];
- this.dispatchEvent(
- new CustomEvent('labels-changed', {
- detail: {name, value},
- bubbles: true,
- composed: true,
- })
- );
+ if (name && value) fire(this, 'labels-changed', {name, value});
};
private computePermittedLabelValues() {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index a4da747..992a1dc 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -48,6 +48,11 @@
import {FormattedReviewerUpdateInfo} from '../../../types/types';
import {resolve} from '../../../models/dependency';
import {createChangeUrl} from '../../../models/views/change';
+import {fire} from '../../../utils/event-util';
+import {
+ ChangeMessageDeletedEventDetail,
+ ReplyEvent,
+} from '../../../types/events';
const UPLOADED_NEW_PATCHSET_PATTERN = /Uploaded patch set (\d+)./;
const MERGED_PATCHSET_PATTERN = /(\d+) is the latest approved patch-set/;
@@ -55,6 +60,12 @@
interface HTMLElementTagNameMap {
'gr-message': GrMessage;
}
+ interface HTMLElementEventMap {
+ 'message-anchor-tap': CustomEvent<MessageAnchorTapDetail>;
+ 'change-message-deleted': CustomEvent<ChangeMessageDeletedEventDetail>;
+ /* prettier-ignore */
+ 'reply': ReplyEvent;
+ }
}
export interface MessageAnchorTapDetail {
@@ -70,12 +81,6 @@
*/
/**
- * Fired when the message's timestamp is tapped.
- *
- * @event message-anchor-tap
- */
-
- /**
* Fired when a change message is deleted.
*
* @event change-message-deleted
@@ -751,24 +756,13 @@
const detail: MessageAnchorTapDetail = {
id: this.message!.id,
};
- this.dispatchEvent(
- new CustomEvent('message-anchor-tap', {
- bubbles: true,
- composed: true,
- detail,
- })
- );
+ fire(this, 'message-anchor-tap', detail);
}
private handleReplyTap(e: Event) {
e.preventDefault();
- this.dispatchEvent(
- new CustomEvent('reply', {
- detail: {message: this.message},
- composed: true,
- bubbles: true,
- })
- );
+ // TODO: Fix the type casting. Might actually be a bug.
+ fire(this, 'reply', {message: this.message as ChangeMessage});
}
private handleDeleteMessage(e: Event) {
@@ -779,13 +773,10 @@
.deleteChangeCommitMessage(this.changeNum, this.message.id)
.then(() => {
this.isDeletingChangeMsg = false;
- this.dispatchEvent(
- new CustomEvent('change-message-deleted', {
- detail: {message: this.message},
- composed: true,
- bubbles: true,
- })
- );
+ // TODO: Fix the type casting. Might actually be a bug.
+ fire(this, 'change-message-deleted', {
+ message: this.message as ChangeMessage,
+ });
});
}
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index f3c23aa..9f2c6be 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -92,7 +92,10 @@
import {pluralize} from '../../../utils/string-util';
import {
fireAlert,
+ fireError,
fireEvent,
+ fireEventNoBubble,
+ fireEventNoBubbleNoCompose,
fireIronAnnounce,
fireReload,
fireServerError,
@@ -116,7 +119,11 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {when} from 'lit/directives/when.js';
import {classMap} from 'lit/directives/class-map.js';
-import {ValueChangedEvent} from '../../../types/events';
+import {
+ AddReviewerEvent,
+ RemoveReviewerEvent,
+ ValueChangedEvent,
+} from '../../../types/events';
import {customElement, property, state, query} from 'lit/decorators.js';
import {subscribe} from '../../lit/subscription-controller';
import {configModelToken} from '../../../models/config/config-model';
@@ -723,17 +730,19 @@
// Plugins on reply-reviewers endpoint can take advantage of these
// events to add / remove reviewers
- this.addEventListener('add-reviewer', e => {
+ this.addEventListener('add-reviewer', (e: AddReviewerEvent) => {
+ const reviewer = e.detail.reviewer;
// Only support account type, see more from:
// elements/shared/gr-account-list/gr-account-list.js#addAccountItem
this.reviewersList?.addAccountItem({
- account: (e as CustomEvent).detail.reviewer,
+ account: reviewer,
count: 1,
});
});
- this.addEventListener('remove-reviewer', e => {
- this.reviewersList?.removeAccount((e as CustomEvent).detail.reviewer);
+ this.addEventListener('remove-reviewer', (e: RemoveReviewerEvent) => {
+ const reviewer = e.detail.reviewer;
+ this.reviewersList?.removeAccount(reviewer);
});
}
@@ -1476,12 +1485,7 @@
this.patchsetLevelDraftMessage = '';
this.includeComments = true;
- this.dispatchEvent(
- new CustomEvent('send', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'send');
fireIronAnnounce(this, 'Reply sent');
return;
})
@@ -1864,12 +1868,7 @@
async cancel() {
assertIsDefined(this.change, 'change');
if (!this.change?.owner) throw new Error('missing required owner property');
- this.dispatchEvent(
- new CustomEvent('cancel', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'cancel');
await this.patchsetLevelGrComment?.save();
this.rebuildReviewerArrays();
}
@@ -1900,13 +1899,7 @@
return;
}
return this.send(this.includeComments, this.canBeStarted).catch(err => {
- this.dispatchEvent(
- new CustomEvent('show-error', {
- bubbles: true,
- composed: true,
- detail: {message: `Error submitting review ${err}`},
- })
- );
+ fireError(this, `Error submitting review ${err}`);
});
}
@@ -2083,7 +2076,7 @@
}
sendDisabledChanged() {
- this.dispatchEvent(new CustomEvent('send-disabled-changed'));
+ fireEventNoBubbleNoCompose(this, 'send-disabled-changed');
}
getReviewerSuggestionsProvider(change?: ChangeInfo | ParsedChangeInfo) {
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
index 9408b82..db19329 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
@@ -23,15 +23,11 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {css} from 'lit';
import {nothing} from 'lit';
+import {fire} from '../../../utils/event-util';
+import {ShowReplyDialogEvent} from '../../../types/events';
@customElement('gr-reviewer-list')
export class GrReviewerList extends LitElement {
- /**
- * Fired when the "Add reviewer..." button is tapped.
- *
- * @event show-reply-dialog
- */
-
@property({type: Object}) change?: ChangeInfo;
@property({type: Object}) account?: AccountDetailInfo;
@@ -203,22 +199,10 @@
handleAddTap(e: Event) {
e.preventDefault();
const value = {
- reviewersOnly: false,
- ccsOnly: false,
+ reviewersOnly: this.reviewersOnly,
+ ccsOnly: this.ccsOnly,
};
- if (this.reviewersOnly) {
- value.reviewersOnly = true;
- }
- if (this.ccsOnly) {
- value.ccsOnly = true;
- }
- this.dispatchEvent(
- new CustomEvent('show-reply-dialog', {
- detail: {value},
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'show-reply-dialog', {value});
}
}
@@ -226,4 +210,8 @@
interface HTMLElementTagNameMap {
'gr-reviewer-list': GrReviewerList;
}
+ interface HTMLElementEventMap {
+ /** Fired when the "Add reviewer..." button is tapped. */
+ 'show-reply-dialog': ShowReplyDialogEvent;
+ }
}
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
index c09d2a2..599e38b 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
@@ -28,7 +28,11 @@
} from '../../../utils/comment-util';
import {pluralize} from '../../../utils/string-util';
import {assertIsDefined} from '../../../utils/common-util';
-import {CommentTabState, TabState} from '../../../types/events';
+import {
+ CommentTabState,
+ TabState,
+ ValueChangedEvent,
+} from '../../../types/events';
import {DropdownItem} from '../../shared/gr-dropdown-list/gr-dropdown-list';
import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip';
import {css, html, LitElement, PropertyValues} from 'lit';
@@ -365,7 +369,7 @@
<gr-dropdown-list
id="sortDropdown"
.value=${this.sortDropdownValue}
- @value-change=${(e: CustomEvent) =>
+ @value-change=${(e: ValueChangedEvent<SortDropdownState>) =>
(this.sortDropdownValue = e.detail.value)}
.items=${this.getSortDropdownEntries()}
>
@@ -521,7 +525,7 @@
}
// private, but visible for testing
- handleCommentsDropdownValueChange(e: CustomEvent) {
+ handleCommentsDropdownValueChange(e: ValueChangedEvent<CommentTabState>) {
const value = e.detail.value;
switch (value) {
case CommentTabState.UNRESOLVED:
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index 128a9b0a..8ba9895 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -634,7 +634,7 @@
return Object.entries(this.errorMessages).map(([plugin, message]) => {
const msg = this.collapsed
? 'Error'
- : `Error while fetching results for ${plugin}:<br />${message}`;
+ : html`Error while fetching results for ${plugin}:<br />${message}`;
return html`
<div class="error">
<div class="left">
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts
index 4bd3446..a858e4d 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts
@@ -22,6 +22,7 @@
);
const getChecksModel = resolve(element, checksModelToken);
setAllFakeRuns(getChecksModel());
+ element.errorMessages = {'test-plugin-name': 'test-error-message'};
await element.updateComplete;
});
@@ -57,6 +58,17 @@
</gr-button>
</gr-tooltip-content>
</h2>
+ <div class="error">
+ <div class="left">
+ <gr-icon filled="" icon="error"> </gr-icon>
+ </div>
+ <div class="right">
+ <div class="message">
+ Error while fetching results for test-plugin-name: <br />
+ test-error-message
+ </div>
+ </div>
+ </div>
<input
id="filterInput"
placeholder="Filter runs by regular expression"
@@ -121,6 +133,14 @@
</gr-button>
</gr-tooltip-content>
</h2>
+ <div class="error">
+ <div class="left">
+ <gr-icon filled="" icon="error"> </gr-icon>
+ </div>
+ <div class="right">
+ <div class="message">Error</div>
+ </div>
+ </div>
<input
hidden
id="filterInput"
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-util.ts b/polygerrit-ui/app/elements/checks/gr-checks-util.ts
index c7477c4..f1a3fb9 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-util.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-util.ts
@@ -9,6 +9,7 @@
AttemptChoice,
LATEST_ATTEMPT,
} from '../../models/checks/checks-util';
+import {fire} from '../../utils/event-util';
export interface RunSelectedEventDetail {
checkName?: string;
@@ -23,13 +24,7 @@
}
export function fireRunSelected(target: EventTarget, checkName: string) {
- target.dispatchEvent(
- new CustomEvent('run-selected', {
- detail: {reset: false, checkName},
- composed: true,
- bubbles: true,
- })
- );
+ fire(target, 'run-selected', {checkName});
}
export function isAttemptSelected(
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.ts b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.ts
index 461781e..3898186 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.ts
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.ts
@@ -7,6 +7,7 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
+import {fireEventNoBubbleNoCompose} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
@@ -83,6 +84,6 @@
}
private handleConfirm() {
- this.dispatchEvent(new CustomEvent('dismiss'));
+ fireEventNoBubbleNoCompose(this, 'dismiss');
}
}
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
index 45ba33b..68ff9e1 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
@@ -16,6 +16,7 @@
ShortcutViewListener,
} from '../../../services/shortcuts/shortcuts-service';
import {resolve} from '../../../models/dependency';
+import {fireEventNoBubble} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
@@ -162,12 +163,7 @@
private handleCloseTap(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('close', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'close');
}
onDirectoryUpdated(directory?: Map<ShortcutSection, SectionView>) {
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 fe90471..1b377e8 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -26,7 +26,7 @@
import {AppElement, AppElementParams} from '../../gr-app-types';
import {LocationChangeEventDetail} from '../../../types/events';
import {GerritView, RouterModel} from '../../../services/router/router-model';
-import {fireAlert, firePageError} from '../../../utils/event-util';
+import {fire, fireAlert, firePageError} from '../../../utils/event-util';
import {windowLocationReload} from '../../../utils/dom-util';
import {
encodeURL,
@@ -555,13 +555,7 @@
hash: window.location.hash,
pathname: window.location.pathname,
};
- document.dispatchEvent(
- new CustomEvent('location-change', {
- detail,
- composed: true,
- bubbles: true,
- })
- );
+ fire(document, 'location-change', detail);
}
_testOnly_startRouter() {
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index 6d8dc2b..406755f 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -26,6 +26,8 @@
import {configModelToken} from '../../../models/config/config-model';
import {resolve} from '../../../models/dependency';
import {subscribe} from '../../lit/subscription-controller';
+import {ValueChangedEvent} from '../../../types/events';
+import {fireNoBubbleNoCompose} from '../../../utils/event-util';
// Possible static search options for auto complete, without negations.
const SEARCH_OPERATORS: ReadonlyArray<string> = [
@@ -231,7 +233,7 @@
@commit=${(e: AutocompleteCommitEvent) => {
this.handleInputCommit(e);
}}
- @text-changed=${(e: CustomEvent) => {
+ @text-changed=${(e: ValueChangedEvent) => {
this.handleSearchTextChanged(e);
}}
>
@@ -310,11 +312,7 @@
const detail: SearchBarHandleSearchDetail = {
inputVal: this.inputVal,
};
- this.dispatchEvent(
- new CustomEvent('handle-search', {
- detail,
- })
- );
+ fireNoBubbleNoCompose(this, 'handle-search', detail);
}
}
@@ -426,7 +424,7 @@
this.searchInput.selectAll();
}
- private handleSearchTextChanged(e: CustomEvent) {
+ private handleSearchTextChanged(e: ValueChangedEvent) {
this.inputVal = e.detail.value;
}
}
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 1608c22..9d29cea 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -36,7 +36,6 @@
import {GrSyntaxLayerWorker} from '../../../embed/diff/gr-syntax-layer/gr-syntax-layer-worker';
import {highlightServiceToken} from '../../../services/highlight/highlight-service';
import {anyLineTooLong} from '../../../embed/diff/gr-diff/gr-diff-utils';
-import {changeModelToken} from '../../../models/change/change-model';
import {fireReload} from '../../../utils/event-util';
interface FilePreview {
@@ -92,9 +91,6 @@
diffPrefs?: DiffPreferencesInfo;
@state()
- isOwner = false;
-
- @state()
onCloseFixPreviewCallbacks: ((fixapplied: boolean) => void)[] = [];
private readonly restApiService = getAppContext().restApiService;
@@ -103,8 +99,6 @@
private readonly getNavigation = resolve(this, navigationToken);
- private readonly getChangeModel = resolve(this, changeModelToken);
-
private readonly syntaxLayer = new GrSyntaxLayerWorker(
resolve(this, highlightServiceToken),
() => getAppContext().reportingService
@@ -114,11 +108,6 @@
super();
subscribe(
this,
- () => this.getChangeModel().isOwner$,
- x => (this.isOwner = x)
- );
- subscribe(
- this,
() => this.getUserModel().preferences$,
preferences => {
const layers: DiffLayer[] = [this.syntaxLayer];
@@ -341,7 +330,6 @@
private computeTooltip() {
if (!this.change || !this.patchNum) return '';
- if (!this.isOwner) return 'Fix can only be applied by author';
const latestPatchNum =
this.change.revisions[this.change.current_revision]._number;
return latestPatchNum !== this.patchNum
@@ -351,7 +339,6 @@
private computeDisableApplyFixButton() {
if (!this.change || !this.patchNum) return true;
- if (!this.isOwner) return true;
const latestPatchNum =
this.change.revisions[this.change.current_revision]._number;
return this.patchNum !== latestPatchNum || this.isApplyFixLoading;
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
index 9284fb2..a686b20 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
@@ -71,7 +71,6 @@
element.changeNum = change._number;
element.patchNum = change.revisions[change.current_revision]._number;
element.change = change;
- element.isOwner = true;
element.diffPrefs = {
...createDefaultDiffPrefs(),
font_size: 12,
@@ -161,22 +160,8 @@
assert.equal(button.getAttribute('title'), '');
});
- test('apply fix button is disabled for non-author', async () => {
- element.isOwner = false;
- await element.updateComplete;
- await open(TWO_FIXES);
- assert.equal(element.currentFix!.fix_id, 'fix_1');
- assert.equal(element.currentPreviews.length, 2);
- const button = getConfirmButton();
- assert.isTrue(button.hasAttribute('disabled'));
- assert.equal(
- button.getAttribute('title'),
- 'Fix can only be applied by author'
- );
- });
-
test('apply fix button is disabled on older patchset', async () => {
- element.change = {
+ element.change = element.change = {
...createParsedChange(),
revisions: createRevisions(2),
current_revision: getCurrentRevision(0),
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index fd0e5d2..79145db 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -68,7 +68,11 @@
import {Timing, Interaction} from '../../../constants/reporting';
import {ChangeComments} from '../gr-comment-api/gr-comment-api';
import {Subscription} from 'rxjs';
-import {DisplayLine, RenderPreferences} from '../../../api/diff';
+import {
+ DisplayLine,
+ LineSelectedEventDetail,
+ RenderPreferences,
+} from '../../../api/diff';
import {resolve} from '../../../models/dependency';
import {browserModelToken} from '../../../models/browser/browser-model';
import {commentsModelToken} from '../../../models/comments/comments-model';
@@ -120,7 +124,7 @@
declare global {
interface HTMLElementEventMap {
/* prettier-ignore */
- 'render': CustomEvent;
+ 'render': CustomEvent<void>;
'diff-context-expanded': CustomEvent<DiffContextExpandedEventDetail>;
'create-comment': CustomEvent<CreateCommentEventDetail>;
'is-blame-loaded-changed': ValueChangedEvent<boolean>;
@@ -129,7 +133,7 @@
'files-weblinks-changed': ValueChangedEvent<FilesWebLinks | undefined>;
'is-image-diff-changed': ValueChangedEvent<boolean>;
// Fired when the user selects a line (See gr-diff).
- 'line-selected': CustomEvent;
+ 'line-selected': CustomEvent<LineSelectedEventDetail>;
// Fired if being logged in is required.
'show-auth-required': void;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts
index 531d2ae..69ff201 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts
@@ -12,6 +12,7 @@
import {customElement, query, state} from 'lit/decorators.js';
import {ValueChangedEvent} from '../../../types/events';
import {modalStyles} from '../../../styles/gr-modal-styles';
+import {fireEventNoBubble} from '../../../utils/event-util';
@customElement('gr-diff-preferences-dialog')
export class GrDiffPreferencesDialog extends LitElement {
@@ -120,12 +121,7 @@
assertIsDefined(this.diffPreferences, 'diffPreferences');
assertIsDefined(this.diffPrefsModal, 'diffPrefsModal');
await this.diffPreferences.save();
- this.dispatchEvent(
- new CustomEvent('reload-diff-preference', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'reload-diff-preference');
this.diffPrefsModal.close();
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 19186ec..b9074b4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -33,6 +33,7 @@
DropdownItem,
GrDropdownList,
} from '../../shared/gr-dropdown-list/gr-dropdown-list';
+import {CommentAnchorTapEventDetail} from '../../shared/gr-comment/gr-comment';
import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
import {
BasePatchSetNum,
@@ -48,7 +49,10 @@
} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
import {FileRange, ParsedChangeInfo} from '../../../types/types';
-import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select';
+import {
+ FilesWebLinks,
+ PatchRangeChangeEvent,
+} from '../gr-patch-range-select/gr-patch-range-select';
import {GrDiffCursor} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
import {CommentSide, DiffViewMode, Side} from '../../../constants/constants';
import {GrApplyFixDialog} from '../gr-apply-fix-dialog/gr-apply-fix-dialog';
@@ -66,7 +70,7 @@
ShortcutSection,
shortcutsServiceToken,
} from '../../../services/shortcuts/shortcuts-service';
-import {DisplayLine} from '../../../api/diff';
+import {DisplayLine, LineSelectedEventDetail} from '../../../api/diff';
import {GrDownloadDialog} from '../../change/gr-download-dialog/gr-download-dialog';
import {commentsModelToken} from '../../../models/comments/comments-model';
import {changeModelToken} from '../../../models/change/change-model';
@@ -768,7 +772,7 @@
.path=${this.path}
.projectName=${this.change?.project}
@is-blame-loaded-changed=${this.onIsBlameLoadedChanged}
- @comment-anchor-tap=${this.onLineSelected}
+ @comment-anchor-tap=${this.onCommentAnchorTap}
@line-selected=${this.onLineSelected}
@diff-changed=${this.onDiffChanged}
@edit-weblinks-changed=${this.onEditWeblinksChanged}
@@ -1443,7 +1447,7 @@
}
// Private but used in tests.
- handlePatchChange(e: CustomEvent) {
+ handlePatchChange(e: PatchRangeChangeEvent) {
if (!this.path) return;
if (!this.patchNum) return;
@@ -1466,16 +1470,23 @@
}
// Private but used in tests.
- onLineSelected(e: CustomEvent) {
- // for on-comment-anchor-tap side can be PARENT/REVISIONS
- // for on-line-selected side can be left/right
+ onCommentAnchorTap(e: CustomEvent<CommentAnchorTapEventDetail>) {
+ const lineNumber = e.detail.number;
+ if (!Number.isInteger(lineNumber)) return;
this.updateUrlToDiffUrl(
- e.detail.number,
- e.detail.side === Side.LEFT || e.detail.side === CommentSide.PARENT
+ lineNumber as number,
+ e.detail.side === CommentSide.PARENT
);
}
// Private but used in tests.
+ onLineSelected(e: CustomEvent<LineSelectedEventDetail>) {
+ const lineNumber = e.detail.number;
+ if (!Number.isInteger(lineNumber)) return;
+ this.updateUrlToDiffUrl(lineNumber as number, e.detail.side === Side.LEFT);
+ }
+
+ // Private but used in tests.
computeDownloadDropdownLinks() {
if (!this.change?.project) return [];
if (!this.changeNum) return [];
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 325798c..01c007b 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
@@ -47,6 +47,7 @@
import {GeneratedWebLink} from '../../../utils/weblink-util';
import {changeModelToken} from '../../../models/change/change-model';
import {changeViewModelToken} from '../../../models/views/change';
+import {fireNoBubbleNoCompose} from '../../../utils/event-util';
// Maximum length for patch set descriptions.
const PATCH_DESC_MAX_LENGTH = 500;
@@ -56,7 +57,7 @@
}
export interface PatchRangeChangeDetail {
- patchNum?: PatchSetNum;
+ patchNum?: RevisionPatchSetNum;
basePatchNum?: BasePatchSetNum;
}
@@ -73,6 +74,12 @@
}
}
+declare global {
+ interface HTMLElementEventMap {
+ 'patch-range-change': PatchRangeChangeEvent;
+ }
+}
+
/**
* Fired when the patch range changes
*
@@ -463,7 +470,9 @@
basePatchNum: this.basePatchNum,
};
const target = e.target;
- const patchSetValue = convertToPatchSetNum(e.detail.value)!;
+ const patchSetValue = convertToPatchSetNum(
+ e.detail.value
+ ) as RevisionPatchSetNum;
const latestPatchNum = computeLatestPatchNum(this.availablePatches);
if (target === this.patchNumDropdown) {
if (detail.patchNum === patchSetValue) return;
@@ -488,8 +497,6 @@
detail.basePatchNum = patchSetValue as BasePatchSetNum;
}
- this.dispatchEvent(
- new CustomEvent('patch-range-change', {detail, bubbles: false})
- );
+ fireNoBubbleNoCompose(this, 'patch-range-change', detail);
}
}
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
index 7229c63..25d3cf4 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
@@ -6,11 +6,16 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, css, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
+import {fire} from '../../../utils/event-util';
+import {ValueChangedEvent} from '../../../types/events';
declare global {
interface HTMLElementTagNameMap {
'gr-default-editor': GrDefaultEditor;
}
+ interface HTMLElementEventMap {
+ 'content-change': ValueChangedEvent;
+ }
}
@customElement('gr-default-editor')
@@ -56,12 +61,7 @@
}
_handleTextareaInput(e: Event) {
- this.dispatchEvent(
- new CustomEvent('content-change', {
- detail: {value: (e.target as HTMLTextAreaElement).value},
- bubbles: true,
- composed: true,
- })
- );
+ const value = (e.target as HTMLTextAreaElement).value;
+ fire(this, 'content-change', {value});
}
}
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
index c442aa6..751b64d 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
@@ -6,8 +6,11 @@
import '../../shared/gr-dropdown/gr-dropdown';
import {GrEditConstants} from '../gr-edit-constants';
import {sharedStyles} from '../../../styles/shared-styles';
+import {FileActionTapEvent} from '../../../types/events';
import {LitElement, css, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
+import {DropdownLink} from '../../shared/gr-dropdown/gr-dropdown';
+import {fire} from '../../../utils/event-util';
interface EditAction {
label: string;
@@ -16,12 +19,6 @@
@customElement('gr-edit-file-controls')
export class GrEditFileControls extends LitElement {
- /**
- * Fired when an action in the overflow menu is tapped.
- *
- * @event file-action-tap
- */
-
@property({type: String})
filePath?: string;
@@ -64,23 +61,20 @@
>`;
}
- _handleActionTap(e: CustomEvent) {
+ _handleActionTap(e: CustomEvent<DropdownLink>) {
e.preventDefault();
e.stopPropagation();
- this._dispatchFileAction(e.detail.id, this.filePath);
+ const actionId = e.detail.id;
+ if (!actionId) return;
+ if (!this.filePath) return;
+ this._dispatchFileAction(actionId, this.filePath);
}
- _dispatchFileAction(action: EditAction, path?: string) {
- this.dispatchEvent(
- new CustomEvent('file-action-tap', {
- detail: {action, path},
- bubbles: true,
- composed: true,
- })
- );
+ _dispatchFileAction(action: string, path: string) {
+ fire(this, 'file-action-tap', {action, path});
}
- _computeFileActions(actions: EditAction[]) {
+ _computeFileActions(actions: EditAction[]): DropdownLink[] {
// TODO(kaspern): conditionally disable some actions based on file status.
return actions.map(action => {
return {
@@ -95,4 +89,7 @@
interface HTMLElementTagNameMap {
'gr-edit-file-controls': GrEditFileControls;
}
+ interface HTMLElementEventMap {
+ 'file-action-tap': FileActionTapEvent;
+ }
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
index bc4e701..b34e1c3 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
@@ -6,6 +6,7 @@
import {AttributeHelperPluginApi} from '../../../api/attribute-helper';
import {PluginApi} from '../../../api/plugin';
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
+import {ValueChangedEvent} from '../../../types/events';
export class GrAttributeHelper implements AttributeHelperPluginApi {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -51,7 +52,7 @@
bind(name: string, callback: (value: any) => void) {
this.reporting.trackApi(this.plugin, 'attribute', 'bind');
const attributeChangedEventName = this._getChangedEventName(name);
- const changedHandler = (e: CustomEvent) =>
+ const changedHandler = (e: ValueChangedEvent) =>
this._reportValue(callback, e.detail.value);
const unbind = () =>
this.element.removeEventListener(
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts
index e73aad6..d3429fe 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts
@@ -5,6 +5,7 @@
*/
import {LitElement, PropertyValues} from 'lit';
import {customElement, property} from 'lit/decorators.js';
+import {fireNoBubbleNoCompose} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
@@ -22,9 +23,7 @@
override willUpdate(changedProperties: PropertyValues) {
if (changedProperties.has('value')) {
- this.dispatchEvent(
- new CustomEvent('value-changed', {detail: {value: this.value}})
- );
+ fireNoBubbleNoCompose(this, 'value-changed', {value: this.value});
}
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
index 31e4f5d..e8c5c92 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
@@ -17,6 +17,8 @@
import {customElement, property} from 'lit/decorators.js';
import {ClassInfo, classMap} from 'lit/directives/class-map.js';
import {getLabelStatus, hasVoted, LabelStatus} from '../../../utils/label-util';
+import {fire} from '../../../utils/event-util';
+import {RemoveAccountEvent} from '../../../types/events';
@customElement('gr-account-chip')
export class GrAccountChip extends LitElement {
@@ -196,13 +198,8 @@
private handleRemoveTap(e: MouseEvent) {
e.preventDefault();
- this.dispatchEvent(
- new CustomEvent('remove', {
- detail: {account: this.account},
- composed: true,
- bubbles: true,
- })
- );
+ if (!this.account) return;
+ fire(this, 'remove', {account: this.account});
}
private getHasAvatars() {
@@ -232,4 +229,8 @@
interface HTMLElementTagNameMap {
'gr-account-chip': GrAccountChip;
}
+ interface HTMLElementEventMap {
+ /* prettier-ignore */
+ 'remove': RemoveAccountEvent;
+ }
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
index 08f7c93..f07bee6 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
@@ -12,9 +12,10 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, html, css} from 'lit';
import {customElement, property, query, state} from 'lit/decorators.js';
-import {BindValueChangeEvent} from '../../../types/events';
+import {AddAccountEvent, BindValueChangeEvent} from '../../../types/events';
import {SuggestedReviewerInfo} from '../../../types/common';
import {PaperInputElement} from '@polymer/paper-input/paper-input';
+import {fire, fireEvent} from '../../../utils/event-util';
/**
* gr-account-entry is an element for entering account
@@ -24,20 +25,6 @@
export class GrAccountEntry extends LitElement {
@query('#input') private input?: GrAutocomplete;
- /**
- * Fired when an account is entered.
- *
- * @event add
- */
-
- /**
- * When allowAnyInput is true, account-text-changed is fired when input text
- * changed. This is needed so that the reply dialog's save button can be
- * enabled for arbitrary cc's, which don't need a 'commit'.
- *
- * @event account-text-changed
- */
-
@property({type: Boolean})
allowAnyInput = false;
@@ -112,21 +99,13 @@
}
private handleInputCommit(e: AutocompleteCommitEvent) {
- this.dispatchEvent(
- new CustomEvent('add', {
- detail: {value: e.detail.value},
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'add', {value: e.detail.value});
this.input!.focus();
}
private inputTextChanged() {
if (this.inputText.length && this.allowAnyInput) {
- this.dispatchEvent(
- new CustomEvent('account-text-changed', {bubbles: true, composed: true})
- );
+ fireEvent(this, 'account-text-changed');
}
}
@@ -139,4 +118,17 @@
interface HTMLElementTagNameMap {
'gr-account-entry': GrAccountEntry;
}
+ interface HTMLElementEventMap {
+ /**
+ * Fired when an account is entered.
+ */
+ /* prettier-ignore */
+ 'add': AddAccountEvent;
+ /**
+ * When allowAnyInput is true, account-text-changed is fired when input text
+ * changed. This is needed so that the reply dialog's save button can be
+ * enabled for arbitrary cc's, which don't need a 'commit'.
+ */
+ 'account-text-changed': CustomEvent<void>;
+ }
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
index bb0200a..0f85266 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
@@ -13,9 +13,9 @@
import {isSelf, isServiceUser} from '../../../utils/account-util';
import {ChangeInfo, AccountInfo, ServerInfo} from '../../../types/common';
import {assertIsDefined, hasOwnProperty} from '../../../utils/common-util';
-import {fireEvent} from '../../../utils/event-util';
+import {fire, fireEvent} from '../../../utils/event-util';
import {isInvolved} from '../../../utils/change-util';
-import {EventType, ShowAlertEventDetail} from '../../../types/events';
+import {EventType} from '../../../types/events';
import {LitElement, css, html, TemplateResult} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {classMap} from 'lit/directives/class-map.js';
@@ -364,16 +364,10 @@
e.stopPropagation();
if (!this.account._account_id) return;
- this.dispatchEvent(
- new CustomEvent<ShowAlertEventDetail>(EventType.SHOW_ALERT, {
- detail: {
- message: 'Saving attention set update ...',
- dismissOnNavigation: true,
- },
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, EventType.SHOW_ALERT, {
+ message: 'Saving attention set update ...',
+ dismissOnNavigation: true,
+ });
// We are deliberately updating the UI before making the API call. It is a
// risk that we are taking to achieve a better UX for 99.9% of the cases.
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
index 661ffa0..34b8f11 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
@@ -6,7 +6,7 @@
import '../gr-cursor-manager/gr-cursor-manager';
import '../../../styles/shared-styles';
import {GrCursorManager} from '../gr-cursor-manager/gr-cursor-manager';
-import {fireEvent} from '../../../utils/event-util';
+import {fire, fireEvent} from '../../../utils/event-util';
import {Key} from '../../../utils/dom-util';
import {FitController} from '../../lit/fit-controller';
import {css, html, LitElement, PropertyValues} from 'lit';
@@ -30,7 +30,7 @@
value?: string;
}
-export interface ItemSelectedEvent {
+export interface ItemSelectedEventDetail {
trigger: string;
selected: HTMLElement | null;
}
@@ -274,32 +274,20 @@
// private but used in tests
handleTab() {
if (this.isSuggestionListInteractible()) {
- this.dispatchEvent(
- new CustomEvent<ItemSelectedEvent>('item-selected', {
- detail: {
- trigger: 'tab',
- selected: this.cursor.target,
- },
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'item-selected', {
+ trigger: 'tab',
+ selected: this.cursor.target,
+ });
}
}
// private but used in tests
handleEnter() {
if (this.isSuggestionListInteractible()) {
- this.dispatchEvent(
- new CustomEvent<ItemSelectedEvent>('item-selected', {
- detail: {
- trigger: 'enter',
- selected: this.cursor.target,
- },
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'item-selected', {
+ trigger: 'enter',
+ selected: this.cursor.target,
+ });
}
}
@@ -318,16 +306,10 @@
}
selected = selected.parentElement!;
}
- this.dispatchEvent(
- new CustomEvent<ItemSelectedEvent>('item-selected', {
- detail: {
- trigger: 'click',
- selected,
- },
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'item-selected', {
+ trigger: 'click',
+ selected,
+ });
}
private fireClose() {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
index db0d236..9b0fa7b 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -11,6 +11,7 @@
AutocompleteQueryStatus,
AutocompleteQueryStatusType,
GrAutocompleteDropdown,
+ ItemSelectedEventDetail,
} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
import {fire, fireEvent} from '../../../utils/event-util';
import {
@@ -73,13 +74,6 @@
*/
/**
- * Fired on keydown to allow for custom hooks into autocomplete textbox
- * behavior.
- *
- * @event input-keydown
- */
-
- /**
* Query for requesting autocomplete suggestions. The function should
* accept the input as a string parameter and return a promise. The
* promise yields an array of suggestion objects with "name", "label",
@@ -305,7 +299,7 @@
class=${this.computeClass()}
?disabled=${this.disabled}
.value=${this.text}
- @value-changed=${(e: CustomEvent) => {
+ @value-changed=${(e: ValueChangedEvent) => {
this.text = e.detail.value;
}}
.placeholder=${this.placeholder}
@@ -366,14 +360,16 @@
this.text = '';
}
- private handleItemSelectEnter(e: CustomEvent | KeyboardEvent) {
+ private handleItemSelectEnter(
+ e: CustomEvent<ItemSelectedEventDetail> | KeyboardEvent
+ ) {
this.handleInputCommit();
e.stopPropagation();
e.preventDefault();
this.focusWithoutDisplayingSuggestions();
}
- handleItemSelect(e: CustomEvent) {
+ handleItemSelect(e: CustomEvent<ItemSelectedEventDetail>) {
if (e.detail.trigger === 'click') {
this.selected = e.detail.selected;
this._commit();
@@ -608,13 +604,6 @@
this.resetQueryOutput();
this.activeQueryId = 0;
}
- this.dispatchEvent(
- new CustomEvent('input-keydown', {
- detail: {key: e.key, input: this.input},
- composed: true,
- bubbles: true,
- })
- );
}
cancel() {
@@ -717,13 +706,7 @@
// 'commit' event
await this.updateComplete;
if (!silent) {
- this.dispatchEvent(
- new CustomEvent('commit', {
- detail: {value} as AutocompleteCommitEventDetail,
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'commit', {value});
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
index c59b8e8..0cef331 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
@@ -1063,27 +1063,14 @@
});
});
- test('input-keydown event fired', async () => {
- const listener = sinon.spy();
- element.addEventListener('input-keydown', listener);
- pressKey(inputEl(), Key.TAB);
- await element.updateComplete;
- assert.isTrue(listener.called);
- });
-
test('enter with modifier does not complete', async () => {
- const dispatchEventStub = sinon.stub(element, 'dispatchEvent');
const commitStub = sinon.stub(element, 'handleInputCommit');
+
pressKey(inputEl(), Key.ENTER, Modifier.CTRL_KEY);
await element.updateComplete;
- assert.equal(dispatchEventStub.lastCall.args[0].type, 'input-keydown');
- assert.equal(
- (dispatchEventStub.lastCall.args[0] as CustomEvent).detail.key,
- Key.ENTER
- );
-
assert.isFalse(commitStub.called);
+
pressKey(inputEl(), Key.ENTER);
await element.updateComplete;
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts
index 0bb451c..54fb825 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts
@@ -14,6 +14,7 @@
import {resolve} from '../../../models/dependency';
import {shortcutsServiceToken} from '../../../services/shortcuts/shortcuts-service';
import {assertIsDefined} from '../../../utils/common-util';
+import {fire} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
@@ -93,12 +94,6 @@
change: this.change,
starred: newVal,
};
- this.dispatchEvent(
- new CustomEvent('toggle-star', {
- bubbles: true,
- composed: true,
- detail,
- })
- );
+ fire(this, 'toggle-star', detail);
}
}
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 c844d42..c36226a 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -275,6 +275,9 @@
this.save();
});
}
+ this.addEventListener('open-user-suggest-preview', e => {
+ this.handleShowFix(e.detail.code);
+ });
this.messagePlaceholder = 'Mention others with @';
subscribe(
this,
@@ -524,7 +527,6 @@
${this.renderCommentMessage()}
<gr-endpoint-slot name="above-actions"></gr-endpoint-slot>
${this.renderHumanActions()} ${this.renderRobotActions()}
- ${this.renderSuggestEditActions()}
</div>
</div>
</gr-endpoint-decorator>
@@ -776,32 +778,13 @@
return html`
<div class="rightActions">
${this.autoSaving ? html`. ` : ''}
- ${this.renderDiscardButton()} ${this.renderPreviewSuggestEditButton()}
- ${this.renderEditButton()} ${this.renderCancelButton()}
- ${this.renderSaveButton()} ${this.renderCopyLinkIcon()}
+ ${this.renderDiscardButton()} ${this.renderEditButton()}
+ ${this.renderCancelButton()} ${this.renderSaveButton()}
+ ${this.renderCopyLinkIcon()}
</div>
`;
}
- private renderPreviewSuggestEditButton() {
- if (!this.flagsService.isEnabled(KnownExperimentId.SUGGEST_EDIT)) {
- return nothing;
- }
- assertIsDefined(this.comment, 'comment');
- if (!hasUserSuggestion(this.comment)) return nothing;
- return html`
- <gr-button
- link
- secondary
- class="action show-fix"
- ?disabled=${this.saving}
- @click=${this.handleShowFix}
- >
- Preview Fix
- </gr-button>
- `;
- }
-
private renderSuggestEditButton() {
if (!this.flagsService.isEnabled(KnownExperimentId.SUGGEST_EDIT)) {
return nothing;
@@ -892,22 +875,6 @@
`;
}
- private renderSuggestEditActions() {
- if (!this.flagsService.isEnabled(KnownExperimentId.SUGGEST_EDIT)) {
- return nothing;
- }
- if (
- !this.account ||
- isRobot(this.comment) ||
- isDraftOrUnsaved(this.comment)
- ) {
- return nothing;
- }
- return html`
- <div class="robotActions">${this.renderPreviewSuggestEditButton()}</div>
- `;
- }
-
private renderShowFixButton() {
if (!(this.comment as RobotCommentInfo)?.fix_suggestions) return;
return html`
@@ -1037,12 +1004,14 @@
}
// private, but visible for testing
- async createFixPreview(): Promise<OpenFixPreviewEventDetail> {
+ async createFixPreview(
+ replacement?: string
+ ): Promise<OpenFixPreviewEventDetail> {
assertIsDefined(this.comment?.patch_set, 'comment.patch_set');
assertIsDefined(this.comment?.path, 'comment.path');
- if (hasUserSuggestion(this.comment)) {
- const replacement = getUserSuggestion(this.comment);
+ if (hasUserSuggestion(this.comment) || replacement) {
+ replacement = replacement ?? getUserSuggestion(this.comment);
assert(!!replacement, 'malformed user suggestion');
const line = await this.getCommentedCode();
@@ -1150,9 +1119,9 @@
fire(this, 'reply-to-comment', eventDetail);
}
- private async handleShowFix() {
+ private async handleShowFix(replacement?: string) {
// Handled top-level in the diff and change view components.
- fire(this, 'open-fix-preview', await this.createFixPreview());
+ fire(this, 'open-fix-preview', await this.createFixPreview(replacement));
}
async createSuggestEdit(e: MouseEvent) {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index 3390369..6625844 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -29,19 +29,12 @@
import {
createComment,
createDraft,
- createFixSuggestionInfo,
createRobotComment,
createUnsaved,
} from '../../../test/test-data-generators';
-import {
- ReplyToCommentEvent,
- OpenFixPreviewEventDetail,
-} from '../../../types/events';
+import {ReplyToCommentEvent} from '../../../types/events';
import {GrConfirmDeleteCommentDialog} from '../gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog';
-import {
- DraftInfo,
- USER_SUGGESTION_START_PATTERN,
-} from '../../../utils/comment-util';
+import {DraftInfo} from '../../../utils/comment-util';
import {assertIsDefined} from '../../../utils/common-util';
import {Modifier} from '../../../utils/dom-util';
import {SinonStub} from 'sinon';
@@ -747,23 +740,6 @@
actions = query(element, '.robotActions gr-button.fix');
assert.isNotOk(actions);
});
-
- test('handleShowFix fires open-fix-preview event', async () => {
- const listener = listenOnce<CustomEvent<OpenFixPreviewEventDetail>>(
- element,
- 'open-fix-preview'
- );
- element.comment = {
- ...createRobotComment(),
- fix_suggestions: [{...createFixSuggestionInfo()}],
- };
- await element.updateComplete;
-
- queryAndAssert<GrButton>(element, '.show-fix').click();
-
- const e = await listener;
- assert.deepEqual(e.detail, await element.createFixPreview());
- });
});
suite('auto saving', () => {
@@ -869,33 +845,5 @@
</gr-button> `
);
});
-
- test('renders preview suggest fix', async () => {
- element.comment = {
- ...createComment(),
- author: {
- name: 'Mr. Peanutbutter',
- email: 'tenn1sballchaser@aol.com' as EmailAddress,
- },
- line: 5,
- path: 'test',
- message: `${USER_SUGGESTION_START_PATTERN}afterSuggestion${'\n```'}`,
- };
- await element.updateComplete;
-
- assert.dom.equal(
- queryAndAssert(element, 'gr-button.show-fix'),
- /* HTML */ `<gr-button
- aria-disabled="false"
- class="action show-fix"
- link=""
- role="button"
- secondary
- tabindex="0"
- >
- Preview Fix
- </gr-button> `
- );
- });
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
index 285a41a..1047b87 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
@@ -11,6 +11,7 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {assertIsDefined} from '../../../utils/common-util';
import {BindValueChangeEvent} from '../../../types/events';
+import {fireEventNoBubble, fireNoBubble} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
@@ -108,23 +109,12 @@
private handleConfirmTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('confirm', {
- detail: {reason: this.message},
- composed: true,
- bubbles: false,
- })
- );
+ fireNoBubble(this, 'confirm', {reason: this.message});
}
private handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('cancel', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'cancel');
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
index 350aa7f..8c280c0 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
@@ -66,7 +66,10 @@
color: var(--primary-text-color);
}
gr-icon {
- color: var(--deemphasized-text-color);
+ color: var(
+ --gr-copy-clipboard-icon-color,
+ var(--deemphasized-text-color)
+ );
}
gr-button {
display: block;
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
index 04d5923..c5ad676 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
@@ -10,6 +10,7 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {fontStyles} from '../../../styles/gr-font-styles';
import {when} from 'lit/directives/when.js';
+import {fireEventNoBubble} from '../../../utils/event-util';
declare global {
interface HTMLElementTagNameMap {
@@ -199,23 +200,13 @@
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('confirm', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'confirm');
}
private handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
- this.dispatchEvent(
- new CustomEvent('cancel', {
- composed: true,
- bubbles: false,
- })
- );
+ fireEventNoBubble(this, 'cancel');
}
_handleKeydown(e: KeyboardEvent) {
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
index b6ca9f5..c935e72 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
@@ -23,6 +23,7 @@
import {incrementalRepeat} from '../../lit/incremental-repeat';
import {when} from 'lit/directives/when.js';
import {isMagicPath} from '../../../utils/path-list-util';
+import {fireNoBubble} from '../../../utils/event-util';
/**
* Required values are text and value. mobileText and triggerText will
@@ -303,12 +304,7 @@
this.text = selectedObj.triggerText
? selectedObj.triggerText
: selectedObj.text;
- this.dispatchEvent(
- new CustomEvent('value-change', {
- detail: {value: this.value},
- bubbles: false,
- })
- );
+ fireNoBubble(this, 'value-change', {value: this.value});
}
/**
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
index 3a8946a..495b448 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
@@ -242,7 +242,8 @@
allowOutsideScroll
.horizontalAlign=${this.horizontalAlign}
@click=${() => this.close()}
- @opened-changed=${(e: CustomEvent) => (this.opened = e.detail.value)}
+ @opened-changed=${(e: ValueChangedEvent<boolean>) =>
+ (this.opened = e.detail.value)}
>
${this.renderDropdownContent()}
</iron-dropdown>`;
@@ -460,13 +461,7 @@
const item = this.items.find(item => item.id === id);
if (id && !this.disabledIds.includes(id)) {
if (item) {
- this.dispatchEvent(
- new CustomEvent('tap-item', {
- detail: item,
- bubbles: true,
- composed: true,
- })
- );
+ fire(this, 'tap-item', item);
}
this.dispatchEvent(new CustomEvent('tap-item-' + id));
}
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
index e176598..01ebb27 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
@@ -386,13 +386,7 @@
handleSave(e: Event) {
e.preventDefault();
- this.dispatchEvent(
- new CustomEvent('editable-content-save', {
- detail: {content: this.newContent},
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'editable-content-save', {content: this.newContent});
// It would be nice, if we would set this.newContent = undefined here,
// but we can only do that when we are sure that the save operation has
// succeeded.
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
index bf8209b..c8574cd 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
@@ -21,6 +21,8 @@
import {PaperInputElement} from '@polymer/paper-input/paper-input';
import {IronInputElement} from '@polymer/iron-input';
import {ShortcutController} from '../../lit/shortcut-controller';
+import {ValueChangedEvent} from '../../../types/events';
+import {fire} from '../../../utils/event-util';
const AWAIT_MAX_ITERS = 10;
const AWAIT_STEP = 5;
@@ -207,7 +209,7 @@
.text=${this.inputText}
.query=${this.query}
@cancel=${this.cancel}
- @text-changed=${(e: CustomEvent) => {
+ @text-changed=${(e: ValueChangedEvent) => {
this.inputText = e.detail.value;
}}
>
@@ -308,13 +310,8 @@
this.value = this.inputText || '';
}
this.editing = false;
- this.dispatchEvent(
- new CustomEvent('changed', {
- detail: this.value,
- composed: true,
- bubbles: true,
- })
- );
+ // TODO: This event seems to be unused (no listener). Remove?
+ fire(this, 'changed', this.value);
}
private cancel() {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index 45eca40..023d8b5 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -18,6 +18,10 @@
import {CommentLinks, EmailAddress} from '../../../api/rest-api';
import {linkifyUrlsAndApplyRewrite} from '../../../utils/link-util';
import '../gr-account-chip/gr-account-chip';
+import '../gr-user-suggestion-fix/gr-user-suggestion-fix';
+import {KnownExperimentId} from '../../../services/flags/flags';
+import {getAppContext} from '../../../services/app-context';
+import {USER_SUGGESTION_INFO_STRING} from '../../../utils/comment-util';
/**
* This element optionally renders markdown and also applies some regex
@@ -34,6 +38,8 @@
@state()
private repoCommentLinks: CommentLinks = {};
+ private readonly flagsService = getAppContext().flagsService;
+
private readonly getConfigModel = resolve(this, configModelToken);
// Private const but used in tests.
@@ -134,6 +140,10 @@
}
private renderAsMarkdown() {
+ // need to find out here, since customRender is not arrow function
+ const suggestEditsEnable = this.flagsService.isEnabled(
+ KnownExperimentId.SUGGEST_EDIT
+ );
// <marked-element> internals will be in charge of calling our custom
// renderer so we wrap 'this.rewriteText' so that 'this' is preserved via
// closure.
@@ -167,7 +177,18 @@
`![${text}](${href})`;
renderer['codespan'] = (text: string) =>
`<code>${unescapeHTML(text)}</code>`;
- renderer['code'] = (text: string) => `<pre><code>${text}</code></pre>`;
+ renderer['code'] = (text: string, infostring: string) => {
+ if (suggestEditsEnable && infostring === USER_SUGGESTION_INFO_STRING) {
+ // default santizer in markedjs is very restrictive, we need to use
+ // existing html element to mark element. We cannot use css class for it.
+ // Therefore we pick mark - as not frequently used html element to represent
+ // unconverted gr-user-suggestion-fix.
+ // TODO(milutin): Find a way to override sanitizer to directly use gr-user-suggestion-fix
+ return `<mark>${text}</mark>`;
+ } else {
+ return `<pre><code>${text}</code></pre>`;
+ }
+ };
renderer['text'] = boundRewriteText;
}
@@ -211,6 +232,9 @@
override updated() {
// Look for @mentions and replace them with an account-label chip.
this.convertEmailsToAccountChips();
+ if (this.flagsService.isEnabled(KnownExperimentId.SUGGEST_EDIT)) {
+ this.convertCodeToSuggestions();
+ }
}
private convertEmailsToAccountChips() {
@@ -235,6 +259,17 @@
}
}
}
+
+ private convertCodeToSuggestions() {
+ for (const userSuggestionMark of this.renderRoot.querySelectorAll('mark')) {
+ const userSuggestion = document.createElement('gr-user-suggestion-fix');
+ userSuggestion.textContent = userSuggestionMark.textContent ?? '';
+ userSuggestionMark.parentNode?.replaceChild(
+ userSuggestion,
+ userSuggestionMark
+ );
+ }
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
index fcebeea..0e5117a 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -587,5 +587,25 @@
`
);
});
+
+ suite('user suggest fix', () => {
+ setup(async () => {
+ const flagsService = getAppContext().flagsService;
+ sinon.stub(flagsService, 'isEnabled').returns(true);
+ });
+
+ test('renders', async () => {
+ element.content = '```suggestion\nHello World```';
+ await element.updateComplete;
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `<marked-element>
+ <div class="markdown-html" slot="markdown-html">
+ <gr-user-suggestion-fix>Hello World</gr-user-suggestion-fix>
+ </div>
+ </marked-element>`
+ );
+ });
+ });
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.ts
index 0628d2f..c81f586 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.ts
@@ -4,13 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {RevisionInfo, ChangeInfo, RequestPayload} from '../../../types/common';
-import {EventType, ShowAlertEventDetail} from '../../../types/events';
import {PluginApi} from '../../../api/plugin';
import {UIActionInfo} from './gr-change-actions-js-api';
import {windowLocationReload} from '../../../utils/dom-util';
import {PopupPluginApi} from '../../../api/popup';
import {GrPopupInterface} from '../../plugins/gr-popup-interface/gr-popup-interface';
import {getAppContext} from '../../../services/app-context';
+import {fireAlert} from '../../../utils/event-util';
interface ButtonCallBacks {
onclick: (event: Event) => boolean;
@@ -110,13 +110,7 @@
.send(this.action.method, this.action.__url, payload)
.then(onSuccess)
.catch((error: unknown) => {
- document.dispatchEvent(
- new CustomEvent<ShowAlertEventDetail>(EventType.SHOW_ALERT, {
- detail: {
- message: `Plugin network error: ${error}`,
- },
- })
- );
+ fireAlert(document, `Plugin network error: ${error}`);
});
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts
index 8b3e2a6..615a04a 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts
@@ -17,7 +17,11 @@
} from '../../../../types/common';
import {HttpMethod} from '../../../../constants/constants';
import {RpcLogEventDetail} from '../../../../types/events';
-import {fireNetworkError, fireServerError} from '../../../../utils/event-util';
+import {
+ fire,
+ fireNetworkError,
+ fireServerError,
+} from '../../../../utils/event-util';
import {FetchRequest} from '../../../../types/types';
import {ErrorCallback} from '../../../../api/rest';
import {Scheduler, Task} from '../../../../services/scheduler/scheduler';
@@ -337,13 +341,7 @@
elapsed,
anonymizedUrl: req.anonymizedUrl,
};
- document.dispatchEvent(
- new CustomEvent('gr-rpc-log', {
- detail,
- composed: true,
- bubbles: true,
- })
- );
+ fire(document, 'gr-rpc-log', detail);
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index 20a6463..782c055 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -12,7 +12,7 @@
import {
GrAutocompleteDropdown,
Item,
- ItemSelectedEvent,
+ ItemSelectedEventDetail,
} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
import {Key} from '../../../utils/dom-util';
import {ValueChangedEvent} from '../../../types/events';
@@ -67,7 +67,7 @@
declare global {
interface HTMLElementEventMap {
- 'item-selected': CustomEvent<ItemSelectedEvent>;
+ 'item-selected': CustomEvent<ItemSelectedEventDetail>;
}
}
@@ -375,7 +375,7 @@
}
// private but used in test
- handleDropdownItemSelect(e: CustomEvent<ItemSelectedEvent>) {
+ handleDropdownItemSelect(e: CustomEvent<ItemSelectedEventDetail>) {
if (e.detail.selected?.dataset['value']) {
this.setValue(e.detail.selected?.dataset['value']);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
index 711866a..154ea0a 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
@@ -6,7 +6,7 @@
import '../../../test/common-test-setup';
import './gr-textarea';
import {GrTextarea} from './gr-textarea';
-import {ItemSelectedEvent} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
+import {ItemSelectedEventDetail} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
import {pressKey, stubRestApi, waitUntil} from '../../../test/test-utils';
import {fixture, html, assert} from '@open-wc/testing';
import {createAccountWithEmail} from '../../../test/test-data-generators';
@@ -482,7 +482,7 @@
element.specialCharIndex = 10;
await element.updateComplete;
const selectedItem = {dataset: {value: '😂'}} as unknown as HTMLElement;
- const event = new CustomEvent<ItemSelectedEvent>('item-selected', {
+ const event = new CustomEvent<ItemSelectedEventDetail>('item-selected', {
detail: {trigger: 'click', selected: selectedItem},
});
element.handleDropdownItemSelect(event);
diff --git a/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts
new file mode 100644
index 0000000..c557acc
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts
@@ -0,0 +1,97 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import {css, html, LitElement, nothing} from 'lit';
+import {customElement} from 'lit/decorators.js';
+import {getAppContext} from '../../../services/app-context';
+import {KnownExperimentId} from '../../../services/flags/flags';
+import {fire} from '../../../utils/event-util';
+
+declare global {
+ interface HTMLElementEventMap {
+ 'open-user-suggest-preview': OpenUserSuggestionPreviewEvent;
+ }
+}
+
+export type OpenUserSuggestionPreviewEvent =
+ CustomEvent<OpenUserSuggestionPreviewEventDetail>;
+export interface OpenUserSuggestionPreviewEventDetail {
+ code: string;
+}
+
+@customElement('gr-user-suggestion-fix')
+export class GrUserSuggetionFix extends LitElement {
+ private readonly flagsService = getAppContext().flagsService;
+
+ static override styles = [
+ css`
+ .header {
+ background-color: var(--user-suggestion-header-background);
+ color: var(--user-suggestion-header-color);
+ border: 1px solid var(--border-color);
+ border-bottom: 0;
+ padding: var(--spacing-xs) var(--spacing-s);
+ display: flex;
+ align-items: center;
+ }
+ .header .title {
+ flex: 1;
+ }
+ gr-copy-clipboard {
+ --gr-copy-clipboard-icon-color: var(--user-suggestion-header-color);
+ }
+ code {
+ max-width: var(--gr-formatted-text-prose-max-width, none);
+ background-color: var(--background-color-secondary);
+ border: 1px solid var(--border-color);
+ border-top: 0;
+ display: block;
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-code);
+ line-height: var(--line-height-mono);
+ margin-bottom: var(--spacing-m);
+ padding: var(--spacing-xxs) var(--spacing-s);
+ overflow-x: auto;
+ /* Pre will preserve whitespace and line breaks but not wrap */
+ white-space: pre;
+ }
+ `,
+ ];
+
+ override render() {
+ if (!this.flagsService.isEnabled(KnownExperimentId.SUGGEST_EDIT)) {
+ return nothing;
+ }
+ if (!this.textContent) return nothing;
+ const code = this.textContent;
+ return html`<div class="header">
+ <div class="title">Suggested fix</div>
+ <div>
+ <gr-copy-clipboard hideInput="" text=${code}></gr-copy-clipboard>
+ </div>
+ <div>
+ <gr-button
+ secondary
+ class="action show-fix"
+ @click=${this.handleShowFix}
+ >
+ Preview Fix
+ </gr-button>
+ </div>
+ </div>
+ <code>${code}</code>`;
+ }
+
+ handleShowFix() {
+ if (!this.textContent) return;
+ fire(this, 'open-user-suggest-preview', {code: this.textContent});
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-user-suggestion-fix': GrUserSuggetionFix;
+ }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix_test.ts b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix_test.ts
new file mode 100644
index 0000000..80422a0
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix_test.ts
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../../../test/common-test-setup';
+import './gr-user-suggestion-fix';
+import {fixture, html, assert} from '@open-wc/testing';
+import {GrUserSuggetionFix} from './gr-user-suggestion-fix';
+import {getAppContext} from '../../../services/app-context';
+
+suite('gr-user-suggestion-fix tests', () => {
+ let element: GrUserSuggetionFix;
+
+ setup(async () => {
+ const flagsService = getAppContext().flagsService;
+ sinon.stub(flagsService, 'isEnabled').returns(true);
+ element = await fixture<GrUserSuggetionFix>(html`
+ <gr-user-suggestion-fix>Hello World</gr-user-suggestion-fix>
+ `);
+ await element.updateComplete;
+ });
+
+ test('render', async () => {
+ await element.updateComplete;
+
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `<div class="header">
+ <div class="title">Suggested fix</div>
+ <div>
+ <gr-copy-clipboard
+ hideinput=""
+ text="Hello World"
+ ></gr-copy-clipboard>
+ </div>
+ <div>
+ <gr-button class="action show-fix" secondary=""
+ >Preview Fix</gr-button
+ >
+ </div>
+ </div>
+ <code>Hello World</code>`
+ );
+ });
+});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts
index 5270603..b3f3714 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-legacy.ts
@@ -3,10 +3,7 @@
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {
- MovedLinkClickedEventDetail,
- RenderPreferences,
-} from '../../../api/diff';
+import {RenderPreferences} from '../../../api/diff';
import {fire} from '../../../utils/event-util';
import {GrDiffLine, GrDiffLineType, LineNumber} from '../gr-diff/gr-diff-line';
import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
@@ -430,16 +427,10 @@
anchor.setAttribute('href', `#${line}`);
anchor.addEventListener('click', e => {
e.preventDefault();
- anchor.dispatchEvent(
- new CustomEvent<MovedLinkClickedEventDetail>('moved-link-clicked', {
- detail: {
- lineNum: line,
- side,
- },
- composed: true,
- bubbles: true,
- })
- );
+ fire(anchor, 'moved-link-clicked', {
+ lineNum: line,
+ side,
+ });
});
return anchor;
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
index d40fdda..e5d3d2e 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
@@ -9,7 +9,6 @@
DiffInfo,
DiffLayer,
DiffViewMode,
- MovedLinkClickedEventDetail,
RenderPreferences,
Side,
LineNumber,
@@ -27,6 +26,7 @@
import '../gr-range-header/gr-range-header';
import './gr-diff-row';
import {when} from 'lit/directives/when.js';
+import {fire} from '../../../utils/event-util';
@customElement('gr-diff-section')
export class GrDiffSection extends LitElement {
@@ -235,16 +235,11 @@
side: Side,
line: number
) {
- anchor?.dispatchEvent(
- new CustomEvent<MovedLinkClickedEventDetail>('moved-link-clicked', {
- detail: {
- lineNum: line,
- side,
- },
- composed: true,
- bubbles: true,
- })
- );
+ if (!anchor) return;
+ fire(anchor, 'moved-link-clicked', {
+ lineNum: line,
+ side,
+ });
}
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
index 14bb17e..9e3640b 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
@@ -8,7 +8,8 @@
import {
DiffViewMode,
GrDiffCursor as GrDiffCursorApi,
- LineNumberEventDetail,
+ LineNumber,
+ LineSelectedEventDetail,
} from '../../../api/diff';
import {ScrollMode, Side} from '../../../constants/constants';
import {toggleClass} from '../../../utils/dom-util';
@@ -19,6 +20,7 @@
import {GrDiffLineType} from '../gr-diff/gr-diff-line';
import {GrDiffGroupType} from '../gr-diff/gr-diff-group';
import {GrDiff} from '../gr-diff/gr-diff';
+import {fire} from '../../../utils/event-util';
type GrDiffRowType = GrDiffLineType | GrDiffGroupType;
@@ -237,7 +239,7 @@
}
moveToLineNumber(
- number: number,
+ number: LineNumber,
side: Side,
path?: string,
intentionalMove?: boolean
@@ -352,13 +354,10 @@
this.preventAutoScrollOnManualScroll = false;
};
- private _boundHandleDiffLineSelected = (event: Event) => {
- const customEvent = event as CustomEvent;
- this.moveToLineNumber(
- customEvent.detail.number,
- customEvent.detail.side,
- customEvent.detail.path
- );
+ private _boundHandleDiffLineSelected = (
+ e: CustomEvent<LineSelectedEventDetail>
+ ) => {
+ this.moveToLineNumber(e.detail.number, e.detail.side, e.detail.path);
};
createCommentInPlace() {
@@ -485,16 +484,10 @@
const address = this.getAddressFor(row, side);
if (address) {
const {leftSide, number} = address;
- row.dispatchEvent(
- new CustomEvent<LineNumberEventDetail>(event, {
- detail: {
- lineNum: number,
- side: leftSide ? Side.LEFT : Side.RIGHT,
- },
- composed: true,
- bubbles: true,
- })
- );
+ fire(row, event, {
+ lineNum: number,
+ side: leftSide ? Side.LEFT : Side.RIGHT,
+ });
}
}
@@ -580,7 +573,7 @@
}
_findRowByNumberAndFile(
- targetNumber: number,
+ targetNumber: LineNumber,
side: Side,
path?: string
): HTMLElement | undefined {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
index 0714645..02f2233 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
@@ -20,6 +20,7 @@
} from '../gr-diff/gr-diff-utils';
import {debounce, DelayedTask} from '../../../utils/async-util';
import {assertIsDefined, queryAndAssert} from '../../../utils/common-util';
+import {fire} from '../../../utils/event-util';
interface SidedRange {
side: Side;
@@ -458,13 +459,9 @@
}
private fireCreateRangeComment(side: Side, range: CommentRange) {
- this.diffTable?.dispatchEvent(
- new CustomEvent('create-range-comment', {
- detail: {side, range},
- composed: true,
- bubbles: true,
- })
- );
+ if (this.diffTable) {
+ fire(this.diffTable, 'create-range-comment', {side, range});
+ }
this.removeActionBox();
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
index 8a92bcc..cb466c3 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -24,14 +24,10 @@
import {classMap} from 'lit/directives/class-map.js';
import {StyleInfo, styleMap} from 'lit/directives/style-map.js';
-import {
- createEvent,
- Dimensions,
- fitToFrame,
- FrameConstrainer,
- Point,
- Rect,
-} from './util';
+import {Dimensions, fitToFrame, FrameConstrainer, Point, Rect} from './util';
+import {ValueChangedEvent} from '../../../types/events';
+import {fireNoBubbleNoCompose} from '../../../utils/event-util';
+import {ImageDiffAction} from '../../../api/diff';
const DRAG_DEAD_ZONE_PIXELS = 5;
@@ -686,27 +682,25 @@
});
}
+ fireAction(detail: ImageDiffAction) {
+ fireNoBubbleNoCompose(this, 'image-diff-action', detail);
+ }
+
selectBase() {
if (!this.baseUrl) return;
this.baseSelected = true;
- this.dispatchEvent(
- createEvent({type: 'version-switcher-clicked', button: 'base'})
- );
+ this.fireAction({type: 'version-switcher-clicked', button: 'base'});
}
selectRevision() {
if (!this.revisionUrl) return;
this.baseSelected = false;
- this.dispatchEvent(
- createEvent({type: 'version-switcher-clicked', button: 'revision'})
- );
+ this.fireAction({type: 'version-switcher-clicked', button: 'revision'});
}
manualBlink() {
this.toggleImage();
- this.dispatchEvent(
- createEvent({type: 'version-switcher-clicked', button: 'switch'})
- );
+ this.fireAction({type: 'version-switcher-clicked', button: 'switch'});
}
private toggleImage() {
@@ -717,9 +711,10 @@
toggleAutomaticBlink() {
this.automaticBlink = !this.automaticBlink;
- this.dispatchEvent(
- createEvent({type: 'automatic-blink-changed', value: this.automaticBlink})
- );
+ this.fireAction({
+ type: 'automatic-blink-changed',
+ value: this.automaticBlink,
+ });
}
private updateAutomaticBlink() {
@@ -751,52 +746,42 @@
private toggleHighlight(source: 'controls' | 'magnifier') {
this.showHighlight = !this.showHighlight;
- this.dispatchEvent(
- createEvent({
- type: 'highlight-changes-changed',
- value: this.showHighlight,
- source,
- })
- );
+ this.fireAction({
+ type: 'highlight-changes-changed',
+ value: this.showHighlight,
+ source,
+ });
}
- zoomControlChanged(event: CustomEvent) {
+ zoomControlChanged(event: ValueChangedEvent<'fit' | number>) {
const value = event.detail.value;
if (!value) return;
if (value === 'fit') {
this.scaledSelected = true;
- this.dispatchEvent(
- createEvent({type: 'zoom-level-changed', scale: 'fit'})
- );
+ this.fireAction({type: 'zoom-level-changed', scale: 'fit'});
}
- if (value > 0) {
+ if (typeof value === 'number' && value > 0) {
this.scaledSelected = false;
this.scale = value;
- this.dispatchEvent(
- createEvent({type: 'zoom-level-changed', scale: value})
- );
+ this.fireAction({type: 'zoom-level-changed', scale: value});
}
this.updateSizes();
}
followMouseChanged() {
this.followMouse = !this.followMouse;
- this.dispatchEvent(
- createEvent({type: 'follow-mouse-changed', value: this.followMouse})
- );
+ this.fireAction({type: 'follow-mouse-changed', value: this.followMouse});
}
pickColor(value: string) {
this.checkerboardSelected = false;
this.backgroundColor = value;
- this.dispatchEvent(createEvent({type: 'background-color-changed', value}));
+ this.fireAction({type: 'background-color-changed', value});
}
pickCheckerboard() {
this.checkerboardSelected = true;
- this.dispatchEvent(
- createEvent({type: 'background-color-changed', value: 'checkerboard'})
- );
+ this.fireAction({type: 'background-color-changed', value: 'checkerboard'});
}
mousemoveImageArea(event: MouseEvent) {
@@ -849,9 +834,9 @@
// external mice.
if (distance < DRAG_DEAD_ZONE_PIXELS) {
this.toggleImage();
- this.dispatchEvent(createEvent({type: 'magnifier-clicked'}));
+ this.fireAction({type: 'magnifier-clicked'});
} else {
- this.dispatchEvent(createEvent({type: 'magnifier-dragged'}));
+ this.fireAction({type: 'magnifier-dragged'});
}
}
@@ -894,17 +879,17 @@
if (!this.ownsMouseDown) return;
this.grabbing = false;
this.ownsMouseDown = false;
- this.dispatchEvent(createEvent({type: 'magnifier-dragged'}));
+ this.fireAction({type: 'magnifier-dragged'});
}
dragstartMagnifier(event: DragEvent) {
event.preventDefault();
}
- onOverviewCenterUpdated(event: CustomEvent) {
+ onOverviewCenterUpdated(event: CustomEvent<Point>) {
this.frameConstrainer.requestCenter({
- x: event.detail.x as number,
- y: event.detail.y as number,
+ x: event.detail.x,
+ y: event.detail.y,
});
this.updateFrames();
}
@@ -955,4 +940,7 @@
interface HTMLElementTagNameMap {
'gr-image-viewer': GrImageViewer;
}
+ interface HTMLElementEventMap {
+ 'image-diff-action': CustomEvent<ImageDiffAction>;
+ }
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
index 1bc1447..21a7cf8 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
@@ -7,8 +7,9 @@
import {customElement, property, query, state} from 'lit/decorators.js';
import {StyleInfo, styleMap} from 'lit/directives/style-map.js';
import {ImageDiffAction} from '../../../api/diff';
+import {fire} from '../../../utils/event-util';
-import {createEvent, Dimensions, fitToFrame, Point, Rect} from './util';
+import {Dimensions, fitToFrame, Point, Rect} from './util';
/**
* Displays a scaled-down version of an image with a draggable frame for
@@ -243,7 +244,7 @@
const detail: ImageDiffAction = {
type: this.dragging ? 'overview-frame-dragged' : 'overview-image-clicked',
};
- this.dispatchEvent(createEvent(detail));
+ fire(this, 'image-diff-action', detail);
this.dragging = false;
this.closeOverlay();
@@ -297,13 +298,7 @@
}
private notifyNewCenter(center: Point) {
- this.dispatchEvent(
- new CustomEvent('center-updated', {
- detail: {...center},
- bubbles: true,
- composed: true,
- })
- );
+ fire(this, 'center-updated', {...center});
}
}
@@ -311,4 +306,7 @@
interface HTMLElementTagNameMap {
'gr-overview-image': GrOverviewImage;
}
+ interface HTMLElementEventMap {
+ 'center-updated': CustomEvent<Point>;
+ }
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/util.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/util.ts
index 38a07b7..896dc11 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/util.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/util.ts
@@ -3,7 +3,6 @@
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {ImageDiffAction} from '../../../api/diff';
export interface Point {
x: number;
@@ -224,13 +223,3 @@
};
}
}
-
-export function createEvent(
- detail: ImageDiffAction
-): CustomEvent<ImageDiffAction> {
- return new CustomEvent('image-diff-action', {
- detail,
- bubbles: true,
- composed: true,
- });
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index b9a01ce..0f4ab2e 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -29,7 +29,10 @@
} from './gr-diff-utils';
import {BlameInfo, CommentRange, ImageInfo} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {GrDiffHighlight} from '../gr-diff-highlight/gr-diff-highlight';
+import {
+ CreateRangeCommentEventDetail,
+ GrDiffHighlight,
+} from '../gr-diff-highlight/gr-diff-highlight';
import {
GrDiffBuilderElement,
getLineNumberCellWidth,
@@ -989,8 +992,10 @@
constructor() {
super();
provide(this, diffModelToken, () => this.diffModel);
- this.addEventListener('create-range-comment', (e: Event) =>
- this.handleCreateRangeComment(e as CustomEvent)
+ this.addEventListener(
+ 'create-range-comment',
+ (e: CustomEvent<CreateRangeCommentEventDetail>) =>
+ this.handleCreateRangeComment(e)
);
this.addEventListener('render-content', () => this.handleRenderContent());
this.addEventListener('moved-link-clicked', (e: MovedLinkClickedEvent) => {
@@ -1343,17 +1348,11 @@
}
private dispatchSelectedLine(number: LineNumber, side: Side) {
- this.dispatchEvent(
- new CustomEvent('line-selected', {
- detail: {
- number,
- side,
- path: this.path,
- },
- composed: true,
- bubbles: true,
- })
- );
+ fire(this, 'line-selected', {
+ number,
+ side,
+ path: this.path,
+ });
}
addDraftAtLine(el: Element) {
@@ -1386,7 +1385,9 @@
}
}
- private handleCreateRangeComment(e: CustomEvent) {
+ private handleCreateRangeComment(
+ e: CustomEvent<CreateRangeCommentEventDetail>
+ ) {
const range = e.detail.range;
const side = e.detail.side;
this.createCommentForSelection(side, range);
@@ -1403,18 +1404,12 @@
if (!contentEl) throw new Error('content el not found for line el');
side = side ?? this.getCommentSideByLineAndContent(lineEl, contentEl);
assertIsDefined(this.path, 'path');
- this.dispatchEvent(
- new CustomEvent<CreateCommentEventDetail>('create-comment', {
- bubbles: true,
- composed: true,
- detail: {
- path: this.path,
- side,
- lineNum,
- range,
- },
- })
- );
+ fire(this, 'create-comment', {
+ path: this.path,
+ side,
+ lineNum,
+ range,
+ });
}
private getCommentSideByLineAndContent(
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts
index 446822f..ad5b217 100644
--- a/polygerrit-ui/app/models/change/change-model.ts
+++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -26,6 +26,7 @@
import {
computeAllPatchSets,
computeLatestPatchNum,
+ computeLatestPatchNumWithEdit,
} from '../../utils/patch-set-util';
import {ParsedChangeInfo} from '../../types/types';
import {fireAlert} from '../../utils/event-util';
@@ -191,6 +192,10 @@
computeLatestPatchNum(patchsets)
);
+ public readonly latestPatchNumWithEdit$ = select(this.patchsets$, patchsets =>
+ computeLatestPatchNumWithEdit(patchsets)
+ );
+
/**
* Emits the current patchset number. If the route does not define the current
* patchset num, then this selector waits for the change to be defined and
@@ -203,7 +208,7 @@
combineLatest([
this.viewModel.state$,
this.state$,
- this.latestPatchNum$,
+ this.latestPatchNumWithEdit$,
]).pipe(
/**
* If you depend on both, view model and change state, then you want to
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index 153777f..d2a0dd8 100644
--- a/polygerrit-ui/app/models/views/change.ts
+++ b/polygerrit-ui/app/models/views/change.ts
@@ -79,6 +79,7 @@
/** These properties apply to the DIFF child view only. */
diffView?: {
path?: string;
+ // TODO: Use LineNumber as a type, i.e. accept FILE and LOST.
lineNum?: number;
leftSide?: boolean;
};
diff --git a/polygerrit-ui/app/services/gr-auth/gr-auth_mock.ts b/polygerrit-ui/app/services/gr-auth/gr-auth_mock.ts
index 480484e..37c2311 100644
--- a/polygerrit-ui/app/services/gr-auth/gr-auth_mock.ts
+++ b/polygerrit-ui/app/services/gr-auth/gr-auth_mock.ts
@@ -3,6 +3,7 @@
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
+import {fire} from '../../utils/event-util';
import {
AuthRequestInit,
AuthService,
@@ -28,14 +29,10 @@
private _setStatus(status: AuthStatus) {
if (this._status === status) return;
if (this._status === AuthStatus.AUTHED) {
- document.dispatchEvent(
- new CustomEvent('auth-error', {
- detail: {
- message: Auth.CREDS_EXPIRED_MSG,
- action: 'Refresh credentials',
- },
- })
- );
+ fire(document, 'auth-error', {
+ message: Auth.CREDS_EXPIRED_MSG,
+ action: 'Refresh credentials',
+ });
}
this._status = status;
}
diff --git a/polygerrit-ui/app/styles/themes/app-theme.ts b/polygerrit-ui/app/styles/themes/app-theme.ts
index 107ee16..0503e4c 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.ts
+++ b/polygerrit-ui/app/styles/themes/app-theme.ts
@@ -278,6 +278,11 @@
--robot-comment-background-color: var(--blue-50);
--unresolved-comment-background-color: #fef7e0;
+
+ /* Suggest edits */
+ --user-suggestion-header-background: var(--gray-700);
+ --user-suggestion-header-color: white;
+
/* vote background colors */
--vote-color-approved: var(--green-300);
--vote-color-disliked: var(--red-50);
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.ts b/polygerrit-ui/app/styles/themes/dark-theme.ts
index a183c86..dc3d4e9 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.ts
+++ b/polygerrit-ui/app/styles/themes/dark-theme.ts
@@ -138,6 +138,10 @@
--robot-comment-background-color: #1e3a5f;
--unresolved-comment-background-color: #614a19;
+ /* Suggest edits */
+ --user-suggestion-header-background: var(--gray-700);
+ --user-suggestion-header-color: white;
+
/* vote background colors */
--vote-color-approved: var(--green-300);
--vote-color-disliked: var(--red-tonal);
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index e2612b0..c28aade 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -3,17 +3,18 @@
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {FixSuggestionInfo, PatchSetNum} from './common';
+import {AccountInfo, FixSuggestionInfo, PatchSetNum} from './common';
import {ChangeMessage} from '../utils/comment-util';
import {FetchRequest} from './types';
import {LineNumberEventDetail, MovedLinkClickedEventDetail} from '../api/diff';
import {Category, RunStatus} from '../api/checks';
+import {DropdownLink} from '../elements/shared/gr-dropdown/gr-dropdown';
+import {AutocompleteCommitEvent} from '../elements/shared/gr-autocomplete/gr-autocomplete';
export enum EventType {
BIND_VALUE_CHANGED = 'bind-value-changed',
CHANGE = 'change',
CHANGED = 'changed',
- CHANGE_MESSAGE_DELETED = 'change-message-deleted',
COMMIT = 'commit',
DIALOG_CHANGE = 'dialog-change',
DROP = 'drop',
@@ -41,15 +42,14 @@
declare global {
interface HTMLElementEventMap {
- /* prettier-ignore */
+ 'add-reviewer': AddReviewerEvent;
'bind-value-changed': BindValueChangeEvent;
/* prettier-ignore */
'change': ChangeEvent;
/* prettier-ignore */
'changed': ChangedEvent;
- 'change-message-deleted': ChangeMessageDeletedEvent;
/* prettier-ignore */
- 'commit': CommitEvent;
+ 'commit': AutocompleteCommitEvent;
'dialog-change': DialogChangeEvent;
/* prettier-ignore */
'drop': DropEvent;
@@ -65,8 +65,7 @@
'reply-to-comment': ReplyToCommentEvent;
/* prettier-ignore */
'reload': ReloadEvent;
- /* prettier-ignore */
- 'reply': ReplyEvent;
+ 'remove-reviewer': RemoveReviewerEvent;
'show-alert': ShowAlertEvent;
'show-error': ShowErrorEvent;
'show-tab': SwitchTabEvent;
@@ -90,6 +89,21 @@
}
}
+export interface AddAccountEventDetail {
+ value: string;
+}
+export type AddAccountEvent = CustomEvent<AddAccountEventDetail>;
+
+export interface AddReviewerEventDetail {
+ reviewer: AccountInfo;
+}
+export type AddReviewerEvent = CustomEvent<AddReviewerEventDetail>;
+
+export interface RemoveReviewerEventDetail {
+ reviewer: AccountInfo;
+}
+export type RemoveReviewerEvent = CustomEvent<RemoveReviewerEventDetail>;
+
export interface BindValueChangeEventDetail {
value: string | undefined;
}
@@ -97,7 +111,8 @@
export type ChangeEvent = InputEvent;
-export type ChangedEvent = CustomEvent<string>;
+// TODO: This event seems to be unused (no listener). Remove?
+export type ChangedEvent = CustomEvent<string | undefined>;
export interface ChangeMessageDeletedEventDetail {
message: ChangeMessage;
@@ -105,8 +120,6 @@
export type ChangeMessageDeletedEvent =
CustomEvent<ChangeMessageDeletedEventDetail>;
-export type CommitEvent = CustomEvent;
-
// TODO(milutin) - remove once new gr-dialog will do it out of the box
// This informs gr-app-element to remove footer, header from a11y tree
export interface DialogChangeEventDetail {
@@ -123,6 +136,13 @@
export type EditableContentSaveEvent =
CustomEvent<EditableContentSaveEventDetail>;
+export interface FileActionTapEventDetail {
+ path: string;
+ action: string;
+}
+
+export type FileActionTapEvent = CustomEvent<FileActionTapEventDetail>;
+
export interface RpcLogEventDetail {
status: number | null;
method: string;
@@ -163,6 +183,7 @@
userWantsToEdit: boolean;
unresolved: boolean;
}
+
export type ReplyToCommentEvent = CustomEvent<ReplyToCommentEventDetail>;
export interface PageErrorEventDetail {
@@ -175,6 +196,11 @@
}
export type ReloadEvent = CustomEvent<ReloadEventDetail>;
+export interface RemoveAccountEventDetail {
+ account: AccountInfo;
+}
+export type RemoveAccountEvent = CustomEvent<RemoveAccountEventDetail>;
+
export interface ReplyEventDetail {
message: ChangeMessage;
}
@@ -200,6 +226,14 @@
}
export type ShowErrorEvent = CustomEvent<ShowErrorEventDetail>;
+export interface ShowReplyDialogEventDetail {
+ value: {
+ reviewersOnly: boolean;
+ ccsOnly: boolean;
+ };
+}
+export type ShowReplyDialogEvent = CustomEvent<ShowReplyDialogEventDetail>;
+
export interface AuthErrorEventDetail {
message: string;
action: string;
@@ -231,7 +265,7 @@
}
export type SwitchTabEvent = CustomEvent<SwitchTabEventDetail>;
-export type TapItemEvent = CustomEvent;
+export type TapItemEvent = CustomEvent<DropdownLink>;
export interface TitleChangeEventDetail {
title: string;
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index a92f0f8..34a90de 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -523,7 +523,8 @@
};
}
-export const USER_SUGGESTION_START_PATTERN = '```suggestion\n';
+export const USER_SUGGESTION_INFO_STRING = 'suggestion';
+export const USER_SUGGESTION_START_PATTERN = `\`\`\`${USER_SUGGESTION_INFO_STRING}\n`;
// This can either mean a user or a checks provided fix.
// "Provided" means that the fix is sent along with the request
diff --git a/polygerrit-ui/app/utils/event-util.ts b/polygerrit-ui/app/utils/event-util.ts
index 49d5382..d45ef55 100644
--- a/polygerrit-ui/app/utils/event-util.ts
+++ b/polygerrit-ui/app/utils/event-util.ts
@@ -20,6 +20,24 @@
);
}
+export function fireEventNoBubble(target: EventTarget, type: string) {
+ target.dispatchEvent(
+ new CustomEvent(type, {
+ composed: true,
+ bubbles: false,
+ })
+ );
+}
+
+export function fireEventNoBubbleNoCompose(target: EventTarget, type: string) {
+ target.dispatchEvent(
+ new CustomEvent(type, {
+ composed: false,
+ bubbles: false,
+ })
+ );
+}
+
export type HTMLElementEventDetailType<K extends keyof HTMLElementEventMap> =
HTMLElementEventMap[K] extends CustomEvent<infer DT>
? unknown extends DT
@@ -56,10 +74,42 @@
);
}
+export function fireNoBubble<K extends keyof HTMLElementEventMap, T>(
+ target: EventTarget,
+ type: K,
+ detail: T
+) {
+ target.dispatchEvent(
+ new CustomEvent<T>(type, {
+ detail,
+ composed: true,
+ bubbles: false,
+ })
+ );
+}
+
+export function fireNoBubbleNoCompose<K extends keyof HTMLElementEventMap, T>(
+ target: EventTarget,
+ type: K,
+ detail: T
+) {
+ target.dispatchEvent(
+ new CustomEvent<T>(type, {
+ detail,
+ composed: false,
+ bubbles: false,
+ })
+ );
+}
+
export function fireAlert(target: EventTarget, message: string) {
fire(target, EventType.SHOW_ALERT, {message, showDismiss: true});
}
+export function fireError(target: EventTarget, message: string) {
+ fire(target, EventType.SHOW_ERROR, {message});
+}
+
export function firePageError(response?: Response | null) {
if (response === null) response = undefined;
fire(document, EventType.PAGE_ERROR, {response});
diff --git a/polygerrit-ui/app/utils/patch-set-util.ts b/polygerrit-ui/app/utils/patch-set-util.ts
index 183671f..7e16ad9 100644
--- a/polygerrit-ui/app/utils/patch-set-util.ts
+++ b/polygerrit-ui/app/utils/patch-set-util.ts
@@ -265,6 +265,16 @@
return latest;
}
+// Basically is computeLatestPatchNum but allows "edits".
+export function computeLatestPatchNumWithEdit(
+ allPatchSets?: PatchSet[]
+): RevisionPatchSetNum | undefined {
+ if (!allPatchSets || !allPatchSets.length) {
+ return undefined;
+ }
+ return allPatchSets[0].num;
+}
+
export function computePredecessor(
patchset?: PatchSetNum
): BasePatchSetNum | undefined {