Merge "Cache rest api for getAccountDetails"
diff --git a/java/com/google/gerrit/entities/RefNames.java b/java/com/google/gerrit/entities/RefNames.java
index 66ffa42..e79c530 100644
--- a/java/com/google/gerrit/entities/RefNames.java
+++ b/java/com/google/gerrit/entities/RefNames.java
@@ -195,7 +195,7 @@
return ref.startsWith(REFS_TAGS);
}
- /** True if the provided ref is {@link REFS_EXTERNAL_IDS}. */
+ /** True if the provided ref is {@link #REFS_EXTERNAL_IDS}. */
public static boolean isExternalIdRef(String ref) {
return REFS_EXTERNAL_IDS.equals(ref);
}
diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
index 72bfe40..92788b7 100644
--- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
@@ -121,7 +121,7 @@
data.put("userIsAuthenticated", true);
if (page == RequestedPage.DASHBOARD) {
data.put("defaultDashboardHex", ListOption.toHex(IndexPreloadingUtil.DASHBOARD_OPTIONS));
- data.put("dashboardQuery", IndexPreloadingUtil.computeDashboardQueryList(serverApi));
+ data.put("dashboardQuery", IndexPreloadingUtil.computeDashboardQueryList());
}
} catch (AuthException e) {
logger.atFine().log("Can't inline account-related data because user is unauthenticated");
diff --git a/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java b/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
index bb3b6d5..afaeaf6 100644
--- a/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
@@ -22,9 +22,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.UsedAt.Project;
-import com.google.gerrit.extensions.api.config.Server;
import com.google.gerrit.extensions.client.ListChangesOption;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.Url;
import java.net.URI;
import java.net.URISyntaxException;
@@ -188,7 +186,7 @@
return Optional.empty();
}
- public static List<String> computeDashboardQueryList(Server serverApi) throws RestApiException {
+ public static List<String> computeDashboardQueryList() {
List<String> queryList = new ArrayList<>();
queryList.add(SELF_DASHBOARD_HAS_UNPUBLISHED_DRAFTS_QUERY);
queryList.add(SELF_YOUR_TURN);
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 8e443f82..7984737 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -505,9 +505,11 @@
/** The user assigned to the change. */
// The getter always returns NO_ASSIGNEE, since assignee field is deprecated.
+ @Deprecated
public static final IndexedField<ChangeData, Integer> ASSIGNEE_FIELD =
IndexedField.<ChangeData>integerBuilder("Assignee").build(changeGetter(c -> NO_ASSIGNEE));
+ @Deprecated
public static final IndexedField<ChangeData, Integer>.SearchSpec ASSIGNEE_SPEC =
ASSIGNEE_FIELD.integer(ChangeQueryBuilder.FIELD_ASSIGNEE);
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 6ddf7a3..82b8f18 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -240,6 +240,7 @@
.build();
/** Remove assignee field. */
+ @SuppressWarnings("deprecation")
static final Schema<ChangeData> V82 =
new Schema.Builder<ChangeData>()
.add(V81)
diff --git a/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java b/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java
index 60dff84..e91f7b7 100644
--- a/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java
+++ b/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java
@@ -72,13 +72,15 @@
if (!ready) {
synchronized (dataDir) {
if (!ready) {
- try {
- Files.createDirectories(dataDir);
- } catch (IOException e) {
- throw new ProvisionException(
- String.format(
- "Cannot create %s for plugin %s", dataDir.toAbsolutePath(), plugin.getName()),
- e);
+ if (!Files.isDirectory(dataDir)) {
+ try {
+ Files.createDirectories(dataDir);
+ } catch (IOException e) {
+ throw new ProvisionException(
+ String.format(
+ "Cannot create %s for plugin %s", dataDir.toAbsolutePath(), plugin.getName()),
+ e);
+ }
}
ready = true;
}
diff --git a/java/com/google/gerrit/server/restapi/BUILD b/java/com/google/gerrit/server/restapi/BUILD
index 62da2f2..dd0ec78d 100644
--- a/java/com/google/gerrit/server/restapi/BUILD
+++ b/java/com/google/gerrit/server/restapi/BUILD
@@ -34,6 +34,7 @@
"//lib/auto:auto-factory",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
+ "//lib/commons:codec",
"//lib/commons:compress",
"//lib/commons:lang3",
"//lib/errorprone:annotations",
diff --git a/java/com/google/gerrit/server/restapi/change/ApplyPatchUtil.java b/java/com/google/gerrit/server/restapi/change/ApplyPatchUtil.java
index d4f549a..4021f77 100644
--- a/java/com/google/gerrit/server/restapi/change/ApplyPatchUtil.java
+++ b/java/com/google/gerrit/server/restapi/change/ApplyPatchUtil.java
@@ -23,6 +23,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import org.apache.commons.codec.binary.Base64;
import org.eclipse.jgit.api.errors.PatchApplyException;
import org.eclipse.jgit.api.errors.PatchFormatException;
import org.eclipse.jgit.lib.ObjectId;
@@ -51,8 +52,12 @@
throws IOException, RestApiException {
checkNotNull(mergeTip);
RevTree tip = mergeTip.getTree();
- InputStream patchStream =
- new ByteArrayInputStream(input.patch.getBytes(StandardCharsets.UTF_8));
+ InputStream patchStream;
+ if (Base64.isBase64(input.patch)) {
+ patchStream = new ByteArrayInputStream(org.eclipse.jgit.util.Base64.decode(input.patch));
+ } else {
+ patchStream = new ByteArrayInputStream(input.patch.getBytes(StandardCharsets.UTF_8));
+ }
try {
PatchApplier applier = new PatchApplier(repo, tip, oi);
PatchApplier.Result applyResult = applier.applyPatch(patchStream);
diff --git a/java/com/google/gerrit/server/restapi/change/RebaseChain.java b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
index db94db2..34a2623 100644
--- a/java/com/google/gerrit/server/restapi/change/RebaseChain.java
+++ b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
@@ -245,6 +245,9 @@
try (Repository repo = repoManager.openRepository(tipRsrc.getProject());
RevWalk rw = new RevWalk(repo)) {
List<PatchSetData> chain = getChainForCurrentPatchSet(tipRsrc);
+ if (chain.size() <= 1) {
+ return description;
+ }
PatchSetData oldestAncestor = chain.get(0);
if (rebaseUtil.canRebase(
oldestAncestor.patchSet(), oldestAncestor.data().change().getDest(), repo, rw)) {
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteRef.java b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
index 5a84f69..b7fe46e 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteRef.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
@@ -16,6 +16,7 @@
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.gerrit.entities.RefNames.isConfigRef;
+import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.BRANCH_MODIFICATION;
import static java.lang.String.format;
import static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
@@ -41,6 +42,7 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefValidationHelper;
import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -102,56 +104,58 @@
*/
public void deleteSingleRef(ProjectState projectState, String ref, @Nullable String prefix)
throws IOException, ResourceConflictException, AuthException, PermissionBackendException {
- if (prefix != null && !ref.startsWith(R_REFS)) {
- ref = prefix + ref;
- }
+ try (RefUpdateContext ctx = RefUpdateContext.open(BRANCH_MODIFICATION)) {
+ if (prefix != null && !ref.startsWith(R_REFS)) {
+ ref = prefix + ref;
+ }
- projectState.checkStatePermitsWrite();
- permissionBackend
- .currentUser()
- .project(projectState.getNameKey())
- .ref(ref)
- .check(RefPermission.DELETE);
+ projectState.checkStatePermitsWrite();
+ permissionBackend
+ .currentUser()
+ .project(projectState.getNameKey())
+ .ref(ref)
+ .check(RefPermission.DELETE);
- try (Repository repository = repoManager.openRepository(projectState.getNameKey())) {
- RefUpdate.Result result;
- RefUpdate u = repository.updateRef(ref);
- u.setExpectedOldObjectId(repository.exactRef(ref).getObjectId());
- u.setNewObjectId(ObjectId.zeroId());
- u.setForceUpdate(true);
- refDeletionValidator.validateRefOperation(
- projectState.getName(),
- identifiedUser.get(),
- u,
- /* pushOptions */ ImmutableListMultimap.of());
- result = u.delete();
+ try (Repository repository = repoManager.openRepository(projectState.getNameKey())) {
+ RefUpdate.Result result;
+ RefUpdate u = repository.updateRef(ref);
+ u.setExpectedOldObjectId(repository.exactRef(ref).getObjectId());
+ u.setNewObjectId(ObjectId.zeroId());
+ u.setForceUpdate(true);
+ refDeletionValidator.validateRefOperation(
+ projectState.getName(),
+ identifiedUser.get(),
+ u,
+ /* pushOptions */ ImmutableListMultimap.of());
+ result = u.delete();
- switch (result) {
- case NEW:
- case NO_CHANGE:
- case FAST_FORWARD:
- case FORCED:
- referenceUpdated.fire(
- projectState.getNameKey(),
- u,
- ReceiveCommand.Type.DELETE,
- identifiedUser.get().state());
- break;
+ switch (result) {
+ case NEW:
+ case NO_CHANGE:
+ case FAST_FORWARD:
+ case FORCED:
+ referenceUpdated.fire(
+ projectState.getNameKey(),
+ u,
+ ReceiveCommand.Type.DELETE,
+ identifiedUser.get().state());
+ break;
- case REJECTED_CURRENT_BRANCH:
- logger.atFine().log("Cannot delete current branch %s: %s", ref, result.name());
- throw new ResourceConflictException("cannot delete current branch");
+ case REJECTED_CURRENT_BRANCH:
+ logger.atFine().log("Cannot delete current branch %s: %s", ref, result.name());
+ throw new ResourceConflictException("cannot delete current branch");
- case LOCK_FAILURE:
- throw new LockFailureException(String.format("Cannot delete %s", ref), u);
- case IO_FAILURE:
- case NOT_ATTEMPTED:
- case REJECTED:
- case RENAMED:
- case REJECTED_MISSING_OBJECT:
- case REJECTED_OTHER_REASON:
- default:
- throw new StorageException(String.format("Cannot delete %s: %s", ref, result.name()));
+ case LOCK_FAILURE:
+ throw new LockFailureException(String.format("Cannot delete %s", ref), u);
+ case IO_FAILURE:
+ case NOT_ATTEMPTED:
+ case REJECTED:
+ case RENAMED:
+ case REJECTED_MISSING_OBJECT:
+ case REJECTED_OTHER_REASON:
+ default:
+ throw new StorageException(String.format("Cannot delete %s: %s", ref, result.name()));
+ }
}
}
}
diff --git a/java/com/google/gerrit/server/update/context/RefUpdateContext.java b/java/com/google/gerrit/server/update/context/RefUpdateContext.java
index d1c5ff8..1144e4f 100644
--- a/java/com/google/gerrit/server/update/context/RefUpdateContext.java
+++ b/java/com/google/gerrit/server/update/context/RefUpdateContext.java
@@ -22,7 +22,7 @@
import java.util.Deque;
/**
- * Passes additional information about an operation to the {@link BatchRefUpdate#execute} method.
+ * Passes additional information about an operation to the {@code BatchRefUpdate#execute} method.
*
* <p>To pass the additional information {@link RefUpdateContext}, wraps a code into an open
* RefUpdateContext, e.g.:
@@ -34,7 +34,7 @@
* }
* }</pre>
*
- * When the {@link BatchRefUpdate#execute} method is executed, it can get all opened contexts and
+ * When the {@code BatchRefUpdate#execute} method is executed, it can get all opened contexts and
* use it for an additional actions, e.g. it can put it in the reflog.
*
* <p>The information provided by this class is used internally in google.
@@ -42,7 +42,7 @@
* <p>The InMemoryRepositoryManager file makes some validation to ensure that RefUpdateContext is
* used correctly within the code (see thee validateRefUpdateContext method).
*
- * <p>The class includes only operations from open-source gerrit and can be extended (see {@link
+ * <p>The class includes only operations from open-source gerrit and can be extended (see {@code
* TestActionRefUpdateContext} for example how to extend it).
*/
public class RefUpdateContext implements AutoCloseable {
@@ -88,7 +88,7 @@
ACCOUNTS_UPDATE,
/** A ref is updated as a part of direct push. */
DIRECT_PUSH,
- /** A ref is updated as a part of explicit branch update operation. */
+ /** A ref is updated as a part of explicit branch or ref update operation. */
BRANCH_MODIFICATION,
/** A ref is updated as a part of explicit tag update operation. */
TAG_MODIFICATION,
@@ -106,7 +106,13 @@
/** A ref is updated as a part of versioned meta data change. */
VERSIONED_META_DATA_CHANGE,
/** A ref is updated as a part of commit-ban operation. */
- BAN_COMMIT
+ BAN_COMMIT,
+ /**
+ * A ref is updated inside a plugin.
+ *
+ * <p>If a plugin updates one of a special refs - it must also open a nested context.
+ */
+ PLUGIN,
}
/** Opens a provided context. */
diff --git a/java/com/google/gerrit/testing/InMemoryRepositoryManager.java b/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
index 4fe6ff7..8d1130c 100644
--- a/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
+++ b/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
@@ -26,6 +26,7 @@
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.INIT_REPO;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.MERGE_CHANGE;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.OFFLINE_OPERATION;
+import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.PLUGIN;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.REPO_SEQ;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.TAG_MODIFICATION;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.VERSIONED_META_DATA_CHANGE;
@@ -160,6 +161,8 @@
RefUpdateContext.hasOpen(MERGE_CHANGE)
|| RefUpdateContext.hasOpen(RefUpdateType.BRANCH_MODIFICATION)
|| RefUpdateContext.hasOpen(RefUpdateType.UPDATE_SUPERPROJECT)
+ // Plugin can update any ref
+ || RefUpdateContext.hasOpen(PLUGIN)
|| isTestRepoCall(),
"Ordinary ref '%s' is updated outside of the expected operation. Wrap code in the correct RefUpdateContext or add the ref as a special ref.",
refName);
@@ -168,7 +171,7 @@
private RefUpdateContextValidator addSpecialRef(
Predicate<String> refNamePredicate, RefUpdateType... validRefUpdateTypes) {
specialRefs.add(
- new SimpleImmutableEntry<Predicate<String>, ImmutableList<RefUpdateType>>(
+ new SimpleImmutableEntry<>(
refNamePredicate, ImmutableList.copyOf(validRefUpdateTypes)));
return this;
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java b/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java
index 898e1ff..0b55563 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java
@@ -213,6 +213,29 @@
}
@Test
+ public void applyGerritBasedPatchUsingRestWithEncodedPatch_success() throws Exception {
+ String head = getHead(repo(), HEAD).name();
+ createBranchWithRevision(BranchNameKey.create(project, "branch"), head);
+ PushOneCommit.Result baseCommit = createChange("Add file", ADDED_FILE_NAME, ADDED_FILE_CONTENT);
+ baseCommit.assertOkStatus();
+ createBranchWithRevision(BranchNameKey.create(project, DESTINATION_BRANCH), head);
+ RestResponse patchResp =
+ userRestSession.get("/changes/" + baseCommit.getChangeId() + "/revisions/current/patch");
+ patchResp.assertOK();
+ String originalEncodedPatch = patchResp.getEntityContent();
+ String originalDecodedPatch = new String(Base64.decode(patchResp.getEntityContent()), UTF_8);
+ ApplyPatchPatchSetInput in = buildInput(originalEncodedPatch);
+ PushOneCommit.Result destChange = createChange();
+
+ RestResponse resp =
+ adminRestSession.post("/changes/" + destChange.getChangeId() + "/patch:apply", in);
+
+ resp.assertOK();
+ BinaryResult resultPatch = gApi.changes().id(destChange.getChangeId()).current().patch();
+ assertThat(removeHeader(resultPatch)).isEqualTo(removeHeader(originalDecodedPatch));
+ }
+
+ @Test
public void applyPatchWithConflict_fails() throws Exception {
initBaseWithFile(MODIFIED_FILE_NAME, "Unexpected base content");
ApplyPatchPatchSetInput in = buildInput(MODIFIED_FILE_DIFF);
@@ -404,6 +427,6 @@
}
private String removeHeader(String s) {
- return s.substring(s.indexOf("\ndiff --git"), s.length() - 1);
+ return s.substring(s.lastIndexOf("\ndiff --git"), s.length() - 1);
}
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index 342b876..19207bc 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -42,6 +42,7 @@
import {classMap} from 'lit/directives/class-map.js';
import {createSearchUrl} from '../../../models/views/search';
import {createChangeUrl} from '../../../models/views/change';
+import {userModelToken} from '../../../models/user/user-model';
import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
enum ChangeSize {
@@ -94,9 +95,6 @@
sectionName?: string;
@property({type: Boolean})
- showStar = false;
-
- @property({type: Boolean})
showNumber = false;
@property({type: String})
@@ -125,6 +123,10 @@
private readonly getNavigation = resolve(this, navigationToken);
+ private readonly getUserModel = resolve(this, userModelToken);
+
+ @state() private isLoggedIn = false;
+
constructor() {
super();
subscribe(
@@ -134,6 +136,11 @@
this.updateCheckedState(selectedChangeNums);
}
);
+ subscribe(
+ this,
+ () => this.getUserModel().loggedIn$,
+ isLoggedIn => (this.isLoggedIn = isLoggedIn)
+ );
}
override connectedCallback() {
@@ -332,6 +339,8 @@
}
private renderCellSelectionBox() {
+ if (!this.isLoggedIn) return;
+
return html`
<td class="cell selection">
<!--
@@ -352,7 +361,7 @@
}
private renderCellStar() {
- if (!this.showStar) return;
+ if (!this.isLoggedIn) return;
return html`
<td class="cell star">
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
index c7cb5b8..5e31cc8 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
@@ -8,9 +8,11 @@
import {
SubmitRequirementResultInfo,
NumericChangeId,
+ Timestamp,
} from '../../../api/rest-api';
import '../../../test/common-test-setup';
import {
+ createAccountWithEmail,
createAccountWithId,
createChange,
createSubmitRequirementExpressionInfo,
@@ -21,7 +23,6 @@
import {
query,
queryAndAssert,
- stubRestApi,
waitUntilObserved,
} from '../../../test/test-utils';
import {
@@ -43,6 +44,7 @@
bulkActionsModelToken,
BulkActionsModel,
} from '../../../models/bulk-actions/bulk-actions-model';
+import {UserModel, userModelToken} from '../../../models/user/user-model';
import {createTestAppContext} from '../../../test/test-app-context-init';
import {ColumnNames} from '../../../constants/constants';
import {testResolver} from '../../../test/common-test-setup';
@@ -58,13 +60,13 @@
let element: GrChangeListItem;
let bulkActionsModel: BulkActionsModel;
+ let userModel: UserModel;
setup(async () => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(false));
-
bulkActionsModel = new BulkActionsModel(
createTestAppContext().restApiService
);
+ userModel = testResolver(userModelToken);
element = (
await fixture<DIProviderElement>(
wrapInProvider(
@@ -105,6 +107,10 @@
test('bulk actions checkboxes', async () => {
element.change = {...createChange(), _number: 1 as NumericChangeId};
bulkActionsModel.sync([element.change]);
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
const checkbox = queryAndAssert<HTMLInputElement>(
@@ -134,6 +140,10 @@
element.globalIndex = 5;
element.change = {...createChange(), _number: 1 as NumericChangeId};
bulkActionsModel.sync([element.change]);
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
const checkbox = queryAndAssert<HTMLInputElement>(
@@ -147,6 +157,10 @@
});
test('checkbox state updates with model updates', async () => {
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
element.requestUpdate();
await element.updateComplete;
@@ -168,6 +182,10 @@
});
test('checkbox state updates with change id update', async () => {
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
element.requestUpdate();
await element.updateComplete;
@@ -361,7 +379,10 @@
const change = createChange();
bulkActionsModel.sync([change]);
bulkActionsModel.addSelectedChangeNum(change._number);
- element.showStar = true;
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
element.showNumber = true;
element.account = createAccountWithId(1);
element.config = createServerInfo();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts
index 8227e11..61b276e 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts
@@ -15,14 +15,15 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {Metadata} from '../../../utils/change-metadata-util';
import {WAITING} from '../../../constants/constants';
-import {provide} from '../../../models/dependency';
+import {provide, resolve} from '../../../models/dependency';
import {
bulkActionsModelToken,
BulkActionsModel,
} from '../../../models/bulk-actions/bulk-actions-model';
+import {createSearchUrl} from '../../../models/views/search';
+import {userModelToken} from '../../../models/user/user-model';
import {subscribe} from '../../lit/subscription-controller';
import {classMap} from 'lit/directives/class-map.js';
-import {createSearchUrl} from '../../../models/views/search';
const NUMBER_FIXED_COLUMNS = 4;
const LABEL_PREFIX_INVALID_PROLOG = 'Invalid-Prolog-Rules-Label-Name--';
@@ -52,9 +53,6 @@
visibleChangeTableColumns?: string[];
@property({type: Boolean})
- showStar = false;
-
- @property({type: Boolean})
showNumber?: boolean; // No default value to prevent flickering.
@property({type: Number})
@@ -104,6 +102,10 @@
getAppContext().restApiService
);
+ private readonly getUserModel = resolve(this, userModelToken);
+
+ private isLoggedIn = false;
+
static override get styles() {
return [
changeListStyles,
@@ -156,6 +158,11 @@
() => this.bulkActionsModel.totalChangeCount$,
totalChangeCount => (this.totalChangeCount = totalChangeCount)
);
+ subscribe(
+ this,
+ () => this.getUserModel().loggedIn$,
+ isLoggedIn => (this.isLoggedIn = isLoggedIn)
+ );
}
override willUpdate(changedProperties: PropertyValues) {
@@ -189,8 +196,8 @@
<td class="leftPadding" aria-hidden="true"></td>
<td
class="star"
- ?aria-hidden=${!this.showStar}
- ?hidden=${!this.showStar}
+ ?aria-hidden=${!this.isLoggedIn}
+ ?hidden=${!this.isLoggedIn}
></td>
<td class="cell" colspan=${colSpan}>
${this.changeSection.emptyStateSlotName
@@ -213,7 +220,7 @@
<tbody>
<tr class="groupHeader">
<td aria-hidden="true" class="leftPadding"></td>
- <td aria-hidden="true" class="star" ?hidden=${!this.showStar}></td>
+ <td aria-hidden="true" class="star" ?hidden=${!this.isLoggedIn}></td>
<td class="cell" colspan=${colSpan}>
<h2 class="heading-3">
<a
@@ -248,7 +255,7 @@
: html` <td
class="star"
aria-label="Star status column"
- ?hidden=${!this.showStar}
+ ?hidden=${!this.isLoggedIn}
></td>
<td class="number" ?hidden=${!this.showNumber}>#</td>
${columns.map(item => this.renderHeaderCell(item))}
@@ -267,7 +274,7 @@
const indeterminate =
this.numSelected > 0 && this.numSelected !== this.totalChangeCount;
return html`
- <td class="selection">
+ <td class="selection" ?hidden=${!this.isLoggedIn}>
<!--
The .checked property must be used rather than the attribute because
the attribute only controls the default checked state and does not
@@ -322,7 +329,6 @@
.sectionName=${this.changeSection.name}
.visibleChangeTableColumns=${columns}
.showNumber=${this.showNumber}
- ?showStar=${this.showStar}
.usp=${this.usp}
.labelNames=${this.labelNames}
.globalIndex=${this.startIndex + index}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section_test.ts
index 8dfecdc..63552c7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section_test.ts
@@ -13,9 +13,10 @@
import {
createChange,
createAccountDetailWithId,
+ createAccountWithEmail,
createServerInfo,
} from '../../../test/test-data-generators';
-import {NumericChangeId, ChangeInfoId} from '../../../api/rest-api';
+import {ChangeInfoId, NumericChangeId, Timestamp} from '../../../api/rest-api';
import {
queryAll,
query,
@@ -27,11 +28,15 @@
import {ChangeListSection} from '../gr-change-list/gr-change-list';
import {fixture, html, assert} from '@open-wc/testing';
import {ColumnNames} from '../../../constants/constants';
+import {testResolver} from '../../../test/common-test-setup';
+import {UserModel, userModelToken} from '../../../models/user/user-model';
suite('gr-change-list section', () => {
let element: GrChangeListSection;
+ let userModel: UserModel;
setup(async () => {
+ userModel = testResolver(userModelToken);
const changeSection: ChangeListSection = {
name: 'test',
query: 'test',
@@ -193,6 +198,10 @@
],
emptyStateSlotName: 'test',
};
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
let rows = queryAll(element, 'gr-change-list-item');
assert.lengthOf(rows, 2);
@@ -235,6 +244,10 @@
],
emptyStateSlotName: 'test',
};
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
const rows = queryAll(element, 'gr-change-list-item');
@@ -273,6 +286,31 @@
});
});
+ test('no checkbox when logged out', async () => {
+ element.changeSection = {
+ name: 'test',
+ query: 'test',
+ results: [
+ {
+ ...createChange(),
+ _number: 1 as NumericChangeId,
+ id: '1' as ChangeInfoId,
+ },
+ {
+ ...createChange(),
+ _number: 2 as NumericChangeId,
+ id: '2' as ChangeInfoId,
+ },
+ ],
+ emptyStateSlotName: 'test',
+ };
+ userModel.setAccount(undefined);
+ await element.updateComplete;
+ const rows = queryAll(element, 'gr-change-list-item');
+ assert.lengthOf(rows, 2);
+ assert.isUndefined(query<HTMLInputElement>(rows[0], 'input'));
+ });
+
test('colspans', async () => {
element.visibleChangeTableColumns = [];
element.changeSection = {results: [{...createChange()}]};
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index d2ba2c9..1c86354 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -195,7 +195,6 @@
.account=${this.account}
.changes=${this.changes}
.preferences=${this.preferences}
- .showStar=${this.loggedIn}
@toggle-star=${(e: CustomEvent<ChangeStarToggleStarDetail>) => {
this.handleToggleStar(e);
}}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index 4c43da5..748c2b8 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -113,9 +113,6 @@
showNumber?: boolean; // No default value to prevent flickering.
@property({type: Boolean})
- showStar = false;
-
- @property({type: Boolean})
showReviewedState = false;
@property({type: Array})
@@ -270,7 +267,6 @@
sectionIndex,
this.sections
)}
- ?showStar=${this.showStar}
.showNumber=${this.showNumber}
.visibleChangeTableColumns=${this.visibleChangeTableColumns}
.usp=${this.usp}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
index bef3166..7e9735f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
@@ -23,6 +23,7 @@
} from '../../../constants/constants';
import {AccountId, NumericChangeId} from '../../../types/common';
import {
+ createAccountWithEmail,
createChange,
createServerInfo,
createSubmitRequirementResultInfo,
@@ -32,12 +33,16 @@
import {fixture, assert} from '@open-wc/testing';
import {html} from 'lit';
import {testResolver} from '../../../test/common-test-setup';
+import {Timestamp} from '../../../api/rest-api';
+import {UserModel, userModelToken} from '../../../models/user/user-model';
suite('gr-change-list basic tests', () => {
let element: GrChangeList;
+ let userModel: UserModel;
setup(async () => {
element = await fixture(html`<gr-change-list></gr-change-list>`);
+ userModel = testResolver(userModelToken);
});
test('renders', async () => {
@@ -285,6 +290,12 @@
});
test('toggle checkbox keyboard shortcut', async () => {
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
+ await element.updateComplete;
+
const getCheckbox = (item: GrChangeListItem) =>
queryAndAssert<HTMLInputElement>(query(item, '.selection'), 'input');
@@ -515,7 +526,7 @@
assert.isTrue(element.isColumnEnabled('Subject'));
});
- test('showStar and showNumber', async () => {
+ test('loggedIn and showNumber', async () => {
element.sections = [{results: [{...createChange()}], name: 'a'}];
element.account = {_account_id: 1001 as AccountId};
element.preferences = {
@@ -534,6 +545,7 @@
],
};
element.config = createServerInfo();
+ userModel.setAccount(undefined);
await element.updateComplete;
const section = query<GrChangeListSection>(
element,
@@ -547,7 +559,10 @@
assert.isNotOk(query(query(section, 'gr-change-list-item'), '.star'));
assert.isNotOk(query(query(section, 'gr-change-list-item'), '.number'));
- element.showStar = true;
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
await section.updateComplete;
assert.isOk(query(query(section, 'gr-change-list-item'), '.star'));
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index d013654..cd30440 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -284,7 +284,6 @@
${this.renderUserHeader()}
<h1 class="assistive-tech-only">Dashboard</h1>
<gr-change-list
- ?showStar=${true}
.account=${this.account}
.preferences=${this.preferences}
.sections=${this.results}
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
index 17d7e95..84a3139 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
@@ -82,7 +82,7 @@
<div class="loading" hidden="">Loading...</div>
<div>
<h1 class="assistive-tech-only">Dashboard</h1>
- <gr-change-list showstar="">
+ <gr-change-list>
<div id="emptyOutgoing" slot="outgoing-slot">No changes</div>
<div id="emptyYourTurn" slot="your-turn-slot">
<span> No changes need your attention  ðŸŽ‰ </span>
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 6f8bd9a..c2739f3 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
@@ -218,7 +218,6 @@
<gr-autocomplete
id="parentInput"
.query=${this.query}
- no-debounce
.text=${this.text}
@text-changed=${(e: ValueChangedEvent) =>
(this.text = e.detail.value)}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
index 776e923..2644d81 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
@@ -74,7 +74,6 @@
<gr-autocomplete
allow-non-suggested-values=""
id="parentInput"
- no-debounce=""
placeholder="Change number, ref, or commit hash"
>
</gr-autocomplete>
@@ -305,7 +304,6 @@
test('input text change triggers function', async () => {
const recentChangesSpy = sinon.spy(element, 'getRecentChanges');
- element.parentInput.noDebounce = true;
pressKey(
queryAndAssert(queryAndAssert(element, '#parentInput'), '#input'),
Key.ENTER
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 fbc87ed..314e126 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
@@ -278,7 +278,7 @@
// fallback to gerrit's official doc
let baseUrl =
this.docsBaseUrl ||
- 'https://gerrit-review.googlesource.com/documentation/';
+ 'https://gerrit-review.googlesource.com/Documentation/';
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
}
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
index 43b1f86..0694453 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
@@ -319,7 +319,7 @@
await element.updateComplete;
assert.equal(
element.computeHelpDocLink(),
- 'https://gerrit-review.googlesource.com/documentation/' +
+ 'https://gerrit-review.googlesource.com/Documentation/' +
'user-search.html'
);
});
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
index 31283ad..0729f21 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
@@ -217,16 +217,20 @@
assert.isFalse(hideDialogStub.called);
queryAndAssert<GrButton>(element, '#open').click();
element.patchNum = 1 as RevisionPatchSetNum;
- await waitUntilVisible(element.modal!);
+ await showDialogSpy.lastCall.returnValue;
assert.isTrue(hideDialogStub.called);
assert.isTrue(element.openDialog!.disabled);
assert.isFalse(queryStub.called);
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
openAutoComplete.focused = true;
- openAutoComplete.noDebounce = true;
openAutoComplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ openAutoComplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await openAutoComplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
await waitUntil(() => !element.openDialog!.disabled);
queryAndAssert<GrButton>(
@@ -242,7 +246,6 @@
queryAndAssert<GrButton>(element, '#open').click();
await waitUntilVisible(element.modal!);
assert.isTrue(element.openDialog!.disabled);
- openAutoComplete.noDebounce = true;
openAutoComplete.text = 'src/test.cpp';
await element.updateComplete;
await waitUntil(() => !element.openDialog!.disabled);
@@ -277,9 +280,13 @@
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
deleteAutocomplete.focused = true;
- deleteAutocomplete.noDebounce = true;
deleteAutocomplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ deleteAutocomplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await deleteAutocomplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
await waitUntil(() => !element.deleteDialog!.disabled);
queryAndAssert<GrButton>(
@@ -304,9 +311,13 @@
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
deleteAutocomplete.focused = true;
- deleteAutocomplete.noDebounce = true;
deleteAutocomplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ deleteAutocomplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await deleteAutocomplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
await waitUntil(() => !element.deleteDialog!.disabled);
queryAndAssert<GrButton>(
@@ -363,9 +374,13 @@
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
renameAutocomplete.focused = true;
- renameAutocomplete.noDebounce = true;
renameAutocomplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ renameAutocomplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await renameAutocomplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
assert.isTrue(element.renameDialog!.disabled);
@@ -395,9 +410,13 @@
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
renameAutocomplete.focused = true;
- renameAutocomplete.noDebounce = true;
renameAutocomplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ renameAutocomplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await renameAutocomplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
assert.isTrue(element.renameDialog!.disabled);
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index b096f76..e5991f6 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -500,11 +500,12 @@
}
private renderEditorView() {
- // The `cache()` is required for re-using the editor view when switching
- // back and forth between change, diff and editor views.
- return cache(
- this.isEditorView() ? html`<gr-editor-view></gr-editor-view>` : nothing
- );
+ // For some reason caching the editor view caused an issue (b/269308770).
+ // We did not bother to root cause that issue, but instead let's forgo
+ // caching of the editor view. It does not help much anyway.
+ return this.isEditorView()
+ ? html`<gr-editor-view></gr-editor-view>`
+ : nothing;
}
private isEditorView() {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
index 6b4d670..cc2723e 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
@@ -401,8 +401,8 @@
);
input.text = 'newTest';
input.input!.focus();
- input.noDebounce = true;
await element.updateComplete;
+ await input.latestSuggestionUpdateComplete;
assert.isTrue(getSuggestionsStub.calledOnce);
assert.equal(getSuggestionsStub.lastCall.args[0], 'newTest');
await waitUntil(() => makeSuggestionItemSpy.getCalls().length === 2);
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 de97741..f8f7f9d 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -9,7 +9,11 @@
import '../../../styles/shared-styles';
import {GrAutocompleteDropdown} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
import {fire, fireEvent} from '../../../utils/event-util';
-import {debounce, DelayedTask} from '../../../utils/async-util';
+import {
+ debounce,
+ DelayedTask,
+ ResolvedDelayedTaskStatus,
+} from '../../../utils/async-util';
import {PropertyType} from '../../../types/common';
import {modifierPressed} from '../../../utils/dom-util';
import {sharedStyles} from '../../../styles/shared-styles';
@@ -150,12 +154,6 @@
@property({type: Boolean, attribute: 'warn-uncommitted'})
warnUncommitted = false;
- /**
- * When true, querying for suggestions is not debounced w/r/t keypresses
- */
- @property({type: Boolean, attribute: 'no-debounce'})
- noDebounce = false;
-
@property({type: Boolean, attribute: 'show-blue-focus-border'})
showBlueFocusBorder = false;
@@ -183,6 +181,15 @@
private updateSuggestionsTask?: DelayedTask;
+ /**
+ * @return Promise that resolves when suggestions are update.
+ */
+ get latestSuggestionUpdateComplete():
+ | Promise<ResolvedDelayedTaskStatus>
+ | undefined {
+ return this.updateSuggestionsTask?.promise;
+ }
+
get nativeInput() {
return (this.input!.inputElement as IronInputElement)
.inputElement as HTMLInputElement;
@@ -254,11 +261,7 @@
}
override willUpdate(changedProperties: PropertyValues) {
- if (
- changedProperties.has('text') ||
- changedProperties.has('threshold') ||
- changedProperties.has('noDebounce')
- ) {
+ if (changedProperties.has('text') || changedProperties.has('threshold')) {
this.updateSuggestions();
}
if (
@@ -404,12 +407,7 @@
}
updateSuggestions() {
- if (
- this.text === undefined ||
- this.threshold === undefined ||
- this.noDebounce === undefined
- )
- return;
+ if (this.text === undefined || this.threshold === undefined) return;
// Reset suggestions for every update
// This will also prevent from carrying over suggestions:
@@ -437,10 +435,11 @@
return;
}
+ const requestText = this.text;
const update = () => {
query(this.text)
.then(suggestions => {
- if (this.text !== this.text) {
+ if (requestText !== this.text) {
// Late response.
return;
}
@@ -462,15 +461,11 @@
});
};
- if (this.noDebounce) {
- update();
- } else {
- this.updateSuggestionsTask = debounce(
- this.updateSuggestionsTask,
- update,
- DEBOUNCE_WAIT_MS
- );
- }
+ this.updateSuggestionsTask = debounce(
+ this.updateSuggestionsTask,
+ update,
+ DEBOUNCE_WAIT_MS
+ );
}
setFocus(focused: boolean) {
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 eb1efec..81949c7 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
@@ -391,7 +391,6 @@
element.query = queryStub;
await element.updateComplete;
- element.noDebounce = false;
focusOnInput();
element.text = 'a';
@@ -413,7 +412,6 @@
test('empty text results in no suggestions', async () => {
element.text = '';
element.threshold = 0;
- element.noDebounce = false;
await element.updateComplete;
assert.equal(element.suggestions.length, 0);
});
@@ -475,7 +473,6 @@
assert.equal(element.suggestions.length, 1);
element.text = '';
element.threshold = 0;
- element.noDebounce = false;
await element.updateComplete;
assert.equal(element.suggestions.length, 0);
});
@@ -494,7 +491,6 @@
await waitUntil(() => element.queryErrorMessage === 'Test error');
element.text = '';
element.threshold = 0;
- element.noDebounce = false;
await element.updateComplete;
assert.isUndefined(element.queryErrorMessage);
});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
index 13a6b00..b97ae59 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
@@ -160,6 +160,8 @@
${this.renderLineNumberCell(Side.RIGHT)}
${this.renderSignCell(Side.RIGHT)} ${this.renderContentCell(Side.RIGHT)}
</tr>
+ ${this.renderPostLineSlot(Side.LEFT)}
+ ${this.renderPostLineSlot(Side.RIGHT)}
`;
if (this.addTableWrapperForTesting) {
return html`<table>
@@ -456,6 +458,13 @@
id=${this.contentId(side)}
>${textElement}</div>`;
}
+
+ private renderPostLineSlot(side: Side) {
+ const lineNumber = this.lineNumber(side);
+ return lineNumber && Number.isInteger(lineNumber)
+ ? html`<slot name="post-${side}-line-${lineNumber}"></slot>`
+ : nothing;
+ }
}
declare global {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts
index 1c7b311..42d30aa 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts
@@ -87,6 +87,8 @@
</div>
</td>
</tr>
+ <slot name="post-left-line-1"></slot>
+ <slot name="post-right-line-1"></slot>
</tbody>
</table>
`
@@ -147,6 +149,8 @@
</div>
</td>
</tr>
+ <slot name="post-left-line-1"></slot>
+ <slot name="post-right-line-1"></slot>
</tbody>
</table>
`
@@ -201,6 +205,7 @@
<slot name="right-1"> </slot>
</div>
</td>
+ <slot name="post-right-line-1"></slot>
</tr>
</tbody>
</table>
@@ -257,6 +262,7 @@
<div class="contentText gr-diff" data-side="right"></div>
</td>
</tr>
+ <slot name="post-left-line-1"></slot>
</tbody>
</table>
`
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts
index 14f4536..381f9b2 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts
@@ -126,8 +126,14 @@
element,
/* HTML */ `
<gr-diff-row class="left-1 right-1"> </gr-diff-row>
+ <slot name="post-left-line-1"></slot>
+ <slot name="post-right-line-1"></slot>
<gr-diff-row class="left-1 right-1"> </gr-diff-row>
+ <slot name="post-left-line-1"></slot>
+ <slot name="post-right-line-1"></slot>
<gr-diff-row class="left-1 right-1"> </gr-diff-row>
+ <slot name="post-left-line-1"></slot>
+ <slot name="post-right-line-1"></slot>
<table>
<tbody class="both gr-diff section">
<tr
diff --git a/polygerrit-ui/app/utils/async-util.ts b/polygerrit-ui/app/utils/async-util.ts
index a4bc98e..752de62 100644
--- a/polygerrit-ui/app/utils/async-util.ts
+++ b/polygerrit-ui/app/utils/async-util.ts
@@ -37,6 +37,11 @@
export const _testOnly_allTasks = new Map<number, DelayedTask>();
+export enum ResolvedDelayedTaskStatus {
+ CALLBACK_EXECUTED = 'CALLBACK_EXECUTED',
+ TASK_CANCELLED = 'TASK_CANCELLED',
+}
+
/**
* This is just a very simple and small wrapper around setTimeout(). Instead of
* the usual:
@@ -55,24 +60,23 @@
private timerId?: number;
/**
- * Promise that is resolved after the callback is run.
- * If the task is cancelled the promise is rejected instead.
+ * Promise that is resolved after the callback is run or the task is
+ * cancelled.
*/
- public readonly promise: Promise<void>;
+ public readonly promise: Promise<ResolvedDelayedTaskStatus>;
- private rejectPromise?: () => void;
-
- private resolvePromise?: () => void;
+ private resolvePromise?: (
+ value: ResolvedDelayedTaskStatus | PromiseLike<ResolvedDelayedTaskStatus>
+ ) => void;
constructor(private callback: () => void, waitMs = 0) {
- this.promise = new Promise((resolve, reject) => {
- this.rejectPromise = reject;
+ this.promise = new Promise(resolve => {
this.resolvePromise = resolve;
this.timerId = window.setTimeout(() => {
if (this.timerId) _testOnly_allTasks.delete(this.timerId);
this.timerId = undefined;
if (this.callback) this.callback();
- resolve();
+ resolve(ResolvedDelayedTaskStatus.CALLBACK_EXECUTED);
}, waitMs);
_testOnly_allTasks.set(this.timerId, this);
});
@@ -87,7 +91,7 @@
cancel() {
if (this.isActive()) {
this.cancelTimer();
- this.rejectPromise?.();
+ this.resolvePromise?.(ResolvedDelayedTaskStatus.TASK_CANCELLED);
}
}
@@ -95,7 +99,7 @@
if (this.isActive()) {
this.cancelTimer();
if (this.callback) this.callback();
- this.resolvePromise?.();
+ this.resolvePromise?.(ResolvedDelayedTaskStatus.CALLBACK_EXECUTED);
}
}