Merge "Do not process keyboard shortcuts for invisible views"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index e0bed37..ff12af2 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -6921,7 +6921,10 @@
The subject of the commit (header line of the commit message).
|`message` ||The commit message.
|`web_links` |optional|
-Links to the commit in external sites as a list of
+Links to the patch set in external sites as a list of
+link:#web-link-info[WebLinkInfo] entities.
+|`resolve_conflicts_web_links` |optional|
+Links to the commit in external sites for resolving conflicts as a list of
link:#web-link-info[WebLinkInfo] entities.
|===========================
diff --git a/java/com/google/gerrit/acceptance/ExtensionRegistry.java b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
index 6c6bab0..85c4c13 100644
--- a/java/com/google/gerrit/acceptance/ExtensionRegistry.java
+++ b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
@@ -35,6 +35,7 @@
import com.google.gerrit.extensions.webui.EditWebLink;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
+import com.google.gerrit.extensions.webui.ResolveConflictsWebLink;
import com.google.gerrit.server.ExceptionHook;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.change.ChangeETagComputation;
@@ -76,6 +77,7 @@
private final DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners;
private final DynamicSet<FileHistoryWebLink> fileHistoryWebLinks;
private final DynamicSet<PatchSetWebLink> patchSetWebLinks;
+ private final DynamicSet<ResolveConflictsWebLink> resolveConflictsWebLinks;
private final DynamicSet<EditWebLink> editWebLinks;
private final DynamicSet<RevisionCreatedListener> revisionCreatedListeners;
private final DynamicSet<GroupBackend> groupBackends;
@@ -111,6 +113,7 @@
DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners,
DynamicSet<FileHistoryWebLink> fileHistoryWebLinks,
DynamicSet<PatchSetWebLink> patchSetWebLinks,
+ DynamicSet<ResolveConflictsWebLink> resolveConflictsWebLinks,
DynamicSet<EditWebLink> editWebLinks,
DynamicSet<RevisionCreatedListener> revisionCreatedListeners,
DynamicSet<GroupBackend> groupBackends,
@@ -143,6 +146,7 @@
this.fileHistoryWebLinks = fileHistoryWebLinks;
this.patchSetWebLinks = patchSetWebLinks;
this.editWebLinks = editWebLinks;
+ this.resolveConflictsWebLinks = resolveConflictsWebLinks;
this.revisionCreatedListeners = revisionCreatedListeners;
this.groupBackends = groupBackends;
this.accountActivationValidationListeners = accountActivationValidationListeners;
@@ -244,6 +248,10 @@
return add(patchSetWebLinks, patchSetWebLink);
}
+ public Registration add(ResolveConflictsWebLink resolveConflictsWebLink) {
+ return add(resolveConflictsWebLinks, resolveConflictsWebLink);
+ }
+
public Registration add(EditWebLink editWebLink) {
return add(editWebLinks, editWebLink);
}
diff --git a/java/com/google/gerrit/extensions/common/CommitInfo.java b/java/com/google/gerrit/extensions/common/CommitInfo.java
index 1fd8755..202b829 100644
--- a/java/com/google/gerrit/extensions/common/CommitInfo.java
+++ b/java/com/google/gerrit/extensions/common/CommitInfo.java
@@ -29,6 +29,7 @@
public String subject;
public String message;
public List<WebLinkInfo> webLinks;
+ public List<WebLinkInfo> resolveConflictsWebLinks;
@Override
public boolean equals(Object o) {
@@ -42,12 +43,14 @@
&& Objects.equals(committer, c.committer)
&& Objects.equals(subject, c.subject)
&& Objects.equals(message, c.message)
- && Objects.equals(webLinks, c.webLinks);
+ && Objects.equals(webLinks, c.webLinks)
+ && Objects.equals(resolveConflictsWebLinks, c.resolveConflictsWebLinks);
}
@Override
public int hashCode() {
- return Objects.hash(commit, parents, author, committer, subject, message, webLinks);
+ return Objects.hash(
+ commit, parents, author, committer, subject, message, webLinks, resolveConflictsWebLinks);
}
@Override
@@ -64,6 +67,9 @@
if (webLinks != null) {
helper.add("webLinks", webLinks);
}
+ if (resolveConflictsWebLinks != null) {
+ helper.add("resolveConflictsWebLinks", resolveConflictsWebLinks);
+ }
return helper.toString();
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/CommitInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/CommitInfoSubject.java
index d344e18..71fc564 100644
--- a/java/com/google/gerrit/extensions/common/testing/CommitInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/CommitInfoSubject.java
@@ -20,6 +20,7 @@
import com.google.common.truth.Correspondence;
import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IterableSubject;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
import com.google.gerrit.extensions.common.CommitInfo;
@@ -68,6 +69,16 @@
return check("message").that(commitInfo.message);
}
+ public IterableSubject webLinks() {
+ isNotNull();
+ return check("webLinks").that(commitInfo.webLinks);
+ }
+
+ public IterableSubject resolveConflictsWebLinks() {
+ isNotNull();
+ return check("resolveConflictsWebLinks").that(commitInfo.resolveConflictsWebLinks);
+ }
+
public static Correspondence<CommitInfo, String> hasCommit() {
return NullAwareCorrespondence.transforming(commitInfo -> commitInfo.commit, "hasCommit");
}
diff --git a/java/com/google/gerrit/extensions/webui/ResolveConflictsWebLink.java b/java/com/google/gerrit/extensions/webui/ResolveConflictsWebLink.java
new file mode 100644
index 0000000..19402a9
--- /dev/null
+++ b/java/com/google/gerrit/extensions/webui/ResolveConflictsWebLink.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.webui;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.WebLinkInfo;
+
+@ExtensionPoint
+public interface ResolveConflictsWebLink extends WebLink {
+
+ /**
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo} describing a link from a patch set to
+ * an external service for the purpose of resolving merge conflicts.
+ *
+ * <p>In order for the web link to be visible {@link
+ * com.google.gerrit.extensions.common.WebLinkInfo#url} and {@link
+ * com.google.gerrit.extensions.common.WebLinkInfo#name} must be set.
+ *
+ * @param projectName name of the project
+ * @param commit commit of the patch set
+ * @param commitMessage the commit message of the change
+ * @param branchName target branch of the change
+ * @return WebLinkInfo that links to patch set in external service, {@code null} if there should
+ * be no link.
+ */
+ WebLinkInfo getResolveConflictsWebLink(
+ String projectName, String commit, String commitMessage, String branchName);
+}
diff --git a/java/com/google/gerrit/server/WebLinks.java b/java/com/google/gerrit/server/WebLinks.java
index 3b626ea..4acef06 100644
--- a/java/com/google/gerrit/server/WebLinks.java
+++ b/java/com/google/gerrit/server/WebLinks.java
@@ -33,6 +33,7 @@
import com.google.gerrit.extensions.webui.ParentWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
+import com.google.gerrit.extensions.webui.ResolveConflictsWebLink;
import com.google.gerrit.extensions.webui.TagWebLink;
import com.google.gerrit.extensions.webui.WebLink;
import com.google.inject.Inject;
@@ -56,6 +57,7 @@
};
private final DynamicSet<PatchSetWebLink> patchSetLinks;
+ private final DynamicSet<ResolveConflictsWebLink> resolveConflictsLinks;
private final DynamicSet<ParentWebLink> parentLinks;
private final DynamicSet<EditWebLink> editLinks;
private final DynamicSet<FileWebLink> fileLinks;
@@ -68,6 +70,7 @@
@Inject
public WebLinks(
DynamicSet<PatchSetWebLink> patchSetLinks,
+ DynamicSet<ResolveConflictsWebLink> resolveConflictsLinks,
DynamicSet<ParentWebLink> parentLinks,
DynamicSet<EditWebLink> editLinks,
DynamicSet<FileWebLink> fileLinks,
@@ -77,6 +80,7 @@
DynamicSet<BranchWebLink> branchLinks,
DynamicSet<TagWebLink> tagLinks) {
this.patchSetLinks = patchSetLinks;
+ this.resolveConflictsLinks = resolveConflictsLinks;
this.parentLinks = parentLinks;
this.editLinks = editLinks;
this.fileLinks = fileLinks;
@@ -103,6 +107,21 @@
/**
* @param project Project name.
+ * @param revision SHA1 of commit.
+ * @param commitMessage the commit message of the commit.
+ * @param branchName branch of the commit.
+ * @return Links for resolving comflicts.
+ */
+ public ImmutableList<WebLinkInfo> getResolveConflictsLinks(
+ Project.NameKey project, String commit, String commitMessage, String branchName) {
+ return filterLinks(
+ resolveConflictsLinks,
+ webLink ->
+ webLink.getResolveConflictsWebLink(project.get(), commit, commitMessage, branchName));
+ }
+
+ /**
+ * @param project Project name.
* @param revision SHA1 of the parent revision.
* @param commitMessage the commit message of the parent revision.
* @param branchName branch of the revision (and parent revision).
diff --git a/java/com/google/gerrit/server/change/RevisionJson.java b/java/com/google/gerrit/server/change/RevisionJson.java
index b702440..33f3d4f 100644
--- a/java/com/google/gerrit/server/change/RevisionJson.java
+++ b/java/com/google/gerrit/server/change/RevisionJson.java
@@ -182,9 +182,14 @@
info.message = commit.getFullMessage();
if (addLinks) {
- ImmutableList<WebLinkInfo> links =
+ ImmutableList<WebLinkInfo> patchSetLinks =
webLinks.getPatchSetLinks(project, commit.name(), commit.getFullMessage(), branchName);
- info.webLinks = links.isEmpty() ? null : links;
+ info.webLinks = patchSetLinks.isEmpty() ? null : patchSetLinks;
+ ImmutableList<WebLinkInfo> resolveConflictsLinks =
+ webLinks.getResolveConflictsLinks(
+ project, commit.name(), commit.getFullMessage(), branchName);
+ info.resolveConflictsWebLinks =
+ resolveConflictsLinks.isEmpty() ? null : resolveConflictsLinks;
}
for (RevCommit parent : commit.getParents()) {
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 339b350..4794858 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -72,6 +72,7 @@
import com.google.gerrit.extensions.webui.ParentWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
+import com.google.gerrit.extensions.webui.ResolveConflictsWebLink;
import com.google.gerrit.extensions.webui.TagWebLink;
import com.google.gerrit.extensions.webui.TopMenu;
import com.google.gerrit.extensions.webui.WebUiPlugin;
@@ -392,6 +393,7 @@
DynamicMap.mapOf(binder(), ProjectConfigEntry.class);
DynamicSet.setOf(binder(), PluginPushOption.class);
DynamicSet.setOf(binder(), PatchSetWebLink.class);
+ DynamicSet.setOf(binder(), ResolveConflictsWebLink.class);
DynamicSet.setOf(binder(), ParentWebLink.class);
DynamicSet.setOf(binder(), FileWebLink.class);
DynamicSet.setOf(binder(), FileHistoryWebLink.class);
diff --git a/java/com/google/gerrit/server/config/GitwebConfig.java b/java/com/google/gerrit/server/config/GitwebConfig.java
index 97cc830..f90a72e 100644
--- a/java/com/google/gerrit/server/config/GitwebConfig.java
+++ b/java/com/google/gerrit/server/config/GitwebConfig.java
@@ -33,6 +33,7 @@
import com.google.gerrit.extensions.webui.ParentWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
+import com.google.gerrit.extensions.webui.ResolveConflictsWebLink;
import com.google.gerrit.extensions.webui.TagWebLink;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
@@ -83,6 +84,7 @@
if (!isNullOrEmpty(type.getRevision())) {
DynamicSet.bind(binder(), PatchSetWebLink.class).to(GitwebLinks.class);
DynamicSet.bind(binder(), ParentWebLink.class).to(GitwebLinks.class);
+ DynamicSet.bind(binder(), ResolveConflictsWebLink.class).to(GitwebLinks.class);
}
if (!isNullOrEmpty(type.getProject())) {
@@ -261,6 +263,7 @@
PatchSetWebLink,
ParentWebLink,
ProjectWebLink,
+ ResolveConflictsWebLink,
TagWebLink {
private final String url;
private final GitwebType type;
@@ -350,6 +353,13 @@
}
@Override
+ public WebLinkInfo getResolveConflictsWebLink(
+ String projectName, String commit, String commitMessage, String branchName) {
+ // For Gitweb treat resolve conflicts links the same as patch set links
+ return getPatchSetWebLink(projectName, commit, commitMessage, branchName);
+ }
+
+ @Override
public WebLinkInfo getParentWebLink(
String projectName, String commit, String commitMessage, String branchName) {
// For Gitweb treat parent revision links the same as patch set links
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 86385da..9d0b1f4 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -93,6 +93,7 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
+import com.google.gerrit.extensions.webui.ResolveConflictsWebLink;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.testing.FakeEmailSender;
@@ -1633,16 +1634,26 @@
@Test
public void commit() throws Exception {
- WebLinkInfo expectedWebLinkInfo = new WebLinkInfo("foo", "imageUrl", "url");
- PatchSetWebLink link =
+ WebLinkInfo expectedPatchSetLinkInfo = new WebLinkInfo("foo", "imageUrl", "url");
+ PatchSetWebLink patchSetLink =
new PatchSetWebLink() {
@Override
public WebLinkInfo getPatchSetWebLink(
String projectName, String commit, String commitMessage, String branchName) {
- return expectedWebLinkInfo;
+ return expectedPatchSetLinkInfo;
}
};
- try (Registration registration = extensionRegistry.newRegistration().add(link)) {
+ WebLinkInfo expectedResolveConflictsLinkInfo = new WebLinkInfo("bar", "img", "resolve");
+ ResolveConflictsWebLink resolveConflictsLink =
+ new ResolveConflictsWebLink() {
+ @Override
+ public WebLinkInfo getResolveConflictsWebLink(
+ String projectName, String commit, String commitMessage, String branchName) {
+ return expectedResolveConflictsLinkInfo;
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(patchSetLink).add(resolveConflictsLink)) {
PushOneCommit.Result r = createChange();
RevCommit c = r.getCommit();
@@ -1659,11 +1670,20 @@
commitInfo = gApi.changes().id(r.getChangeId()).current().commit(true);
assertThat(commitInfo.webLinks).hasSize(1);
- WebLinkInfo webLinkInfo = Iterables.getOnlyElement(commitInfo.webLinks);
- assertThat(webLinkInfo.name).isEqualTo(expectedWebLinkInfo.name);
- assertThat(webLinkInfo.imageUrl).isEqualTo(expectedWebLinkInfo.imageUrl);
- assertThat(webLinkInfo.url).isEqualTo(expectedWebLinkInfo.url);
- assertThat(webLinkInfo.target).isEqualTo(expectedWebLinkInfo.target);
+ WebLinkInfo patchSetLinkInfo = Iterables.getOnlyElement(commitInfo.webLinks);
+ assertThat(patchSetLinkInfo.name).isEqualTo(expectedPatchSetLinkInfo.name);
+ assertThat(patchSetLinkInfo.imageUrl).isEqualTo(expectedPatchSetLinkInfo.imageUrl);
+ assertThat(patchSetLinkInfo.url).isEqualTo(expectedPatchSetLinkInfo.url);
+ assertThat(patchSetLinkInfo.target).isEqualTo(expectedPatchSetLinkInfo.target);
+
+ assertThat(commitInfo.resolveConflictsWebLinks).hasSize(1);
+ WebLinkInfo resolveCommentsLinkInfo =
+ Iterables.getOnlyElement(commitInfo.resolveConflictsWebLinks);
+ assertThat(resolveCommentsLinkInfo.name).isEqualTo(expectedResolveConflictsLinkInfo.name);
+ assertThat(resolveCommentsLinkInfo.imageUrl)
+ .isEqualTo(expectedResolveConflictsLinkInfo.imageUrl);
+ assertThat(resolveCommentsLinkInfo.url).isEqualTo(expectedResolveConflictsLinkInfo.url);
+ assertThat(resolveCommentsLinkInfo.target).isEqualTo(expectedResolveConflictsLinkInfo.target);
}
}
diff --git a/polygerrit-ui/app/api/checks.ts b/polygerrit-ui/app/api/checks.ts
index 62b9ad9..5d20f3f 100644
--- a/polygerrit-ui/app/api/checks.ts
+++ b/polygerrit-ui/app/api/checks.ts
@@ -298,6 +298,11 @@
externalId?: string;
/**
+ * SUCCESS: Indicates that some build, test or check is passing. A COMPLETED
+ * run without results will also be treated as "passing" and will get
+ * an artificial SUCCESS result. But you can also make this explicit,
+ * which also allows one run to have multiple "passing" results,
+ * maybe along with results of other categories.
* INFO: The user will typically not bother to look into this category,
* only for looking up something that they are searching for. Can be
* used for reporting secondary metrics and analysis, or a wider
@@ -383,6 +388,7 @@
}
export enum Category {
+ SUCCESS = 'SUCCESS',
INFO = 'INFO',
WARNING = 'WARNING',
ERROR = 'ERROR',
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js
index a4dd4bc..b51eb5f 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js
@@ -203,17 +203,17 @@
});
function checkComputeAttention(status, userId, reviewerIds, ownerId,
- attSetIds, replyToIds, expectedIds, uploaderId, hasDraft,
+ attSetIds, replyToIds, expectedIds, uploaderId, hasDraft = true,
includeComments = true) {
const user = {_account_id: userId};
const reviewers = {base: reviewerIds.map(id => {
return {_account_id: id};
})};
- const draftThreads = [
- {comments: []},
- ];
+ let draftThreads = [];
if (hasDraft) {
- draftThreads[0].comments.push({__draft: true, unresolved: true});
+ draftThreads = [
+ {comments: [{__draft: true, unresolved: true}]},
+ ];
}
replyToIds.forEach(id => draftThreads[0].comments.push({
author: {_account_id: id},
@@ -240,7 +240,6 @@
}
test('computeNewAttention NEW', () => {
- checkComputeAttention('NEW', null, [], 999, [], [], [999]);
checkComputeAttention('NEW', 1, [], 999, [], [], [999]);
checkComputeAttention('NEW', 1, [], 999, [1], [], [999]);
checkComputeAttention('NEW', 1, [22], 999, [], [], [999]);
@@ -264,14 +263,14 @@
});
test('computeNewAttention MERGED', () => {
- checkComputeAttention('MERGED', null, [], 999, [], [], []);
- checkComputeAttention('MERGED', 1, [], 999, [], [], []);
- checkComputeAttention('MERGED', 1, [], 999, [], [], [999], undefined, true);
+ checkComputeAttention('MERGED', 1, [], 999, [], [], [], undefined, false);
+ checkComputeAttention('MERGED', 1, [], 999, [], [], [999], undefined);
checkComputeAttention(
'MERGED', 1, [], 999, [], [], [], undefined, true, false);
- checkComputeAttention('MERGED', 1, [], 999, [1], [], []);
- checkComputeAttention('MERGED', 1, [22], 999, [], [], []);
- checkComputeAttention('MERGED', 1, [22], 999, [22], [], [22]);
+ checkComputeAttention('MERGED', 1, [], 999, [1], [], [], undefined, false);
+ checkComputeAttention('MERGED', 1, [22], 999, [], [], [], undefined, false);
+ checkComputeAttention('MERGED', 1, [22], 999, [22], [], [22], undefined,
+ false);
checkComputeAttention('MERGED', 1, [22], 999, [], [22], []);
checkComputeAttention('MERGED', 1, [22, 33], 999, [33], [22], [33]);
checkComputeAttention('MERGED', 1, [], 1, [], [], []);
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 49f51e02..1c60161 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -39,17 +39,15 @@
import {sharedStyles} from '../../styles/shared-styles';
import {
allActions$,
- checksPatchsetNumber$,
- someProvidersAreLoading$,
- RunResult,
- CheckRun,
allLinks$,
+ CheckRun,
+ checksPatchsetNumber$,
+ RunResult,
+ someProvidersAreLoading$,
} from '../../services/checks/checks-model';
import {
allResults,
fireActionTriggered,
- hasCompletedWithoutResults,
- hasResultsOf,
iconForCategory,
iconForLink,
tooltipForLink,
@@ -462,11 +460,11 @@
}
}
-const SHOW_ALL_THRESHOLDS: Map<Category | 'SUCCESS', number> = new Map();
+const SHOW_ALL_THRESHOLDS: Map<Category, number> = new Map();
SHOW_ALL_THRESHOLDS.set(Category.ERROR, 20);
SHOW_ALL_THRESHOLDS.set(Category.WARNING, 10);
SHOW_ALL_THRESHOLDS.set(Category.INFO, 5);
-SHOW_ALL_THRESHOLDS.set('SUCCESS', 5);
+SHOW_ALL_THRESHOLDS.set(Category.SUCCESS, 5);
@customElement('gr-checks-results')
export class GrChecksResults extends GrLitElement {
@@ -514,21 +512,21 @@
/** Maintains the state of which result sections should show all results. */
@internalProperty()
- isShowAll: Map<Category | 'SUCCESS', boolean> = new Map();
+ isShowAll: Map<Category, boolean> = new Map();
/**
* This is the current state of whether a section is expanded or not. As long
* as isSectionExpandedByUser is false this will be computed by a default rule
* on every render.
*/
- private isSectionExpanded = new Map<Category | 'SUCCESS', boolean>();
+ private isSectionExpanded = new Map<Category, boolean>();
/**
* Keeps track of whether the user intentionally changed the expansion state.
* Once this is true the default rule for showing a section expanded or not
* is not applied anymore.
*/
- private isSectionExpandedByUser = new Map<Category | 'SUCCESS', boolean>();
+ private isSectionExpandedByUser = new Map<Category, boolean>();
private readonly checksService = appContext.checksService;
@@ -732,7 +730,8 @@
<div class="body">
${this.renderSection(Category.ERROR)}
${this.renderSection(Category.WARNING)}
- ${this.renderSection(Category.INFO)} ${this.renderSection('SUCCESS')}
+ ${this.renderSection(Category.INFO)}
+ ${this.renderSection(Category.SUCCESS)}
</div>
`;
}
@@ -859,16 +858,11 @@
this.filterRegExp = new RegExp(this.filterInput.value, 'i');
}
- renderSection(category: Category | 'SUCCESS') {
+ renderSection(category: Category) {
const catString = category.toString().toLowerCase();
- let allRuns = this.runs.filter(run =>
+ const allRuns = this.runs.filter(run =>
isAttemptSelected(this.selectedAttempts, run)
);
- if (category === 'SUCCESS') {
- allRuns = allRuns.filter(hasCompletedWithoutResults);
- } else {
- allRuns = allRuns.filter(r => hasResultsOf(r, category));
- }
const all = allRuns.reduce(
(results: RunResult[], run) => [
...results,
@@ -928,7 +922,7 @@
}
renderShowAllButton(
- category: Category | 'SUCCESS',
+ category: Category,
isShowAll: boolean,
showAllThreshold: number,
resultCount: number
@@ -947,7 +941,7 @@
`;
}
- toggleShowAll(category: Category | 'SUCCESS') {
+ toggleShowAll(category: Category) {
const current = this.isShowAll.get(category) ?? false;
this.isShowAll.set(category, !current);
this.requestUpdate();
@@ -1011,7 +1005,7 @@
return html`(${filtered.length} of ${all.length})`;
}
- toggleExpanded(category: Category | 'SUCCESS') {
+ toggleExpanded(category: Category) {
const expanded = this.isSectionExpanded.get(category);
assertIsDefined(expanded, 'expanded must have been set in initial render');
this.isSectionExpanded.set(category, !expanded);
@@ -1019,8 +1013,11 @@
this.requestUpdate();
}
- computeRunResults(category: Category | 'SUCCESS', run: CheckRun) {
- if (category === 'SUCCESS') return [this.computeSuccessfulRunResult(run)];
+ computeRunResults(category: Category, run: CheckRun) {
+ const noResults = (run.results ?? []).length === 0;
+ if (noResults && category === Category.SUCCESS) {
+ return [this.computeSuccessfulRunResult(run)];
+ }
return (
run.results
?.filter(result => result.category === category)
@@ -1033,7 +1030,7 @@
computeSuccessfulRunResult(run: CheckRun): RunResult {
const adaptedRun: RunResult = {
internalResultId: run.internalRunId + '-0',
- category: Category.INFO, // will not be used, but is required
+ category: Category.SUCCESS,
summary: run.statusDescription ?? '',
...run,
};
diff --git a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
new file mode 100644
index 0000000..60b1a1a
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
@@ -0,0 +1,463 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '@polymer/paper-button/paper-button';
+import '@polymer/paper-card/paper-card';
+import '@polymer/paper-checkbox/paper-checkbox';
+import '@polymer/paper-dropdown-menu/paper-dropdown-menu';
+import '@polymer/paper-fab/paper-fab';
+import '@polymer/paper-icon-button/paper-icon-button';
+import '@polymer/paper-item/paper-item';
+import '@polymer/paper-listbox/paper-listbox';
+import '@polymer/paper-tooltip/paper-tooltip.js';
+
+import '../../shared/gr-button/gr-button';
+import {pluralize} from '../../../utils/string-util';
+import {fire} from '../../../utils/event-util';
+import {DiffInfo} from '../../../types/diff';
+import {assertIsDefined} from '../../../utils/common-util';
+import {css, customElement, html, LitElement, property} from 'lit-element';
+
+import {
+ ContextButtonType,
+ RenderPreferences,
+ SyntaxBlock,
+} from '../../../api/diff';
+
+import {GrDiffGroup, hideInContextControl} from '../gr-diff/gr-diff-group';
+
+const PARTIAL_CONTEXT_AMOUNT = 10;
+
+/**
+ * Traverses a hierarchical structure of syntax blocks and
+ * finds the most local/nested block that can be associated line.
+ * It finds the closest block that contains the whole line and
+ * returns the whole path from the syntax layer (blocks) sent as parameter
+ * to the most nested block - the complete path from the top to bottom layer of
+ * a syntax tree. Example: [myNamepace, MyClass, myMethod1, aLocalFunctionInsideMethod1]
+ *
+ * @param lineNum line number for the targeted line.
+ * @param blocks Blocks for a specific syntax level in the file (to allow recursive calls)
+ */
+function findBlockTreePathForLine(
+ lineNum: number,
+ blocks?: SyntaxBlock[]
+): SyntaxBlock[] {
+ const containingBlock = blocks?.find(
+ ({range}) => range.start_line < lineNum && range.end_line > lineNum
+ );
+ if (!containingBlock) return [];
+ const innerPathInChild = findBlockTreePathForLine(
+ lineNum,
+ containingBlock?.children
+ );
+ return [containingBlock].concat(innerPathInChild);
+}
+
+@customElement('gr-context-controls')
+export class GrContextControls extends LitElement {
+ @property({type: Object}) renderPreferences?: RenderPreferences;
+
+ @property({type: Object}) diff?: DiffInfo;
+
+ @property({type: Object}) section?: HTMLElement;
+
+ @property({type: Object}) contextGroups: GrDiffGroup[] = [];
+
+ @property({type: Boolean}) showAbove = false;
+
+ @property({type: Boolean}) showBelow = false;
+
+ static styles = css`
+ :host {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ justify-content: center;
+ position: absolute;
+ }
+ .contextControlButton {
+ background-color: var(--default-button-background-color);
+ font: var(--context-control-button-font, inherit);
+ /* All position is relative to container, so ignore sibling buttons. */
+ position: absolute;
+ }
+ .contextControlButton:first-child {
+ /* First button needs to claim width to display without text wrapping. */
+ position: relative;
+ }
+ .centeredButton {
+ /* Center over divider. */
+ top: 50%;
+ transform: translateY(-50%);
+ }
+ .aboveBelowButtons {
+ display: flex;
+ flex-direction: column;
+ margin-left: var(--spacing-m);
+ position: relative;
+ }
+ .aboveBelowButtons:first-child {
+ margin-left: 0;
+ }
+
+ .aboveButton {
+ /* Display over preceding content / background placeholder. */
+ transform: translateY(-100%);
+ }
+ .belowButton {
+ top: calc(100% + var(--divider-border));
+ }
+ .breadcrumbTooltip {
+ white-space: nowrap;
+ }
+ `;
+
+ // To pass CSS mixins for @apply to Polymer components, they need to be
+ // wrapped in a <custom-style>.
+ static customStyles = html`
+ <custom-style>
+ <style>
+ .centeredButton {
+ --gr-button: {
+ color: var(--diff-context-control-color);
+ border-style: solid;
+ border-color: var(--border-color);
+ border-top-width: 1px;
+ border-right-width: 1px;
+ border-bottom-width: 1px;
+ border-left-width: 1px;
+
+ border-top-left-radius: var(--border-radius);
+ border-top-right-radius: var(--border-radius);
+ border-bottom-right-radius: var(--border-radius);
+ border-bottom-left-radius: var(--border-radius);
+ padding: var(--spacing-s) var(--spacing-l);
+ }
+ }
+ .aboveButton {
+ --gr-button: {
+ color: var(--diff-context-control-color);
+ border-style: solid;
+ border-color: var(--border-color);
+ border-top-width: 1px;
+ border-right-width: 1px;
+ border-bottom-width: 0;
+ border-left-width: 1px;
+
+ border-top-left-radius: var(--border-radius);
+ border-top-right-radius: var(--border-radius);
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: var(--border-radius);
+ padding: var(--spacing-xxs) var(--spacing-l);
+ }
+ }
+ .belowButton {
+ --gr-button: {
+ color: var(--diff-context-control-color);
+ border-style: solid;
+ border-color: var(--border-color);
+ border-top-width: 0;
+ border-right-width: 1px;
+ border-bottom-width: 1px;
+ border-left-width: 1px;
+
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: var(--border-radius);
+ border-bottom-left-radius: var(--border-radius);
+ padding: var(--spacing-xxs) var(--spacing-l);
+ }
+ }
+ </style>
+ </custom-style>
+ `;
+
+ private numLines() {
+ const {leftStart, leftEnd} = this.contextRange();
+ return leftEnd - leftStart + 1;
+ }
+
+ private createExpandAllButtonContainer() {
+ return html` <div
+ class="style-scope gr-diff aboveBelowButtons fullExpansion"
+ >
+ ${this.createContextButton(ContextButtonType.ALL, this.numLines())}
+ </div>`;
+ }
+
+ /**
+ * Creates a specific expansion button (e.g. +X common lines, +10, +Block).
+ */
+ private createContextButton(
+ type: ContextButtonType,
+ linesToExpand: number,
+ tooltipText?: string
+ ) {
+ let text = '';
+ let groups: GrDiffGroup[] = []; // The groups that replace this one if tapped.
+ let ariaLabel = '';
+ let classes = 'contextControlButton showContext ';
+
+ if (type === ContextButtonType.ALL) {
+ text = `+${pluralize(linesToExpand, 'common line')}`;
+ ariaLabel = `Show ${pluralize(linesToExpand, 'common line')}`;
+ classes +=
+ this.showAbove && this.showBelow
+ ? 'centeredButton'
+ : this.showAbove
+ ? 'aboveButton'
+ : 'belowButton';
+ if (this.partialContent) {
+ // Expanding content would require load of more data
+ text += ' (too large)';
+ }
+ groups.push(...this.contextGroups);
+ } else if (type === ContextButtonType.ABOVE) {
+ groups = hideInContextControl(
+ this.contextGroups,
+ linesToExpand,
+ this.numLines()
+ );
+ text = `+${linesToExpand}`;
+ classes += 'aboveButton';
+ ariaLabel = `Show ${pluralize(linesToExpand, 'line')} above`;
+ } else if (type === ContextButtonType.BELOW) {
+ groups = hideInContextControl(
+ this.contextGroups,
+ 0,
+ this.numLines() - linesToExpand
+ );
+ text = `+${linesToExpand}`;
+ classes += 'belowButton';
+ ariaLabel = `Show ${pluralize(linesToExpand, 'line')} below`;
+ } else if (type === ContextButtonType.BLOCK_ABOVE) {
+ groups = hideInContextControl(
+ this.contextGroups,
+ linesToExpand,
+ this.numLines()
+ );
+ text = '+Block';
+ classes += 'aboveButton';
+ ariaLabel = 'Show block above';
+ } else if (type === ContextButtonType.BLOCK_BELOW) {
+ groups = hideInContextControl(
+ this.contextGroups,
+ 0,
+ this.numLines() - linesToExpand
+ );
+ text = '+Block';
+ classes += 'belowButton';
+ ariaLabel = 'Show block below';
+ }
+ const expandHandler = this.createExpansionHandler(
+ linesToExpand,
+ type,
+ groups
+ );
+
+ const tooltip = tooltipText
+ ? html`<paper-tooltip offset="10"
+ ><div class="breadcrumbTooltip">${tooltipText}</div></paper-tooltip
+ >`
+ : undefined;
+ const button = html` <gr-button
+ class="${classes}"
+ link="true"
+ no-uppercase="true"
+ aria-label="${ariaLabel}"
+ @click="${expandHandler}"
+ >
+ <span class="showContext">${text}</span>
+ ${tooltip}
+ </gr-button>`;
+ return button;
+ }
+
+ private createExpansionHandler(
+ linesToExpand: number,
+ type: ContextButtonType,
+ groups: GrDiffGroup[]
+ ) {
+ return (e: Event) => {
+ e.stopPropagation();
+ if (type === ContextButtonType.ALL && this.partialContent) {
+ const {leftStart, leftEnd, rightStart, rightEnd} = this.contextRange();
+ const lineRange = {
+ left: {
+ start_line: leftStart,
+ end_line: leftEnd,
+ },
+ right: {
+ start_line: rightStart,
+ end_line: rightEnd,
+ },
+ };
+ fire(this, 'content-load-needed', {
+ lineRange,
+ });
+ } else {
+ assertIsDefined(this.section, 'section');
+ fire(this, 'diff-context-expanded', {
+ groups,
+ section: this.section!,
+ numLines: this.numLines(),
+ buttonType: type,
+ expandedLines: linesToExpand,
+ });
+ }
+ };
+ }
+
+ private showPartialLinks() {
+ return this.numLines() > PARTIAL_CONTEXT_AMOUNT;
+ }
+
+ /**
+ * Creates a container div with partial (+10) expansion buttons (above and/or below).
+ */
+ private createPartialExpansionButtons() {
+ if (!this.showPartialLinks()) {
+ return undefined;
+ }
+ let aboveButton;
+ let belowButton;
+ if (this.showAbove) {
+ aboveButton = this.createContextButton(
+ ContextButtonType.ABOVE,
+ PARTIAL_CONTEXT_AMOUNT
+ );
+ }
+ if (this.showBelow) {
+ belowButton = this.createContextButton(
+ ContextButtonType.BELOW,
+ PARTIAL_CONTEXT_AMOUNT
+ );
+ }
+ return aboveButton || belowButton
+ ? html` <div class="aboveBelowButtons partialExpansion">
+ ${aboveButton} ${belowButton}
+ </div>`
+ : undefined;
+ }
+
+ /**
+ * Checks if the collapsed section contains unavailable content (skip chunks).
+ */
+ private get partialContent() {
+ return this.contextGroups.some(c => !!c.skip);
+ }
+
+ /**
+ * Creates a container div with block expansion buttons (above and/or below).
+ */
+ private createBlockExpansionButtons() {
+ if (
+ !this.showPartialLinks() ||
+ !this.renderPreferences?.use_block_expansion ||
+ this.partialContent
+ ) {
+ return undefined;
+ }
+ let aboveBlockButton;
+ let belowBlockButton;
+ if (this.showAbove) {
+ aboveBlockButton = this.createBlockButton(
+ ContextButtonType.BLOCK_ABOVE,
+ this.numLines(),
+ this.contextRange().rightStart - 1
+ );
+ }
+ if (this.showBelow) {
+ belowBlockButton = this.createBlockButton(
+ ContextButtonType.BLOCK_BELOW,
+ this.numLines(),
+ this.contextRange().rightEnd + 1
+ );
+ }
+ if (aboveBlockButton || belowBlockButton) {
+ return html` <div class="aboveBelowButtons blockExpansion">
+ ${aboveBlockButton} ${belowBlockButton}
+ </div>`;
+ }
+ return undefined;
+ }
+
+ private createBlockButton(
+ buttonType: ContextButtonType,
+ numLines: number,
+ referenceLine: number
+ ) {
+ assertIsDefined(this.diff, 'diff');
+ const syntaxTree = this.diff!.meta_b.syntax_tree;
+ const outlineSyntaxPath = findBlockTreePathForLine(
+ referenceLine,
+ syntaxTree
+ );
+ let linesToExpand = numLines;
+ let tooltipText = `${linesToExpand} common lines`;
+ if (outlineSyntaxPath.length) {
+ const {range} = outlineSyntaxPath[outlineSyntaxPath.length - 1];
+ // Create breadcrumb string:
+ // myNamepace > MyClass > myMethod1 > aLocalFunctionInsideMethod1 > (anonymous)
+ tooltipText = outlineSyntaxPath
+ .map(b => b.name || '(anonymous)')
+ .join(' > ');
+ const targetLine =
+ buttonType === ContextButtonType.BLOCK_ABOVE
+ ? range.end_line
+ : range.start_line;
+ const distanceToTargetLine = Math.abs(targetLine - referenceLine);
+ if (distanceToTargetLine < numLines) {
+ linesToExpand = distanceToTargetLine;
+ }
+ }
+ return this.createContextButton(buttonType, linesToExpand, tooltipText);
+ }
+
+ private contextRange() {
+ return {
+ leftStart: this.contextGroups[0].lineRange.left.start_line,
+ leftEnd: this.contextGroups[this.contextGroups.length - 1].lineRange.left
+ .end_line,
+ rightStart: this.contextGroups[0].lineRange.right.start_line,
+ rightEnd: this.contextGroups[this.contextGroups.length - 1].lineRange
+ .right.end_line,
+ };
+ }
+
+ private hasValidProperties() {
+ return !!(this.diff && this.section && this.contextGroups?.length);
+ }
+
+ render() {
+ if (!this.hasValidProperties()) {
+ console.error('Invalid properties for gr-context-controls!');
+ return html`<p>invalid properties</p>`;
+ }
+ return html`
+ ${GrContextControls.customStyles} ${this.createExpandAllButtonContainer()}
+ ${this.createPartialExpansionButtons()}
+ ${this.createBlockExpansionButtons()}
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-context-controls': GrContextControls;
+ }
+}
diff --git a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls_test.ts b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls_test.ts
new file mode 100644
index 0000000..d866c1a
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls_test.ts
@@ -0,0 +1,377 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import '../gr-diff/gr-diff-group';
+import './gr-context-controls';
+import {GrContextControls} from './gr-context-controls';
+import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
+
+import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
+import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
+import {DiffFileMetaInfo, DiffInfo, SyntaxBlock} from '../../../api/diff';
+
+const blankFixture = fixtureFromElement('div');
+
+suite('gr-context-control tests', () => {
+ let element: GrContextControls;
+
+ setup(async () => {
+ element = document.createElement('gr-context-controls');
+ element.diff = ({content: []} as any) as DiffInfo;
+ element.renderPreferences = {};
+ element.section = document.createElement('div');
+ blankFixture.instantiate().appendChild(element);
+ await flush();
+ });
+
+ function createContextGroups(options: {offset?: number; count?: number}) {
+ const offset = options.offset || 0;
+ const numLines = options.count || 10;
+ const lines = [];
+ for (let i = 0; i < numLines; i++) {
+ const line = new GrDiffLine(GrDiffLineType.BOTH);
+ line.beforeNumber = offset + i + 1;
+ line.afterNumber = offset + i + 1;
+ line.text = 'lorem upsum';
+ lines.push(line);
+ }
+
+ return [new GrDiffGroup(GrDiffGroupType.BOTH, lines)];
+ }
+
+ test('no +10 buttons for 10 or less lines', async () => {
+ element.contextGroups = createContextGroups({count: 10});
+
+ await flush();
+
+ const buttons = element.shadowRoot!.querySelectorAll(
+ 'gr-button.showContext'
+ );
+ assert.equal(buttons.length, 1);
+ assert.equal(buttons[0].textContent!.trim(), '+10 common lines');
+ });
+
+ test('context control at the top', async () => {
+ element.contextGroups = createContextGroups({offset: 0, count: 20});
+ element.showBelow = true;
+
+ await flush();
+
+ const buttons = element.shadowRoot!.querySelectorAll(
+ 'gr-button.showContext'
+ );
+
+ assert.equal(buttons.length, 2);
+ assert.equal(buttons[0].textContent!.trim(), '+20 common lines');
+ assert.equal(buttons[1].textContent!.trim(), '+10');
+
+ assert.include([...buttons[0].classList.values()], 'belowButton');
+ assert.include([...buttons[1].classList.values()], 'belowButton');
+ });
+
+ test('context control in the middle', async () => {
+ element.contextGroups = createContextGroups({offset: 10, count: 20});
+ element.showAbove = true;
+ element.showBelow = true;
+
+ await flush();
+
+ const buttons = element.shadowRoot!.querySelectorAll(
+ 'gr-button.showContext'
+ );
+
+ assert.equal(buttons.length, 3);
+ assert.equal(buttons[0].textContent!.trim(), '+20 common lines');
+ assert.equal(buttons[1].textContent!.trim(), '+10');
+ assert.equal(buttons[2].textContent!.trim(), '+10');
+
+ assert.include([...buttons[0].classList.values()], 'centeredButton');
+ assert.include([...buttons[1].classList.values()], 'aboveButton');
+ assert.include([...buttons[2].classList.values()], 'belowButton');
+ });
+
+ test('context control at the bottom', async () => {
+ element.contextGroups = createContextGroups({offset: 30, count: 20});
+ element.showAbove = true;
+
+ await flush();
+
+ const buttons = element.shadowRoot!.querySelectorAll(
+ 'gr-button.showContext'
+ );
+
+ assert.equal(buttons.length, 2);
+ assert.equal(buttons[0].textContent!.trim(), '+20 common lines');
+ assert.equal(buttons[1].textContent!.trim(), '+10');
+
+ assert.include([...buttons[0].classList.values()], 'aboveButton');
+ assert.include([...buttons[1].classList.values()], 'aboveButton');
+ });
+
+ function prepareForBlockExpansion(syntaxTree: SyntaxBlock[]) {
+ element.renderPreferences!.use_block_expansion = true;
+ element.diff!.meta_b = ({
+ syntax_tree: syntaxTree,
+ } as any) as DiffFileMetaInfo;
+ }
+
+ test('context control with block expansion at the top', async () => {
+ prepareForBlockExpansion([]);
+ element.contextGroups = createContextGroups({offset: 0, count: 20});
+ element.showBelow = true;
+
+ await flush();
+
+ const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.fullExpansion gr-button'
+ );
+ const partialExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.partialExpansion gr-button'
+ );
+ const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.blockExpansion gr-button'
+ );
+ assert.equal(fullExpansionButtons.length, 1);
+ assert.equal(partialExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons.length, 1);
+ assert.equal(
+ blockExpansionButtons[0].querySelector('span')!.textContent!.trim(),
+ '+Block'
+ );
+ assert.include(
+ [...blockExpansionButtons[0].classList.values()],
+ 'belowButton'
+ );
+ });
+
+ test('context control with block expansion in the middle', async () => {
+ prepareForBlockExpansion([]);
+ element.contextGroups = createContextGroups({offset: 10, count: 20});
+ element.showAbove = true;
+ element.showBelow = true;
+
+ await flush();
+
+ const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.fullExpansion gr-button'
+ );
+ const partialExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.partialExpansion gr-button'
+ );
+ const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.blockExpansion gr-button'
+ );
+ assert.equal(fullExpansionButtons.length, 1);
+ assert.equal(partialExpansionButtons.length, 2);
+ assert.equal(blockExpansionButtons.length, 2);
+ assert.equal(
+ blockExpansionButtons[0].querySelector('span')!.textContent!.trim(),
+ '+Block'
+ );
+ assert.equal(
+ blockExpansionButtons[1].querySelector('span')!.textContent!.trim(),
+ '+Block'
+ );
+ assert.include(
+ [...blockExpansionButtons[0].classList.values()],
+ 'aboveButton'
+ );
+ assert.include(
+ [...blockExpansionButtons[1].classList.values()],
+ 'belowButton'
+ );
+ });
+
+ test('context control with block expansion at the bottom', async () => {
+ prepareForBlockExpansion([]);
+ element.contextGroups = createContextGroups({offset: 30, count: 20});
+ element.showAbove = true;
+
+ await flush();
+
+ const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.fullExpansion gr-button'
+ );
+ const partialExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.partialExpansion gr-button'
+ );
+ const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.blockExpansion gr-button'
+ );
+ assert.equal(fullExpansionButtons.length, 1);
+ assert.equal(partialExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons.length, 1);
+ assert.equal(
+ blockExpansionButtons[0].querySelector('span')!.textContent!.trim(),
+ '+Block'
+ );
+ assert.include(
+ [...blockExpansionButtons[0].classList.values()],
+ 'aboveButton'
+ );
+ });
+
+ test('+ Block tooltip tooltip shows syntax block containing the target lines above and below', async () => {
+ prepareForBlockExpansion([
+ {
+ name: 'aSpecificFunction',
+ range: {start_line: 1, start_column: 0, end_line: 25, end_column: 0},
+ children: [],
+ },
+ {
+ name: 'anotherFunction',
+ range: {start_line: 26, start_column: 0, end_line: 50, end_column: 0},
+ children: [],
+ },
+ ]);
+ element.contextGroups = createContextGroups({offset: 10, count: 20});
+ element.showAbove = true;
+ element.showBelow = true;
+
+ await flush();
+
+ const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.blockExpansion gr-button'
+ );
+ assert.equal(
+ blockExpansionButtons[0]
+ .querySelector('.breadcrumbTooltip')!
+ .textContent?.trim(),
+ 'aSpecificFunction'
+ );
+ assert.equal(
+ blockExpansionButtons[1]
+ .querySelector('.breadcrumbTooltip')!
+ .textContent?.trim(),
+ 'anotherFunction'
+ );
+ });
+
+ test('+Block tooltip shows nested syntax blocks as breadcrumbs', async () => {
+ prepareForBlockExpansion([
+ {
+ name: 'aSpecificNamespace',
+ range: {start_line: 1, start_column: 0, end_line: 200, end_column: 0},
+ children: [
+ {
+ name: 'MyClass',
+ range: {
+ start_line: 2,
+ start_column: 0,
+ end_line: 100,
+ end_column: 0,
+ },
+ children: [
+ {
+ name: 'aMethod',
+ range: {
+ start_line: 5,
+ start_column: 0,
+ end_line: 80,
+ end_column: 0,
+ },
+ children: [],
+ },
+ ],
+ },
+ ],
+ },
+ ]);
+ element.contextGroups = createContextGroups({offset: 10, count: 20});
+ element.showAbove = true;
+ element.showBelow = true;
+
+ await flush();
+
+ const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.blockExpansion gr-button'
+ );
+ assert.equal(
+ blockExpansionButtons[0]
+ .querySelector('.breadcrumbTooltip')!
+ .textContent?.trim(),
+ 'aSpecificNamespace > MyClass > aMethod'
+ );
+ });
+
+ test('+Block tooltip shows (anonymous) for empty blocks', async () => {
+ prepareForBlockExpansion([
+ {
+ name: 'aSpecificNamespace',
+ range: {start_line: 1, start_column: 0, end_line: 200, end_column: 0},
+ children: [
+ {
+ name: '',
+ range: {
+ start_line: 2,
+ start_column: 0,
+ end_line: 100,
+ end_column: 0,
+ },
+ children: [
+ {
+ name: 'aMethod',
+ range: {
+ start_line: 5,
+ start_column: 0,
+ end_line: 80,
+ end_column: 0,
+ },
+ children: [],
+ },
+ ],
+ },
+ ],
+ },
+ ]);
+ element.contextGroups = createContextGroups({offset: 10, count: 20});
+ element.showAbove = true;
+ element.showBelow = true;
+ await flush();
+
+ const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.blockExpansion gr-button'
+ );
+ assert.equal(
+ blockExpansionButtons[0]
+ .querySelector('.breadcrumbTooltip')!
+ .textContent?.trim(),
+ 'aSpecificNamespace > (anonymous) > aMethod'
+ );
+ });
+
+ test('+Block tooltip shows "all common lines" for empty syntax tree', async () => {
+ prepareForBlockExpansion([]);
+
+ element.contextGroups = createContextGroups({offset: 10, count: 20});
+ element.showAbove = true;
+ element.showBelow = true;
+ await flush();
+
+ const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
+ '.blockExpansion gr-button'
+ );
+ // querySelector('.breadcrumbTooltip')!.textContent!.trim()
+ assert.equal(
+ blockExpansionButtons[0]
+ .querySelector('.breadcrumbTooltip')!
+ .textContent?.trim(),
+ '20 common lines'
+ );
+ });
+});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
index 5ba3606..4455bd5 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
@@ -18,6 +18,7 @@
import '../../../test/common-test-setup-karma.js';
import '../gr-diff/gr-diff-group.js';
import './gr-diff-builder.js';
+import '../gr-context-controls/gr-context-controls.js';
import {getMockDiffResponse} from '../../../test/mocks/diff-response.js';
import './gr-diff-builder-element.js';
import {stubBaseUrl} from '../../../test/test-utils.js';
@@ -74,152 +75,6 @@
assert.isTrue(node.classList.contains('classes'));
});
- suite('context control', () => {
- function createContextGroups(options) {
- const offset = options.offset || 0;
- const numLines = options.count || 10;
- const lines = [];
- for (let i = 0; i < numLines; i++) {
- const line = new GrDiffLine(GrDiffLineType.BOTH);
- line.beforeNumber = offset + i + 1;
- line.afterNumber = offset + i + 1;
- line.text = 'lorem upsum';
- lines.push(line);
- }
-
- return [new GrDiffGroup(GrDiffGroupType.BOTH, lines)];
- }
-
- function createContextSectionForGroups(options) {
- const section = document.createElement('div');
- builder._createContextControls(
- section, createContextGroups(options), DiffViewMode.UNIFIED);
- return section;
- }
-
- let diffInfo;
- let renderPrefs;
-
- setup(() => {
- diffInfo = {content: []};
- renderPrefs = {};
- builder = new GrDiffBuilder(diffInfo, prefs, null, [], renderPrefs);
- });
-
- test('no +10 buttons for 10 or less lines', () => {
- const section = createContextSectionForGroups({count: 10});
- const buttons = section.querySelectorAll('gr-button.showContext');
-
- assert.equal(buttons.length, 1);
- assert.equal(buttons[0].textContent, '+10 common lines');
- });
-
- test('context control at the top', () => {
- builder._numLinesLeft = 50;
- const section = createContextSectionForGroups({offset: 0, count: 20});
- const buttons = section.querySelectorAll('gr-button.showContext');
-
- assert.equal(buttons.length, 2);
- assert.equal(buttons[0].textContent, '+20 common lines');
- assert.equal(buttons[1].textContent, '+10');
-
- assert.include([...buttons[0].classList.values()], 'belowButton');
- assert.include([...buttons[1].classList.values()], 'belowButton');
- });
-
- test('context control in the middle', () => {
- builder._numLinesLeft = 50;
- const section = createContextSectionForGroups({offset: 10, count: 20});
- const buttons = section.querySelectorAll('gr-button.showContext');
-
- assert.equal(buttons.length, 3);
- assert.equal(buttons[0].textContent, '+20 common lines');
- assert.equal(buttons[1].textContent, '+10');
- assert.equal(buttons[2].textContent, '+10');
-
- assert.include([...buttons[0].classList.values()], 'centeredButton');
- assert.include([...buttons[1].classList.values()], 'aboveButton');
- assert.include([...buttons[2].classList.values()], 'belowButton');
- });
-
- test('context control at the bottom', () => {
- builder._numLinesLeft = 50;
- const section = createContextSectionForGroups({offset: 30, count: 20});
- const buttons = section.querySelectorAll('gr-button.showContext');
-
- assert.equal(buttons.length, 2);
- assert.equal(buttons[0].textContent, '+20 common lines');
- assert.equal(buttons[1].textContent, '+10');
-
- assert.include([...buttons[0].classList.values()], 'aboveButton');
- assert.include([...buttons[1].classList.values()], 'aboveButton');
- });
-
- suite('with block expansion', () => {
- setup(() => {
- builder._numLinesLeft = 50;
- renderPrefs.use_block_expansion = true;
- diffInfo.meta_b = {
- syntax_tree: [],
- };
- });
-
- test('context control with block expansion at the top', () => {
- const section = createContextSectionForGroups({offset: 0, count: 20});
-
- const fullExpansionButtons = section
- .querySelectorAll('.fullExpansion gr-button');
- const partialExpansionButtons = section
- .querySelectorAll('.partialExpansion gr-button');
- const blockExpansionButtons = section
- .querySelectorAll('.blockExpansion gr-button');
- assert.equal(fullExpansionButtons.length, 1);
- assert.equal(partialExpansionButtons.length, 1);
- assert.equal(blockExpansionButtons.length, 1);
- assert.equal(blockExpansionButtons[0].textContent, '+Block');
- assert.include([...blockExpansionButtons[0].classList.values()],
- 'belowButton');
- });
-
- test('context control in the middle', () => {
- const section = createContextSectionForGroups({offset: 10, count: 20});
-
- const fullExpansionButtons = section
- .querySelectorAll('.fullExpansion gr-button');
- const partialExpansionButtons = section
- .querySelectorAll('.partialExpansion gr-button');
- const blockExpansionButtons = section
- .querySelectorAll('.blockExpansion gr-button');
- assert.equal(fullExpansionButtons.length, 1);
- assert.equal(partialExpansionButtons.length, 2);
- assert.equal(blockExpansionButtons.length, 2);
- assert.equal(blockExpansionButtons[0].textContent, '+Block');
- assert.equal(blockExpansionButtons[1].textContent, '+Block');
- assert.include([...blockExpansionButtons[0].classList.values()],
- 'aboveButton');
- assert.include([...blockExpansionButtons[1].classList.values()],
- 'belowButton');
- });
-
- test('context control at the bottom', () => {
- const section = createContextSectionForGroups({offset: 30, count: 20});
-
- const fullExpansionButtons = section
- .querySelectorAll('.fullExpansion gr-button');
- const partialExpansionButtons = section
- .querySelectorAll('.partialExpansion gr-button');
- const blockExpansionButtons = section
- .querySelectorAll('.blockExpansion gr-button');
- assert.equal(fullExpansionButtons.length, 1);
- assert.equal(partialExpansionButtons.length, 1);
- assert.equal(blockExpansionButtons.length, 1);
- assert.equal(blockExpansionButtons[0].textContent, '+Block');
- assert.include([...blockExpansionButtons[0].classList.values()],
- 'aboveButton');
- });
- });
- });
-
test('newlines 1', () => {
let text = 'abcdef';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index adfe75f..0ccf8bc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -16,25 +16,20 @@
*/
import {
ContentLoadNeededEventDetail,
- ContextButtonType,
DiffContextExpandedExternalDetail,
MovedLinkClickedEventDetail,
RenderPreferences,
- SyntaxBlock,
} from '../../../api/diff';
import {getBaseUrl} from '../../../utils/url-util';
import {GrDiffLine, GrDiffLineType, LineNumber} from '../gr-diff/gr-diff-line';
-import {
- GrDiffGroup,
- GrDiffGroupType,
- hideInContextControl,
-} from '../gr-diff/gr-diff-group';
+import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
+
+import '../gr-context-controls/gr-context-controls';
+import {GrContextControls} from '../gr-context-controls/gr-context-controls';
import {BlameInfo} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
import {DiffViewMode, Side} from '../../../constants/constants';
import {DiffLayer} from '../../../types/types';
-import {pluralize} from '../../../utils/string-util';
-import {fire} from '../../../utils/event-util';
/**
* In JS, unicode code points above 0xFFFF occupy two elements of a string.
@@ -58,8 +53,6 @@
*/
const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
-const PARTIAL_CONTEXT_AMOUNT = 10;
-
export interface DiffContextExpandedEventDetail
extends DiffContextExpandedExternalDetail {
groups: GrDiffGroup[];
@@ -74,19 +67,6 @@
}
}
-function findMostNestedContainingBlock(
- lineNum: number,
- blocks?: SyntaxBlock[]
-): SyntaxBlock | undefined {
- const containingBlock = blocks?.find(
- ({range}) => range.start_line < lineNum && range.end_line > lineNum
- );
- const containingChildBlock = containingBlock
- ? findMostNestedContainingBlock(lineNum, containingBlock?.children)
- : undefined;
- return containingChildBlock || containingBlock;
-}
-
export abstract class GrDiffBuilder {
private readonly _diff: DiffInfo;
@@ -320,7 +300,6 @@
);
}
- // TODO(renanoliveira): Move context controls to polymer component (or at least a separate class).
_createContextControls(
section: HTMLElement,
contextGroups: GrDiffGroup[],
@@ -329,10 +308,6 @@
const leftStart = contextGroups[0].lineRange.left.start_line;
const leftEnd =
contextGroups[contextGroups.length - 1].lineRange.left.end_line;
- const rightStart = contextGroups[0].lineRange.right.start_line;
- const rightEnd =
- contextGroups[contextGroups.length - 1].lineRange.right.end_line;
-
const firstGroupIsSkipped = !!contextGroups[0].skip;
const lastGroupIsSkipped = !!contextGroups[contextGroups.length - 1].skip;
@@ -349,9 +324,7 @@
section,
contextGroups,
showAbove,
- showBelow,
- rightStart,
- rightEnd
+ showBelow
)
);
if (showBelow) {
@@ -369,13 +342,8 @@
section: HTMLElement,
contextGroups: GrDiffGroup[],
showAbove: boolean,
- showBelow: boolean,
- rightStart: number,
- rightEnd: number
+ showBelow: boolean
): HTMLElement {
- const numLines = rightEnd - rightStart + 1;
- if (numLines === 0) console.error('context group without lines');
-
const row = this._createElement('tr', 'contextDivider');
if (!(showAbove && showBelow)) {
row.classList.add('collapsed');
@@ -384,193 +352,19 @@
const element = this._createElement('td', 'dividerCell');
row.appendChild(element);
- const showAllContainer = this._createExpandAllButtonContainer(
- section,
- contextGroups,
- showAbove,
- showBelow,
- numLines
- );
- element.appendChild(showAllContainer);
-
- const showPartialLinks = numLines > PARTIAL_CONTEXT_AMOUNT;
- if (showPartialLinks) {
- const partialExpansionContainer = this._createPartialExpansionButtons(
- section,
- contextGroups,
- showAbove,
- showBelow,
- numLines
- );
- if (partialExpansionContainer) {
- element.appendChild(partialExpansionContainer);
- }
- const blockExpansionContainer = this._createBlockExpansionButtons(
- section,
- contextGroups,
- showAbove,
- showBelow,
- rightStart,
- rightEnd,
- numLines
- );
- if (blockExpansionContainer) {
- element.appendChild(blockExpansionContainer);
- }
- }
+ const contextControls = this._createElement(
+ 'gr-context-controls'
+ ) as GrContextControls;
+ contextControls.diff = this._diff;
+ contextControls.renderPreferences = this._renderPrefs;
+ contextControls.section = section;
+ contextControls.contextGroups = contextGroups;
+ contextControls.showAbove = showAbove;
+ contextControls.showBelow = showBelow;
+ element.appendChild(contextControls);
return row;
}
- private _createExpandAllButtonContainer(
- section: HTMLElement,
- contextGroups: GrDiffGroup[],
- showAbove: boolean,
- showBelow: boolean,
- numLines: number
- ) {
- const showAllButton = this._createContextButton(
- ContextButtonType.ALL,
- section,
- contextGroups,
- numLines,
- numLines
- );
- showAllButton.classList.add(
- showAbove && showBelow
- ? 'centeredButton'
- : showAbove
- ? 'aboveButton'
- : 'belowButton'
- );
- const showAllContainer = this._createElement(
- 'div',
- 'aboveBelowButtons fullExpansion'
- );
- showAllContainer.appendChild(showAllButton);
- return showAllContainer;
- }
-
- private _createPartialExpansionButtons(
- section: HTMLElement,
- contextGroups: GrDiffGroup[],
- showAbove: boolean,
- showBelow: boolean,
- numLines: number
- ) {
- let aboveButton;
- let belowButton;
- if (showAbove) {
- aboveButton = this._createContextButton(
- ContextButtonType.ABOVE,
- section,
- contextGroups,
- numLines,
- PARTIAL_CONTEXT_AMOUNT
- );
- }
- if (showBelow) {
- belowButton = this._createContextButton(
- ContextButtonType.BELOW,
- section,
- contextGroups,
- numLines,
- PARTIAL_CONTEXT_AMOUNT
- );
- }
- if (aboveButton || belowButton) {
- const partialExpansionContainer = this._createElement(
- 'div',
- 'aboveBelowButtons partialExpansion'
- );
- aboveButton && partialExpansionContainer.appendChild(aboveButton);
- belowButton && partialExpansionContainer.appendChild(belowButton);
- return partialExpansionContainer;
- }
- return undefined;
- }
-
- private _createBlockExpansionButtons(
- section: HTMLElement,
- contextGroups: GrDiffGroup[],
- showAbove: boolean,
- showBelow: boolean,
- rightStart: number,
- rightEnd: number,
- numLines: number
- ) {
- const fullContentNotAvailable =
- contextGroups.find(c => !!c.skip) !== undefined;
- if (!this._renderPrefs?.use_block_expansion || fullContentNotAvailable) {
- return undefined;
- }
- let aboveBlockButton;
- let belowBlockButton;
- const rightSyntaxTree = this._diff.meta_b.syntax_tree;
- if (showAbove) {
- aboveBlockButton = this._createBlockButton(
- section,
- contextGroups,
- ContextButtonType.BLOCK_ABOVE,
- numLines,
- rightStart - 1,
- rightSyntaxTree
- );
- }
- if (showBelow) {
- belowBlockButton = this._createBlockButton(
- section,
- contextGroups,
- ContextButtonType.BLOCK_BELOW,
- numLines,
- rightEnd + 1,
- rightSyntaxTree
- );
- }
- if (aboveBlockButton || belowBlockButton) {
- const blockExpansionContainer = this._createElement(
- 'div',
- 'blockExpansion aboveBelowButtons'
- );
- aboveBlockButton && blockExpansionContainer.appendChild(aboveBlockButton);
- belowBlockButton && blockExpansionContainer.appendChild(belowBlockButton);
- return blockExpansionContainer;
- }
- return undefined;
- }
-
- private _createBlockButton(
- section: HTMLElement,
- contextGroups: GrDiffGroup[],
- buttonType: ContextButtonType,
- numLines: number,
- referenceLine: number,
- syntaxTree?: SyntaxBlock[]
- ) {
- const containingBlock = findMostNestedContainingBlock(
- referenceLine,
- syntaxTree
- );
- let linesToExpand = numLines;
- if (containingBlock) {
- const {range} = containingBlock;
- const targetLine =
- buttonType === ContextButtonType.BLOCK_ABOVE
- ? range.end_line
- : range.start_line;
- const distanceToTargetLine = Math.abs(targetLine - referenceLine);
- if (distanceToTargetLine < numLines) {
- linesToExpand = distanceToTargetLine;
- }
- }
- return this._createContextButton(
- buttonType,
- section,
- contextGroups,
- numLines,
- linesToExpand
- );
- }
-
/**
* Creates a table row to serve as padding between code and context controls.
* Blame column, line gutters, and content area will continue visually, but
@@ -599,99 +393,6 @@
return row;
}
- _createContextButton(
- type: ContextButtonType,
- section: HTMLElement,
- contextGroups: GrDiffGroup[],
- numLines: number,
- linesToExpand: number
- ) {
- const button = this._createElement('gr-button', 'showContext');
- button.classList.add('contextControlButton');
- button.setAttribute('link', 'true');
- button.setAttribute('no-uppercase', 'true');
-
- let text = '';
- let groups: GrDiffGroup[] = []; // The groups that replace this one if tapped.
- let requiresLoad = false;
- if (type === ContextButtonType.ALL) {
- text = `+${pluralize(linesToExpand, 'common line')}`;
- button.setAttribute(
- 'aria-label',
- `Show ${pluralize(linesToExpand, 'common line')}`
- );
- requiresLoad = contextGroups.find(c => !!c.skip) !== undefined;
- if (requiresLoad) {
- // Expanding content would require load of more data
- text += ' (too large)';
- }
- groups.push(...contextGroups);
- } else if (type === ContextButtonType.ABOVE) {
- groups = hideInContextControl(contextGroups, linesToExpand, numLines);
- text = `+${linesToExpand}`;
- button.classList.add('aboveButton');
- button.setAttribute(
- 'aria-label',
- `Show ${pluralize(linesToExpand, 'line')} above`
- );
- } else if (type === ContextButtonType.BELOW) {
- groups = hideInContextControl(contextGroups, 0, numLines - linesToExpand);
- text = `+${linesToExpand}`;
- button.classList.add('belowButton');
- button.setAttribute(
- 'aria-label',
- `Show ${pluralize(linesToExpand, 'line')} below`
- );
- } else if (type === ContextButtonType.BLOCK_ABOVE) {
- groups = hideInContextControl(contextGroups, linesToExpand, numLines);
- text = '+Block';
- button.classList.add('aboveButton');
- button.setAttribute('aria-label', 'Show block above');
- } else if (type === ContextButtonType.BLOCK_BELOW) {
- groups = hideInContextControl(contextGroups, 0, numLines - linesToExpand);
- text = '+Block';
- button.classList.add('belowButton');
- button.setAttribute('aria-label', 'Show block below');
- }
- const textSpan = this._createElement('span', 'showContext');
- textSpan.textContent = text;
- button.appendChild(textSpan);
-
- if (requiresLoad) {
- button.addEventListener('click', e => {
- e.stopPropagation();
- const firstRange = groups[0].lineRange;
- const lastRange = groups[groups.length - 1].lineRange;
- const lineRange = {
- left: {
- start_line: firstRange.left.start_line,
- end_line: lastRange.left.end_line,
- },
- right: {
- start_line: firstRange.right.start_line,
- end_line: lastRange.right.end_line,
- },
- };
- fire(button, 'content-load-needed', {
- lineRange,
- });
- });
- } else {
- button.addEventListener('click', e => {
- e.stopPropagation();
- fire(button, 'diff-context-expanded', {
- groups,
- section,
- numLines,
- buttonType: type,
- expandedLines: linesToExpand,
- });
- });
- }
-
- return button;
- }
-
_createLineEl(
line: GrDiffLine,
number: LineNumber,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
index 75439c8..901f72a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
@@ -617,6 +617,7 @@
test('expand context updates stops', done => {
sinon.spy(cursorElement, '_updateStops');
MockInteractions.tap(diffElement.shadowRoot
+ .querySelector('gr-context-controls').shadowRoot
.querySelector('.showContext'));
flush(() => {
assert.isTrue(cursorElement._updateStops.called);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
index 285c942..f48d15a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
@@ -344,81 +344,6 @@
top: 0;
left: 0;
}
- .contextControlButton {
- background-color: var(--default-button-background-color);
- font: var(--context-control-button-font, inherit);
- /* All position is relative to container, so ignore sibling buttons. */
- position: absolute;
- }
- .contextControlButton:first-child {
- /* First button needs to claim width to display without text wrapping. */
- position: relative;
- }
- .centeredButton {
- /* Center over divider. */
- top: 50%;
- transform: translateY(-50%);
- --gr-button: {
- color: var(--diff-context-control-color);
- border-style: solid;
- border-color: var(--border-color);
- border-top-width: 1px;
- border-right-width: 1px;
- border-bottom-width: 1px;
- border-left-width: 1px;
- border-top-left-radius: var(--border-radius);
- border-top-right-radius: var(--border-radius);
- border-bottom-right-radius: var(--border-radius);
- border-bottom-left-radius: var(--border-radius);
- padding: var(--spacing-s) var(--spacing-l);
- }
- }
- .aboveBelowButtons {
- display: flex;
- flex-direction: column;
- margin-left: var(--spacing-m);
- position: relative;
- }
- .aboveBelowButtons:first-child {
- margin-left: 0;
- }
- .aboveButton {
- /* Display over preceding content / background placeholder. */
- transform: translateY(-100%);
- --gr-button: {
- color: var(--diff-context-control-color);
- border-style: solid;
- border-color: var(--border-color);
- border-top-width: 1px;
- border-right-width: 1px;
- border-bottom-width: 0;
- border-left-width: 1px;
- border-top-left-radius: var(--border-radius);
- border-top-right-radius: var(--border-radius);
- border-bottom-right-radius: 0;
- border-bottom-left-radius: 0;
- padding: var(--spacing-xxs) var(--spacing-l);
- }
- }
- .belowButton {
- /* Display over following content / background placeholder. */
- top: calc(100% + var(--divider-border));
- --gr-button: {
- color: var(--diff-context-control-color);
- border-style: solid;
- border-color: var(--border-color);
- border-top-width: 0;
- border-right-width: 1px;
- border-bottom-width: 1px;
- border-left-width: 1px;
- border-top-left-radius: 0;
- border-top-right-radius: 0;
- border-bottom-right-radius: var(--border-radius);
- border-bottom-left-radius: var(--border-radius);
- padding: var(--spacing-xxs) var(--spacing-l);
- }
- }
-
.displayLine .diff-row.target-row td {
box-shadow: inset 0 -1px var(--border-color);
}
diff --git a/polygerrit-ui/app/services/checks/checks-util.ts b/polygerrit-ui/app/services/checks/checks-util.ts
index 15841f3..0a0881b 100644
--- a/polygerrit-ui/app/services/checks/checks-util.ts
+++ b/polygerrit-ui/app/services/checks/checks-util.ts
@@ -79,10 +79,11 @@
if (hasResultsOf(run, Category.ERROR)) return Category.ERROR;
if (hasResultsOf(run, Category.WARNING)) return Category.WARNING;
if (hasResultsOf(run, Category.INFO)) return Category.INFO;
+ if (hasResultsOf(run, Category.SUCCESS)) return Category.SUCCESS;
return undefined;
}
-export function iconForCategory(category: Category | 'SUCCESS') {
+export function iconForCategory(category: Category) {
switch (category) {
case Category.ERROR:
return 'error';
@@ -90,7 +91,7 @@
return 'info-outline';
case Category.WARNING:
return 'warning';
- case 'SUCCESS':
+ case Category.SUCCESS:
return 'check-circle-outline';
default:
assertNever(category, `Unsupported category: ${category}`);
@@ -210,12 +211,14 @@
export function level(cat?: Category) {
if (!cat) return -1;
switch (cat) {
- case Category.INFO:
+ case Category.SUCCESS:
return 0;
- case Category.WARNING:
+ case Category.INFO:
return 1;
- case Category.ERROR:
+ case Category.WARNING:
return 2;
+ case Category.ERROR:
+ return 3;
}
}
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 65095dd..70cddc0 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -638,6 +638,7 @@
subject: string;
message: string;
web_links?: WebLinkInfo[];
+ resolve_conflicts_web_links?: WebLinkInfo[];
}
export interface CommitInfoWithRequiredCommit extends CommitInfo {