Merge "Specify project in URLs"
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CorsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CorsIT.java
index bd9c98d..861a22c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CorsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CorsIT.java
@@ -212,6 +212,11 @@
Header allowOrigin = r.getFirstHeader(ACCESS_CONTROL_ALLOW_ORIGIN);
assertThat(allowOrigin).named(ACCESS_CONTROL_ALLOW_ORIGIN).isNotNull();
assertThat(allowOrigin.getValue()).named(ACCESS_CONTROL_ALLOW_ORIGIN).isEqualTo(origin);
+
+ Header allowAuth = r.getFirstHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS);
+ assertThat(allowAuth).named(ACCESS_CONTROL_ALLOW_CREDENTIALS).isNotNull();
+ assertThat(allowAuth.getValue()).named(ACCESS_CONTROL_ALLOW_CREDENTIALS).isEqualTo("true");
+
checkTopic(change, "test-xd");
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 8adf5f5..5b1045a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -554,6 +554,7 @@
}
res.addHeader(VARY, ORIGIN);
res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+ res.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
} else if (!Strings.isNullOrEmpty(origin)) {
// All other requests must be processed, but conditionally set CORS headers.
if (globals.allowOrigin != null) {
@@ -591,7 +592,6 @@
String headers = req.getHeader(ACCESS_CONTROL_REQUEST_HEADERS);
if (headers != null) {
- res.addHeader(VARY, ACCESS_CONTROL_REQUEST_HEADERS);
for (String reqHdr : Splitter.on(',').trimResults().split(headers)) {
if (!ALLOWED_CORS_REQUEST_HEADERS.contains(reqHdr.toLowerCase(Locale.US))) {
throw new BadRequestException(reqHdr + " not allowed in CORS");
@@ -1148,7 +1148,6 @@
CurrentUser user = globals.currentUser.get();
if (isRead(req)) {
user.setAccessPath(AccessPath.REST_API);
- user.setLastLoginExternalIdKey(globals.webSession.get().getLastLoginExternalId());
} else if (user instanceof AnonymousUser) {
throw new AuthException("Authentication required");
} else if (!globals.webSession.get().isAccessPathOk(AccessPath.REST_API)) {
@@ -1156,6 +1155,9 @@
"Invalid authentication method. In order to authenticate, "
+ "prefix the REST endpoint URL with /a/ (e.g. http://example.com/a/projects/).");
}
+ if (user.isIdentifiedUser()) {
+ user.setLastLoginExternalIdKey(globals.webSession.get().getLastLoginExternalId());
+ }
}
private static boolean isRead(HttpServletRequest req) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java
index 340e35e..0d42ee5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.index;
import com.google.common.collect.ImmutableSortedMap;
-import com.google.inject.Provider;
+import com.google.gerrit.common.Nullable;
/**
* Definition of an index over a Gerrit data type.
@@ -33,13 +33,13 @@
private final SchemaDefinitions<V> schemaDefs;
private final IndexCollection<K, V, I> indexCollection;
private final IndexFactory<K, V, I> indexFactory;
- private final Provider<SiteIndexer<K, V, I>> siteIndexer;
+ private final SiteIndexer<K, V, I> siteIndexer;
protected IndexDefinition(
SchemaDefinitions<V> schemaDefs,
IndexCollection<K, V, I> indexCollection,
IndexFactory<K, V, I> indexFactory,
- Provider<SiteIndexer<K, V, I>> siteIndexer) {
+ @Nullable SiteIndexer<K, V, I> siteIndexer) {
this.schemaDefs = schemaDefs;
this.indexCollection = indexCollection;
this.indexFactory = indexFactory;
@@ -66,7 +66,8 @@
return indexFactory;
}
+ @Nullable
public final SiteIndexer<K, V, I> getSiteIndexer() {
- return siteIndexer.get();
+ return siteIndexer;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
index 72f23be..25bf541 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
@@ -19,7 +19,6 @@
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.index.IndexDefinition;
import com.google.inject.Inject;
-import com.google.inject.util.Providers;
public class AccountIndexDefinition
extends IndexDefinition<Account.Id, AccountState, AccountIndex> {
@@ -29,10 +28,6 @@
AccountIndexCollection indexCollection,
AccountIndex.Factory indexFactory,
@Nullable AllAccountsIndexer allAccountsIndexer) {
- super(
- AccountSchemaDefinitions.INSTANCE,
- indexCollection,
- indexFactory,
- Providers.of(allAccountsIndexer));
+ super(AccountSchemaDefinitions.INSTANCE, indexCollection, indexFactory, allAccountsIndexer);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
index 4404298..8b63a1d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
@@ -19,7 +19,6 @@
import com.google.gerrit.server.index.IndexDefinition;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
-import com.google.inject.util.Providers;
public class ChangeIndexDefinition extends IndexDefinition<Change.Id, ChangeData, ChangeIndex> {
@@ -28,10 +27,6 @@
ChangeIndexCollection indexCollection,
ChangeIndex.Factory indexFactory,
@Nullable AllChangesIndexer allChangesIndexer) {
- super(
- ChangeSchemaDefinitions.INSTANCE,
- indexCollection,
- indexFactory,
- Providers.of(allChangesIndexer));
+ super(ChangeSchemaDefinitions.INSTANCE, indexCollection, indexFactory, allChangesIndexer);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
index 0dbea79..8e15b5e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
@@ -18,7 +18,6 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.index.IndexDefinition;
import com.google.inject.Inject;
-import com.google.inject.util.Providers;
public class GroupIndexDefinition
extends IndexDefinition<AccountGroup.UUID, AccountGroup, GroupIndex> {
@@ -28,10 +27,6 @@
GroupIndexCollection indexCollection,
GroupIndex.Factory indexFactory,
@Nullable AllGroupsIndexer allGroupsIndexer) {
- super(
- GroupSchemaDefinitions.INSTANCE,
- indexCollection,
- indexFactory,
- Providers.of(allGroupsIndexer));
+ super(GroupSchemaDefinitions.INSTANCE, indexCollection, indexFactory, allGroupsIndexer);
}
}
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
index ec7541b..faf1489 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
@@ -30,6 +30,8 @@
/** @polymerBehavior Gerrit.PatchSetBehavior */
const PatchSetBehavior = {
+ // Corresponds to the patchNum for an edit patchset.
+ EDIT_NAME: 'edit',
/**
* As patchNum can be either a string (e.g. 'edit', 'PARENT') OR a number,
@@ -58,6 +60,55 @@
},
/**
+ * Find change edit base revision if change edit exists.
+ *
+ * @param {!Array<!Object>} revisions The revisions array.
+ * @return {Object} change edit parent revision or null if change edit
+ * doesn't exist.
+ */
+ findEditParentRevision(revisions) {
+ const editInfo =
+ revisions.find(info => info._number === PatchSetBehavior.EDIT_NAME);
+
+ if (!editInfo) { return null; }
+
+ return revisions.find(info => info._number === editInfo.basePatchNum) ||
+ null;
+ },
+
+ /**
+ * Find change edit base patch set number if change edit exists.
+ *
+ * @param {!Array<!Object>} revisions The revisions array.
+ * @return {number} Change edit patch set number or -1.
+ */
+ findEditParentPatchNum(revisions) {
+ const revisionInfo = PatchSetBehavior.findEditParentRevision(revisions);
+ return revisionInfo ? revisionInfo._number : -1;
+ },
+
+ /**
+ * Sort given revisions array according to the patch set number. The sort
+ * algorithm is change edit aware. Change edit has patch set number equals
+ * 0, but must appear after the patch set it was based on. Example: change
+ * edit is based on patch set 2, and another patch set was uploaded after
+ * change edit creation, the sorted order should be: 1, 2, (0|edit), 3.
+ *
+ * @param {!Array<!Object>} revisions The revisions array
+ * @return {!Array<!Object>} The sorted {revisions} array
+ */
+ sortRevisions(revisions) {
+ const editParent = PatchSetBehavior.findEditParentPatchNum(revisions);
+ // Map a normal patchNum to 2 * (patchNum - 1) + 1... I.e. 1 -> 1,
+ // 2 -> 3, 3 -> 5, etc.
+ // Map an edit to the patchNum of parent*2... I.e. edit on 2 -> 4.
+ const num = r => r._number === PatchSetBehavior.EDIT_NAME ?
+ 2 * editParent :
+ 2 * (r._number - 1) + 1;
+ return revisions.sort((a, b) => num(a) - num(b));
+ },
+
+ /**
* Construct a chronological list of patch sets derived from change details.
* Each element of this list is an object with the following properties:
*
@@ -68,32 +119,37 @@
* The wip property is determined by the change's current work_in_progress
* property and its log of change messages.
*
- * @param {Object} change The change details
- * @return {Array<Object>} Sorted list of patch set objects, as described
+ * @param {!Object} change The change details
+ * @return {!Array<!Object>} Sorted list of patch set objects, as described
* above
*/
computeAllPatchSets(change) {
if (!change) { return []; }
- const patchNums = [];
- for (const commit in change.revisions) {
- if (change.revisions.hasOwnProperty(commit)) {
- patchNums.push({
- num: change.revisions[commit]._number,
- desc: change.revisions[commit].description,
- });
- }
+ let patchNums = [];
+ if (change.revisions &&
+ Object.keys(change.revisions).length) {
+ patchNums =
+ PatchSetBehavior.sortRevisions(Object.values(change.revisions))
+ .map(e => {
+ // TODO(kaspern): Mark which patchset an edit was made on, if an
+ // edit exists -- perhaps with a temporary description.
+ return {
+ num: e._number,
+ desc: e.description,
+ };
+ });
}
- patchNums.sort((a, b) => { return a.num - b.num; });
+ patchNums.sort((a, b) => a.num - b.num);
return PatchSetBehavior._computeWipForPatchSets(change, patchNums);
},
/**
* Populate the wip properties of the given list of patch sets.
*
- * @param {Object} change The change details
- * @param {Array<Object>} patchNums Sorted list of patch set objects, as
+ * @param {!Object} change The change details
+ * @param {!Array<!Object>} patchNums Sorted list of patch set objects, as
* generated by computeAllPatchSets
- * @return {Array<Object>} The given list of patch set objects, with the
+ * @return {!Array<!Object>} The given list of patch set objects, with the
* wip property set on each of them
*/
_computeWipForPatchSets(change, patchNums) {
@@ -122,15 +178,20 @@
computeLatestPatchNum(allPatchSets) {
if (!allPatchSets || !allPatchSets.length) { return undefined; }
+ if (allPatchSets[allPatchSets.length - 1].num ===
+ PatchSetBehavior.EDIT_NAME) {
+ return allPatchSets[allPatchSets.length - 2].num;
+ }
return allPatchSets[allPatchSets.length - 1].num;
},
/**
* Check whether there is no newer patch than the latest patch that was
* available when this change was loaded.
- * @return {Promise} A promise that yields true if the latest patch has been
- * loaded, and false if a newer patch has been uploaded in the meantime.
- * The promise is rejected on network error.
+ *
+ * @return {Promise<boolean>} A promise that yields true if the latest patch
+ * has been loaded, and false if a newer patch has been uploaded in the
+ * meantime. The promise is rejected on network error.
*/
fetchIsLatestKnown(change, restAPI) {
const knownLatest = PatchSetBehavior.computeLatestPatchNum(
@@ -145,6 +206,18 @@
return actualLatest <= knownLatest;
});
},
+
+ /**
+ * @param {number|string} patchNum
+ * @param {!Array<!Object>} revisions A sorted array of revisions.
+ *
+ * @return {number} The index of the revision with the given patchNum.
+ */
+ findSortedIndex(patchNum, revisions) {
+ revisions = revisions || [];
+ const findNum = rev => rev._number + '' === patchNum + '';
+ return revisions.findIndex(findNum);
+ },
};
window.Gerrit = window.Gerrit || {};
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
index 2a5caed..c2ccccd 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
@@ -173,5 +173,63 @@
assert.isTrue(equals('edit', 'edit'));
assert.isTrue(equals('PARENT', 'PARENT'));
});
+
+ test('findEditParentRevision', () => {
+ const findParent = Gerrit.PatchSetBehavior.findEditParentRevision;
+ let revisions = [
+ {_number: 0},
+ {_number: 1},
+ {_number: 2},
+ ];
+ assert.strictEqual(findParent(revisions), null);
+
+ revisions = [...revisions, {_number: 'edit', basePatchNum: 3}];
+ assert.strictEqual(findParent(revisions), null);
+
+ revisions = [...revisions, {_number: 3}];
+ assert.deepEqual(findParent(revisions), {_number: 3});
+ });
+
+ test('findEditParentPatchNum', () => {
+ const findNum = Gerrit.PatchSetBehavior.findEditParentPatchNum;
+ let revisions = [
+ {_number: 0},
+ {_number: 1},
+ {_number: 2},
+ ];
+ assert.equal(findNum(revisions), -1);
+
+ revisions =
+ [...revisions, {_number: 'edit', basePatchNum: 3}, {_number: 3}];
+ assert.deepEqual(findNum(revisions), 3);
+ });
+
+ test('sortRevisions', () => {
+ const sort = Gerrit.PatchSetBehavior.sortRevisions;
+ const revisions = [
+ {_number: 0},
+ {_number: 2},
+ {_number: 1},
+ ];
+ const sorted = [
+ {_number: 0},
+ {_number: 1},
+ {_number: 2},
+ ];
+
+ assert.deepEqual(sort(revisions), sorted);
+
+ // Edit patchset should follow directly after its basePatchNum.
+ revisions.push({_number: 'edit', basePatchNum: 2});
+ sorted.push({_number: 'edit', basePatchNum: 2});
+ assert.deepEqual(sort(revisions), sorted);
+
+ revisions[3].basePatchNum = 0;
+ const edit = sorted.pop();
+ edit.basePatchNum = 0;
+ // Edit patchset should be at index 1.
+ sorted.splice(1, 0, edit);
+ assert.deepEqual(sort(revisions), sorted);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
index 12b401f..00aeb91 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
@@ -69,6 +69,7 @@
attached() {
this._getCreateGroupCapability();
+ this.fire('title-change', {title: 'Groups'});
},
_paramsChanged(params) {
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
index 4b51df5..4f93e98 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
@@ -69,7 +69,7 @@
attached() {
this._getCreateProjectCapability();
- this.fire('title-change', {title: 'Project List'});
+ this.fire('title-change', {title: 'Projects'});
},
_paramsChanged(params) {
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
index a18a862..624ff57 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
@@ -59,7 +59,7 @@
],
attached() {
- this.fire('title-change', {title: 'Plugin List'});
+ this.fire('title-change', {title: 'Plugins'});
},
_paramsChanged(params) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 329a343..f8dad01 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -817,8 +817,8 @@
_setLabelValuesOnRevert(newChangeId) {
const labels = this.$.jsAPI.getLabelValuesPostRevert(this.change);
if (labels) {
- const url = `/changes/${newChangeId}/revisions/current/review`;
- this.$.restAPI.send(this.actions.revert.method, url, {labels});
+ this.$.restAPI.getChangeURLAndSend(newChangeId,
+ this.actions.revert.method, 'current', '/review', {labels});
}
},
@@ -878,11 +878,10 @@
cleanupFn();
return Promise.resolve();
}
-
- const url = this.$.restAPI.getChangeActionURL(this.changeNum,
- revisionAction ? this.patchNum : null, actionEndpoint);
- return this.$.restAPI.send(method, url, payload,
- this._handleResponseError, this).then(response => {
+ const patchNum = revisionAction ? this.patchNum : null;
+ return this.$.restAPI.getChangeURLAndSend(this.changeNum, method,
+ patchNum, actionEndpoint, payload, this._handleResponseError,
+ this).then(response => {
cleanupFn.call(this);
return response;
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 1f96c31..7e305bb 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -71,12 +71,12 @@
send(method, url, payload) {
if (method !== 'POST') { return Promise.reject('bad method'); }
- if (url === '/changes/42/revisions/2/submit') {
+ if (url === '/changes/test~42/revisions/2/submit') {
return Promise.resolve({
ok: true,
text() { return Promise.resolve(')]}\'\n{}'); },
});
- } else if (url === '/changes/42/revisions/2/rebase') {
+ } else if (url === '/changes/test~42/revisions/2/rebase') {
return Promise.resolve({
ok: true,
text() { return Promise.resolve(')]}\'\n{}'); },
@@ -228,6 +228,8 @@
});
test('submit change', done => {
+ sandbox.stub(element.$.restAPI, '_getFromProjectLookup')
+ .returns(Promise.resolve('test'));
sandbox.stub(element, 'fetchIsLatestKnown',
() => { return Promise.resolve(true); });
element.change = {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index b1cbe3e..f9f1d03 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -459,8 +459,9 @@
<select>
<template is="dom-repeat" items="[[_allPatchSets]]"
as="patchNum">
- <option value$="[[patchNum.num]]"
- disabled$="[[_computePatchSetDisabled(patchNum.num, _patchRange.basePatchNum)]]">
+ <option
+ value$="[[patchNum.num]]"
+ disabled$="[[_computePatchSetDisabled(patchNum.num, _patchRange.basePatchNum, _sortedRevisions)]]">
[[patchNum.num]]
/
[[computeLatestPatchNum(_allPatchSets)]]
@@ -511,10 +512,11 @@
patch-range="{{_patchRange}}"
comments="[[_comments]]"
drafts="[[_diffDrafts]]"
- revisions="[[_change.revisions]]"
+ revisions="[[_sortedRevisions]]"
project-config="[[_projectConfig]]"
selected-index="{{viewState.selectedFileIndex}}"
diff-view-mode="{{viewState.diffMode}}"
+ edit-loaded="[[_editLoaded]]"
num-files-shown="{{_numFilesShown}}"
file-list-increment="{{_numFilesShown}}"></gr-file-list>
</section>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 1a20e7c..1d8025e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -181,6 +181,11 @@
value: true,
},
_updateCheckTimerHandle: Number,
+ _sortedRevisions: Array,
+ _editLoaded: {
+ type: Boolean,
+ computed: '_computeEditLoaded(_patchRange.*)',
+ },
},
behaviors: [
@@ -195,6 +200,7 @@
observers: [
'_labelsChanged(_change.labels.*)',
'_paramsAndChangeChanged(params, _change)',
+ '_updateSortedRevisions(_change.revisions.*)',
],
keyBindings: {
@@ -243,6 +249,11 @@
}
},
+ _updateSortedRevisions(revisionsRecord) {
+ const revisions = revisionsRecord.base;
+ this._sortedRevisions = this.sortRevisions(Object.values(revisions));
+ },
+
_computePrefsButtonHidden(prefs, loggedIn) {
return !loggedIn || !prefs;
},
@@ -684,13 +695,15 @@
/**
* Determines if a patch number should be disabled based on value of the
* basePatchNum from gr-file-list.
- * @param {Number} patchNum Patch number available in dropdown
- * @param {Number|String} basePatchNum Base patch number from file list
- * @return {Boolean}
+ * @param {number} patchNum Patch number available in dropdown
+ * @param {number|string} basePatchNum Base patch number from file list
+ * @return {boolean}
*/
_computePatchSetDisabled(patchNum, basePatchNum) {
- basePatchNum = basePatchNum === 'PARENT' ? 0 : basePatchNum;
- return parseInt(patchNum, 10) <= parseInt(basePatchNum, 10);
+ if (basePatchNum === 'PARENT') { return false; }
+
+ return this.findSortedIndex(patchNum, this._sortedRevisions) <=
+ this.findSortedIndex(basePatchNum, this._sortedRevisions);
},
_computeLabelNames(labels) {
@@ -901,12 +914,24 @@
},
_getChangeDetail() {
- return this.$.restAPI.getChangeDetail(this._changeNum,
- this._handleGetChangeDetailError.bind(this)).then(change => {
+ const detailCompletes = this.$.restAPI.getChangeDetail(
+ this._changeNum, this._handleGetChangeDetailError.bind(this));
+ const editCompletes = this._getEdit();
+
+ return Promise.all([detailCompletes, editCompletes])
+ .then(([change, edit]) => {
if (!change) {
return '';
}
this._upgradeUrl(change, this.params);
+ if (edit) {
+ change.revisions[edit.commit.commit] = {
+ _number: this.EDIT_NAME,
+ basePatchNum: edit.base_patch_set_number,
+ commit: edit.commit,
+ fetch: edit.fetch,
+ };
+ }
// Issue 4190: Coalesce missing topics to null.
if (!change.topic) { change.topic = null; }
if (!change.reviewer_updates) {
@@ -952,6 +977,10 @@
});
},
+ _getEdit() {
+ return this.$.restAPI.getChangeEdit(this._changeNum, true);
+ },
+
_getLatestCommitMessage() {
return this.$.restAPI.getChangeCommitInfo(this._changeNum,
this.computeLatestPatchNum(this._allPatchSets)).then(commitInfo => {
@@ -1294,5 +1323,10 @@
_computeHeaderClass(change) {
return change.work_in_progress ? 'header wip' : 'header';
},
+
+ _computeEditLoaded(patchRangeRecord) {
+ const patchRange = patchRangeRecord.base || {};
+ return this.patchNumEquals(patchRange.patchNum, this.EDIT_NAME);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 31d1591..ddbbd5f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -58,6 +58,10 @@
});
suite('keyboard shortcuts', () => {
+ setup(() => {
+ sandbox.stub(element, '_updateSortedRevisions');
+ });
+
test('S should toggle the CL star', () => {
const starStub = sandbox.stub(element.$.changeStar, 'toggleStar');
MockInteractions.pressAndReleaseKeyOn(element, 83, null, 's');
@@ -233,6 +237,12 @@
});
test('_computePatchSetDisabled', () => {
+ element._sortedRevisions = [
+ {_number: 1},
+ {_number: 2},
+ {_number: element.EDIT_NAME, basePatchNum: 2},
+ {_number: 3},
+ ];
let basePatchNum = 'PARENT';
let patchNum = 1;
assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
@@ -243,6 +253,12 @@
patchNum = 2;
assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
false);
+ basePatchNum = element.EDIT_NAME;
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ true);
+ patchNum = '3';
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ false);
});
test('_prepareCommitMsgForLinkify', () => {
@@ -470,6 +486,7 @@
});
test('change num change', () => {
+ sandbox.stub(element, '_updateSortedRevisions');
element._changeNum = null;
element._patchRange = {
basePatchNum: 'PARENT',
@@ -852,6 +869,37 @@
});
});
+ test('edit is added to change', () => {
+ sandbox.stub(element, '_changeChanged');
+ sandbox.stub(element, '_upgradeUrl');
+ sandbox.stub(element.$.restAPI, 'getChangeDetail', () => {
+ return Promise.resolve({
+ id: '123456789',
+ labels: {},
+ current_revision: 'foo',
+ revisions: {foo: {commit: {}}},
+ });
+ });
+ sandbox.stub(element, '_getEdit', () => {
+ return Promise.resolve({
+ base_patch_set_number: 1,
+ commit: {commit: 'bar'},
+ });
+ });
+
+ return element._getChangeDetail().then(() => {
+ const revs = element._change.revisions;
+ assert.equal(Object.keys(revs).length, 2);
+ assert.deepEqual(revs['foo'], {commit: {commit: 'foo'}});
+ assert.deepEqual(revs['bar'], {
+ _number: element.EDIT_NAME,
+ basePatchNum: 1,
+ commit: {commit: 'bar'},
+ fetch: undefined,
+ });
+ });
+ });
+
test('reply dialog focus can be controlled', () => {
const FocusTarget = element.$.replyDialog.FocusTarget;
const openStub = sandbox.stub(element, '_openReplyDialog');
@@ -966,6 +1014,7 @@
suite('reply dialog tests', () => {
setup(() => {
sandbox.stub(element.$.replyDialog, '_draftChanged');
+ sandbox.stub(element, '_updateSortedRevisions');
sandbox.stub(element.$.replyDialog, 'fetchIsLatestKnown',
() => { return Promise.resolve(true); });
element._change = {labels: {}};
@@ -1239,6 +1288,14 @@
assert.isTrue(element.$.relatedChanges.reload.calledOnce);
});
+ test('_computeEditLoaded', () => {
+ const callCompute = range => element._computeEditLoaded({base: range});
+ assert.isFalse(callCompute({}));
+ assert.isFalse(callCompute({basePatchNum: 'PARENT', patchNum: 1}));
+ assert.isFalse(callCompute({basePatchNum: 'edit', patchNum: 1}));
+ assert.isTrue(callCompute({basePatchNum: 1, patchNum: 'edit'}));
+ });
+
suite('_upgradeUrl calls', () => {
let upgradeStub;
const mockChange = {project: 'test'};
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index dc64042..b218f02 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -168,6 +168,9 @@
.mobile {
display: none;
}
+ #container.editLoaded .hideOnEdit {
+ display: none;
+ }
@media screen and (max-width: 50em) {
.desktop {
display: none;
@@ -234,7 +237,7 @@
items="[[computeAllPatchSets(change)]]"
as="patchNum">
<option
- disabled$="[[_computePatchSetDisabled(patchNum.num, patchRange.patchNum)]]"
+ disabled$="[[_computePatchSetDisabled(patchNum.num, patchRange.patchNum, revisions)]]"
value$="[[patchNum.num]]">
[[patchNum.num]]
[[patchNum.desc]]
@@ -245,7 +248,10 @@
</label>
</div>
</header>
- <div on-tap="_handleFileListTap">
+ <div
+ id="container"
+ class$="[[_computeContainerClass(editLoaded)]]"
+ on-tap="_handleFileListTap">
<template is="dom-repeat"
items="[[_shownFiles]]"
id="files"
@@ -253,7 +259,7 @@
initial-count="[[fileListIncrement]]"
target-framerate="1">
<div class="file-row row" data-path$="[[file.__path]]" tabindex="-1">
- <div class="reviewed" hidden$="[[!_loggedIn]]" hidden>
+ <div class="reviewed hideOnEdit" hidden$="[[!_loggedIn]]" hidden>
<input
type="checkbox"
checked="[[file.isReviewed]]"
@@ -267,9 +273,9 @@
[[_computeFileStatus(file.status)]]
</div>
<span
- data-url="[[_computeDiffURL(change, patchRange, file.__path)]]"
+ data-url="[[_computeDiffURL(change, patchRange.patchNum, patchRange.basePatchNum, file.__path)]]"
class$="[[_computePathClass(file.__path, _expandedFilePaths.*)]]">
- <a href$="[[_computeDiffURL(change, patchRange, file.__path)]]">
+ <a href$="[[_computeDiffURL(change, patchRange.patchNum, patchRange.basePatchNum, file.__path)]]">
<span title$="[[_computeFileDisplayName(file.__path)]]"
class="fullFileName">
[[_computeFileDisplayName(file.__path)]]
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 8560717..851f6ef 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -14,6 +14,8 @@
(function() {
'use strict';
+ const ERR_EDIT_LOADED = 'You cannot change the review status of an edit.';
+
// Maximum length for patch set descriptions.
const PATCH_DESC_MAX_LENGTH = 500;
const WARN_SHOW_ALL_THRESHOLD = 1000;
@@ -41,7 +43,8 @@
changeNum: String,
comments: Object,
drafts: Object,
- revisions: Object,
+ // Already sorted by the change-view.
+ revisions: Array,
projectConfig: Object,
selectedIndex: {
type: Number,
@@ -57,6 +60,7 @@
notify: true,
observer: '_updateDiffPreferences',
},
+ editLoaded: Boolean,
_files: {
type: Array,
observer: '_filesChanged',
@@ -116,6 +120,7 @@
type: Boolean,
observer: '_loadingChanged',
},
+ _sortedRevisions: Array,
},
behaviors: [
@@ -231,7 +236,8 @@
},
_computePatchSetDisabled(patchNum, currentPatchNum) {
- return parseInt(patchNum, 10) >= parseInt(currentPatchNum, 10);
+ return this.findSortedIndex(patchNum, this.revisions) >=
+ this.findSortedIndex(currentPatchNum, this.revisions);
},
_togglePathExpanded(path) {
@@ -384,6 +390,10 @@
},
_reviewFile(path) {
+ if (this.editLoaded) {
+ this.fire('show-alert', {message: ERR_EDIT_LOADED});
+ return;
+ }
const index = this._reviewed.indexOf(path);
const reviewed = index !== -1;
if (reviewed) {
@@ -662,9 +672,8 @@
return status || 'M';
},
- _computeDiffURL(change, patchRange, path) {
- return Gerrit.Nav.getUrlForDiff(change, path, patchRange.patchNum,
- patchRange.basePatchNum);
+ _computeDiffURL(change, patchNum, basePatchNum, path) {
+ return Gerrit.Nav.getUrlForDiff(change, path, patchNum, basePatchNum);
},
_computeFileDisplayName(path) {
@@ -940,5 +949,9 @@
this.classList.toggle('loading', loading && this._files.length);
}, LOADING_DEBOUNCE_INTERVAL);
},
+
+ _computeContainerClass(editLoaded) {
+ return editLoaded ? 'editLoaded' : '';
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 9be19a7..e8123a7 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -664,21 +664,29 @@
revisions: {
rev1: {_number: 1},
rev2: {_number: 2},
- rev3: {_number: 3},
+ rev3: {_number: 'edit', basePatchNum: 2},
+ rev4: {_number: 3},
},
};
+ element.revisions = [
+ {_number: 1},
+ {_number: 2},
+ {_number: 'edit', basePatchNum: 2},
+ {_number: 3},
+ ];
+
flush(() => {
const selectEl = element.$.patchChange;
assert.equal(selectEl.nativeSelect.value, 'PARENT');
assert.isTrue(element.$$('option[value="3"]').hasAttribute('disabled'));
selectEl.addEventListener('change', () => {
- assert.equal(selectEl.nativeSelect.value, '2');
- assert(navStub.lastCall.calledWithExactly(element.change, '3', '2'),
- 'Should navigate to /c/42/2..3');
+ assert.equal(selectEl.nativeSelect.value, 'edit');
+ assert(navStub.lastCall.calledWithExactly(element.change, '3', 'edit'),
+ 'Should navigate to /c/42/edit..3');
navStub.restore();
done();
});
- selectEl.nativeSelect.value = '2';
+ selectEl.nativeSelect.value = 'edit';
element.fire('change', {}, {node: selectEl.nativeSelect});
});
});
@@ -988,6 +996,21 @@
element.flushDebouncer('loading-change');
assert.isFalse(element.classList.contains('loading'));
});
+
+ test('no execute _computeDiffURL before patchNum is knwon', done => {
+ const urlStub = sandbox.stub(element, '_computeDiffURL');
+ element.change = {_number: 123};
+ element.patchRange = {patchNum: undefined, basePatchNum: 'PARENT'};
+ element._files = [{__path: 'foo/bar.cpp'}];
+ flush(() => {
+ assert.isFalse(urlStub.called);
+ element.set('patchRange.patchNum', 4);
+ flush(() => {
+ assert.isTrue(urlStub.called);
+ done();
+ });
+ });
+ });
});
suite('gr-file-list inline diff tests', () => {
@@ -1259,7 +1282,30 @@
element._handleEscKey(mockEvent);
assert.isFalse(element._displayLine);
});
- });
+ suite('editLoaded behavior', () => {
+ const isVisible = el => {
+ assert.ok(el);
+ return getComputedStyle(el).getPropertyValue('display') !== 'none';
+ };
+
+ test('reviewed checkbox', () => {
+ const alertStub = sandbox.stub();
+ element.addEventListener('show-alert', alertStub);
+ element.editLoaded = false;
+ // Reviewed checkbox should be shown.
+ assert.isTrue(isVisible(element.$$('.reviewed')));
+ MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
+ assert.isFalse(alertStub.called);
+
+ element.editLoaded = true;
+ flushAsynchronousOperations();
+
+ assert.isFalse(isVisible(element.$$('.reviewed')));
+ MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
+ assert.isTrue(alertStub.called);
+ });
+ });
+ });
a11ySuite('basic');
</script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 6e1cb64..67a8110 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -39,6 +39,7 @@
const encode = window.Gerrit.URLEncodingBehavior.encodeURL;
const patchNumEquals = window.Gerrit.PatchSetBehavior.patchNumEquals;
+ const EDIT_NAME = window.Gerrit.PatchSetBehavior.EDIT_NAME;
function startRouter(generateUrl) {
const base = window.Gerrit.BaseUrlBehavior.getBaseUrl();
@@ -419,18 +420,27 @@
if (params.basePatchNum &&
patchNumEquals(params.basePatchNum, params.patchNum)) {
params.basePatchNum = null;
- upgradeUrl(params);
} else if (params.basePatchNum && !params.patchNum) {
params.patchNum = params.basePatchNum;
params.basePatchNum = null;
}
+ // In GWTUI, edits are represented in URLs with either 0 or 'edit'.
+ // TODO(kaspern): Remove this normalization when GWT UI is gone.
+ if (patchNumEquals(params.basePatchNum, 0)) {
+ params.basePatchNum = EDIT_NAME;
+ }
+ if (patchNumEquals(params.patchNum, 0)) {
+ params.patchNum = EDIT_NAME;
+ }
+ upgradeUrl(params);
};
// Matches
- // /c/<project>/+/<changeNum>/[<basePatchNum>..][<patchNum>]/[path].
+ // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..][<patchNum|edit>]/[path].
// TODO(kaspern): Migrate completely to project based URLs, with backwards
// compatibility for change-only.
- page(/^\/c\/(.+)\/\+\/(\d+)(\/?((\d+)(\.\.(\d+))?(\/(.+))?))?\/?$/,
+ // eslint-disable-next-line max-len
+ page(/^\/c\/(.+)\/\+\/(\d+)(\/?((\d+|edit)(\.\.(\d+|edit))?(\/(.+))?))?\/?$/,
ctx => {
// Parameter order is based on the regex group number matched.
const params = {
@@ -448,7 +458,7 @@
});
// Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>][/].
- page(/^\/c\/(\d+)\/?(((\d+)(\.\.(\d+))?))?\/?$/, ctx => {
+ page(/^\/c\/(\d+)\/?(((\d+|edit)(\.\.(\d+|edit))?))?\/?$/, ctx => {
// Parameter order is based on the regex group number matched.
const params = {
changeNum: ctx.params[0],
@@ -462,7 +472,7 @@
});
// Matches /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
- page(/^\/c\/(\d+)\/((\d+)(\.\.(\d+))?)\/(.+)/, ctx => {
+ page(/^\/c\/(\d+)\/((\d+|edit)(\.\.(\d+|edit))?)\/(.+)/, ctx => {
// Check if path has an '@' which indicates it was using GWT style line
// numbers. Even if the filename had an '@' in it, it would have already
// been URI encoded. Redirect to hash version of path.
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index 70d19e5..2908f85 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -271,7 +271,7 @@
change-num="[[_changeNum]]"
patch-range="[[_patchRange]]"
files-weblinks="[[_filesWeblinks]]"
- available-patches="[[_computeAvailablePatches(_change.revisions)]]"
+ available-patches="[[_computeAvailablePatches(_change.revisions, _change.revisions.*)]]"
revisions="[[_change.revisions]]">
</gr-patch-range-select>
<span class="download desktop">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 5574fd6..0f6a07d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -176,6 +176,10 @@
}
},
+ _getChangeEdit(changeNum) {
+ return this.$.restAPI.getChangeEdit(this._changeNum);
+ },
+
_getFiles(changeNum, patchRangeRecord) {
const patchRange = patchRangeRecord.base;
return this.$.restAPI.getChangeFilePathsAsSpeciallySortedArray(
@@ -495,7 +499,17 @@
promises.push(this._loadComments());
- Promise.all(promises).then(() => {
+ promises.push(this._getChangeEdit(this._changeNum));
+
+ Promise.all(promises).then(r => {
+ const edit = r[4];
+ if (edit) {
+ this.set('_change.revisions.' + edit.commit.commit, {
+ _number: this.EDIT_NAME,
+ basePatchNum: edit.base_patch_set_number,
+ commit: edit.commit,
+ });
+ }
this._loading = false;
this.$.diff.comments = this._commentsForDiff;
this.$.diff.reload();
@@ -562,13 +576,8 @@
return patchStr;
},
- _computeAvailablePatches(revisions) {
- const patchNums = [];
- if (!revisions) { return patchNums; }
- for (const rev of Object.values(revisions)) {
- patchNums.push(rev._number);
- }
- return patchNums.sort((a, b) => { return a - b; });
+ _computeAvailablePatches(revs) {
+ return this.sortRevisions(Object.values(revs)).map(e => e._number);
},
/**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 7135601..7ef5c60 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -15,6 +15,8 @@
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-diff-builder/gr-diff-builder.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 94f60d9..728a766 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -14,6 +14,9 @@
(function() {
'use strict';
+ const ERR_COMMENT_ON_EDIT = 'You cannot comment on an edit.';
+ const ERR_INVALID_LINE = 'Invalid line number: ';
+
const DiffViewMode = {
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
UNIFIED: 'UNIFIED_DIFF',
@@ -79,10 +82,6 @@
},
noRenderOnPrefsChange: Boolean,
comments: Object,
- _loggedIn: {
- type: Boolean,
- value: false,
- },
lineWrapping: {
type: Boolean,
value: false,
@@ -93,6 +92,10 @@
value: DiffViewMode.SIDE_BY_SIDE,
observer: '_viewModeObserver',
},
+ _loggedIn: {
+ type: Boolean,
+ value: false,
+ },
_diff: Object,
_diffHeaderItems: {
type: Array,
@@ -120,6 +123,10 @@
_showWarning: Boolean,
},
+ behaviors: [
+ Gerrit.PatchSetBehavior,
+ ],
+
listeners: {
'thread-discard': '_handleThreadDiscard',
'comment-discard': '_handleCommentDiscard',
@@ -169,27 +176,6 @@
return Polymer.dom(this.root).querySelectorAll('.diff-row');
},
- addDraftAtLine(el) {
- this._selectLine(el);
- this._getLoggedIn().then(loggedIn => {
- if (!loggedIn) {
- this.fire('show-auth-required');
- return;
- }
-
- const value = el.getAttribute('data-value');
- if (value === GrDiffLine.FILE) {
- this._addDraft(el);
- return;
- }
- const lineNum = parseInt(value, 10);
- if (isNaN(lineNum)) {
- throw Error('Invalid line number: ' + value);
- }
- this._addDraft(el, lineNum);
- });
- },
-
isRangeSelected() {
return this.$.highlights.isRangeSelected();
},
@@ -254,34 +240,65 @@
});
},
- _handleCreateComment(e) {
- const range = e.detail.range;
- const diffSide = e.detail.side;
- const line = range.endLine;
- const lineEl = this.$.diffBuilder.getLineElByNumber(line, diffSide);
- const contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
- const contentEl = contentText.parentElement;
- const patchNum = this._getPatchNumByLineAndContent(lineEl, contentEl);
- const isOnParent =
- this._getIsParentCommentByLineAndContent(lineEl, contentEl);
- const threadEl = this._getOrCreateThreadAtLineRange(contentEl, patchNum,
- diffSide, isOnParent, range);
+ addDraftAtLine(el) {
+ this._selectLine(el);
+ this._isValidElForComment(el).then(valid => {
+ if (!valid) { return; }
- threadEl.addOrEditDraft(line, range);
+ const value = el.getAttribute('data-value');
+ let lineNum;
+ if (value !== GrDiffLine.FILE) {
+ lineNum = parseInt(value, 10);
+ if (isNaN(lineNum)) {
+ this.fire('show-alert', {message: ERR_INVALID_LINE + value});
+ return;
+ }
+ }
+ this._createComment(el, lineNum);
+ });
},
- _addDraft(lineEl, opt_lineNum) {
+ _handleCreateComment(e) {
+ const range = e.detail.range;
+ const side = e.detail.side;
+ const lineNum = range.endLine;
+ const lineEl = this.$.diffBuilder.getLineElByNumber(lineNum, side);
+ this._isValidElForComment(el).then(valid => {
+ if (!valid) { return; }
+
+ this._createComment(lineEl, lineNum, side, range);
+ });
+ },
+
+ _isValidElForComment(el) {
+ return this._getLoggedIn().then(loggedIn => {
+ if (!loggedIn) {
+ this.fire('show-auth-required');
+ return false;
+ }
+ const patchNum = el.classList.contains(DiffSide.LEFT) ?
+ this.patchRange.basePatchNum :
+ this.patchRange.patchNum;
+
+ if (this.patchNumEquals(patchNum, this.EDIT_NAME)) {
+ this.fire('show-alert', {message: ERR_COMMENT_ON_EDIT});
+ return false;
+ }
+ return true;
+ });
+ },
+
+ _createComment(lineEl, opt_lineNum, opt_side, opt_range) {
const contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
const contentEl = contentText.parentElement;
- const patchNum = this._getPatchNumByLineAndContent(lineEl, contentEl);
- const commentSide =
+ const side = opt_side ||
this._getCommentSideByLineAndContent(lineEl, contentEl);
+ const patchNum = this._getPatchNumByLineAndContent(lineEl, contentEl);
const isOnParent =
- this._getIsParentCommentByLineAndContent(lineEl, contentEl);
+ this._getIsParentCommentByLineAndContent(lineEl, contentEl);
const threadEl = this._getOrCreateThreadAtLineRange(contentEl, patchNum,
- commentSide, isOnParent);
-
- threadEl.addOrEditDraft(opt_lineNum);
+ side, isOnParent);
+ threadEl.addOrEditDraft(opt_lineNum, opt_range);
},
_getThreadForRange(threadGroupEl, rangeToCheck) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 8077e3a..8e790a0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -713,6 +713,7 @@
});
suite('logged in', () => {
+ let fakeLineEl;
setup(() => {
stub('gr-rest-api-interface', {
getLoggedIn() { return Promise.resolve(true); },
@@ -721,18 +722,43 @@
},
});
element = fixture('basic');
+ element.patchRange = {};
+
+ fakeLineEl = {
+ getAttribute: sandbox.stub().returns(42),
+ classList: {
+ contains: sandbox.stub().returns(true),
+ },
+ };
});
test('addDraftAtLine', done => {
- const fakeLineEl = {getAttribute: sandbox.stub().returns(42)};
sandbox.stub(element, '_selectLine');
- sandbox.stub(element, '_addDraft');
+ sandbox.stub(element, '_createComment');
const loggedInErrorSpy = sandbox.spy();
element.addEventListener('show-auth-required', loggedInErrorSpy);
element.addDraftAtLine(fakeLineEl);
flush(() => {
assert.isFalse(loggedInErrorSpy.called);
- assert.isTrue(element._addDraft.calledWithExactly(fakeLineEl, 42));
+ assert.isTrue(element._createComment
+ .calledWithExactly(fakeLineEl, 42));
+ done();
+ });
+ });
+
+ test.only('addDraftAtLine on an edit', done => {
+ element.patchRange.basePatchNum = element.EDIT_NAME;
+ sandbox.stub(element, '_selectLine');
+ sandbox.stub(element, '_createComment');
+ const loggedInErrorSpy = sandbox.spy();
+ const alertSpy = sandbox.spy();
+ element.addEventListener('show-auth-required', loggedInErrorSpy);
+ element.addEventListener('show-alert', alertSpy);
+ element.addDraftAtLine(fakeLineEl);
+ flush(() => {
+ assert.isFalse(loggedInErrorSpy.called);
+ assert.isTrue(alertSpy.called);
+ assert.isFalse(element._createComment.called);
done();
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
index e9437bd..9a4000f 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
@@ -30,10 +30,13 @@
observer: '_updateSelected',
},
revisions: Object,
+ _sortedRevisions: Array,
_rightSelected: String,
_leftSelected: String,
},
+ observers: ['_updateSortedRevisions(revisions.*)'],
+
behaviors: [Gerrit.PatchSetBehavior],
_updateSelected() {
@@ -41,6 +44,11 @@
this._leftSelected = this.patchRange.basePatchNum;
},
+ _updateSortedRevisions(revisionsRecord) {
+ const revisions = revisionsRecord.base;
+ this._sortedRevisions = this.sortRevisions(Object.values(revisions));
+ },
+
_handlePatchChange(e) {
const leftPatch = this._leftSelected;
const rightPatch = this._rightSelected;
@@ -53,12 +61,15 @@
},
_computeLeftDisabled(patchNum, patchRange) {
- return parseInt(patchNum, 10) >= parseInt(patchRange.patchNum, 10);
+ return this.findSortedIndex(patchNum, this._sortedRevisions) >=
+ this.findSortedIndex(patchRange.patchNum, this._sortedRevisions);
},
_computeRightDisabled(patchNum, patchRange) {
if (patchRange.basePatchNum == 'PARENT') { return false; }
- return parseInt(patchNum, 10) <= parseInt(patchRange.basePatchNum, 10);
+
+ return this.findSortedIndex(patchNum, this._sortedRevisions) <=
+ this.findSortedIndex(patchRange.basePatchNum, this._sortedRevisions);
},
// On page load, the dom-if for options getting added occurs after
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
index abe3e5d..2fe5986 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
@@ -36,16 +36,26 @@
<script>
suite('gr-patch-range-select tests', () => {
let element;
+ let sandbox;
setup(() => {
element = fixture('basic');
+ sandbox = sinon.sandbox.create();
});
+ teardown(() => sandbox.restore());
+
test('enabled/disabled options', () => {
const patchRange = {
basePatchNum: 'PARENT',
patchNum: '3',
};
+ element._sortedRevisions = [
+ {_number: 1},
+ {_number: 2},
+ {_number: element.EDIT_NAME, basePatchNum: 2},
+ {_number: 3},
+ ];
for (const patchNum of ['1', '2', '3']) {
assert.isFalse(element._computeRightDisabled(patchNum, patchRange));
}
@@ -54,18 +64,21 @@
}
assert.isTrue(element._computeLeftDisabled('3', patchRange));
- patchRange.basePatchNum = '2';
+ patchRange.basePatchNum = element.EDIT_NAME;
assert.isTrue(element._computeLeftDisabled('3', patchRange));
assert.isTrue(element._computeRightDisabled('1', patchRange));
assert.isTrue(element._computeRightDisabled('2', patchRange));
assert.isFalse(element._computeRightDisabled('3', patchRange));
+ assert.isTrue(element._computeRightDisabled(element.EDIT_NAME, patchRange));
});
test('navigation', done => {
- const showStub = sinon.stub(page, 'show');
+ sandbox.stub(element, '_computeLeftDisabled').returns(false);
+ sandbox.stub(element, '_computeRightDisabled').returns(false);
+ const showStub = sandbox.stub(page, 'show');
const leftSelectEl = element.$.leftPatchSelect;
const rightSelectEl = element.$.rightPatchSelect;
- const blurSpy = sinon.spy(leftSelectEl, 'blur');
+ const blurSpy = sandbox.spy(leftSelectEl, 'blur');
element.changeNum = '42';
element.path = 'path/to/file.txt';
element.availablePatches = ['1', '2', '3'];
@@ -89,7 +102,6 @@
assert(showStub.lastCall.calledWithExactly(
'/c/42/1..3/path/to/file.txt'),
'Should navigate to /c/42/1..3/path/to/file.txt');
- showStub.restore();
done();
}
});
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 9317425..5ddd0fe 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -596,7 +596,8 @@
},
getChangeActionURL(changeNum, opt_patchNum, endpoint) {
- return this._changeBaseURL(changeNum, opt_patchNum) + endpoint;
+ return this._changeBaseURL(changeNum, opt_patchNum)
+ .then(url => url + endpoint);
},
getChangeDetail(changeNum, opt_errFn, opt_cancelCondition) {
@@ -624,34 +625,34 @@
_getChangeDetail(changeNum, params, opt_errFn,
opt_cancelCondition) {
- const url = this.getChangeActionURL(changeNum, null, '/detail');
- const urlWithParams = this._urlWithParams(url, params);
- return this._fetchRawJSON(
- url,
- opt_errFn,
- opt_cancelCondition,
- {O: params},
- this._etags.getOptions(urlWithParams))
- .then(response => {
- if (response && response.status === 304) {
- return Promise.resolve(
- this._etags.getCachedPayload(urlWithParams));
- } else {
- const payloadPromise = response ?
- this.getResponseObject(response) :
- Promise.resolve();
- payloadPromise.then(payload => {
- this._etags.collect(urlWithParams, response, payload);
- this._maybeInsertInLookup(payload);
- });
- return payloadPromise;
- }
- });
+ return this.getChangeActionURL(changeNum, null, '/detail').then(url => {
+ const urlWithParams = this._urlWithParams(url, params);
+ return this._fetchRawJSON(
+ url,
+ opt_errFn,
+ opt_cancelCondition,
+ {O: params},
+ this._etags.getOptions(urlWithParams))
+ .then(response => {
+ if (response && response.status === 304) {
+ return Promise.resolve(
+ this._etags.getCachedPayload(urlWithParams));
+ } else {
+ const payloadPromise = response ?
+ this.getResponseObject(response) :
+ Promise.resolve();
+ payloadPromise.then(payload => {
+ this._etags.collect(urlWithParams, response, payload);
+ this._maybeInsertInLookup(payload);
+ });
+ return payloadPromise;
+ }
+ });
+ });
},
getChangeCommitInfo(changeNum, patchNum) {
- return this.fetchJSON(
- this.getChangeActionURL(changeNum, patchNum, '/commit?links'));
+ return this._getChangeURLAndFetch(changeNum, '/commit?links', patchNum);
},
getChangeFiles(changeNum, patchRange) {
@@ -659,8 +660,8 @@
if (patchRange.basePatchNum !== 'PARENT') {
endpoint += '?base=' + encodeURIComponent(patchRange.basePatchNum);
}
- return this.fetchJSON(
- this.getChangeActionURL(changeNum, patchRange.patchNum, endpoint));
+ return this._getChangeURLAndFetch(changeNum, endpoint,
+ patchRange.patchNum);
},
getChangeFilesAsSpeciallySortedArray(changeNum, patchRange) {
@@ -689,26 +690,23 @@
},
getChangeRevisionActions(changeNum, patchNum) {
- return this.fetchJSON(
- this.getChangeActionURL(changeNum, patchNum, '/actions')).then(
- revisionActions => {
- // The rebase button on change screen is always enabled.
+ return this._getChangeURLAndFetch(changeNum, '/actions', patchNum)
+ .then(revisionActions => {
+ // The rebase button on change screen is always enabled.
if (revisionActions.rebase) {
revisionActions.rebase.rebaseOnCurrent =
- !!revisionActions.rebase.enabled;
+ !!revisionActions.rebase.enabled;
revisionActions.rebase.enabled = true;
}
return revisionActions;
});
},
- getChangeSuggestedReviewers(changeNum, inputVal, opt_errFn,
- opt_ctx) {
- const url =
- this.getChangeActionURL(changeNum, null, '/suggest_reviewers');
- const req = {n: 10};
- if (inputVal) { req.q = inputVal; }
- return this.fetchJSON(url, opt_errFn, opt_ctx, req);
+ getChangeSuggestedReviewers(changeNum, inputVal, opt_errFn) {
+ const params = {n: 10};
+ if (inputVal) { params.q = inputVal; }
+ return this._getChangeURLAndFetch(changeNum, '/suggest_reviewers', null,
+ opt_errFn, null, params);
},
_computeFilter(filter) {
@@ -808,30 +806,30 @@
},
_sendChangeReviewerRequest(method, changeNum, reviewerID) {
- let url = this.getChangeActionURL(changeNum, null, '/reviewers');
- let body;
- switch (method) {
- case 'POST':
- body = {reviewer: reviewerID};
- break;
- case 'DELETE':
- url += '/' + encodeURIComponent(reviewerID);
- break;
- default:
- throw Error('Unsupported HTTP method: ' + method);
- }
+ return this.getChangeActionURL(changeNum, null, '/reviewers')
+ .then(url => {
+ let body;
+ switch (method) {
+ case 'POST':
+ body = {reviewer: reviewerID};
+ break;
+ case 'DELETE':
+ url += '/' + encodeURIComponent(reviewerID);
+ break;
+ default:
+ throw Error('Unsupported HTTP method: ' + method);
+ }
- return this.send(method, url, body);
+ return this.send(method, url, body);
+ });
},
getRelatedChanges(changeNum, patchNum) {
- return this.fetchJSON(
- this.getChangeActionURL(changeNum, patchNum, '/related'));
+ return this._getChangeURLAndFetch(changeNum, '/related', patchNum);
},
getChangesSubmittedTogether(changeNum) {
- return this.fetchJSON(
- this.getChangeActionURL(changeNum, null, '/submitted_together'));
+ return this._getChangeURLAndFetch(changeNum, '/submitted_together', null);
},
getChangeConflicts(changeNum) {
@@ -879,90 +877,84 @@
},
getReviewedFiles(changeNum, patchNum) {
- return this.fetchJSON(
- this.getChangeActionURL(changeNum, patchNum, '/files?reviewed'));
+ return this._getChangeURLAndFetch(changeNum, '/files?reviewed', patchNum);
},
saveFileReviewed(changeNum, patchNum, path, reviewed, opt_errFn, opt_ctx) {
const method = reviewed ? 'PUT' : 'DELETE';
- const url = this.getChangeActionURL(changeNum, patchNum,
- '/files/' + encodeURIComponent(path) + '/reviewed');
-
- return this.send(method, url, null, opt_errFn, opt_ctx);
+ const e = `/files/${encodeURIComponent(path)}/reviewed`;
+ return this.getChangeURLAndSend(changeNum, method, patchNum, e, null,
+ opt_errFn, opt_ctx);
},
saveChangeReview(changeNum, patchNum, review, opt_errFn, opt_ctx) {
- const url = this.getChangeActionURL(changeNum, patchNum, '/review');
- return this.awaitPendingDiffDrafts()
- .then(() => this.send('POST', url, review, opt_errFn, opt_ctx));
+ const promises = [
+ this.awaitPendingDiffDrafts(),
+ this.getChangeActionURL(changeNum, patchNum, '/review'),
+ ];
+ return Promise.all(promises).then(([, url]) => {
+ return this.send('POST', url, review, opt_errFn, opt_ctx);
+ });
+ },
+
+ getChangeEdit(changeNum, opt_download_commands) {
+ const params = opt_download_commands ? {'download-commands': true} : null;
+ return this.getLoggedIn().then(loggedIn => {
+ return loggedIn ?
+ this._getChangeURLAndFetch(changeNum, '/edit/', null, null, null,
+ params) :
+ false;
+ });
},
getFileInChangeEdit(changeNum, path) {
- return this.send('GET',
- this.getChangeActionURL(changeNum, null,
- '/edit/' + encodeURIComponent(path)
- ));
+ const e = '/edit/' + encodeURIComponent(path);
+ return this.getChangeURLAndSend(changeNum, 'GET', null, e);
},
rebaseChangeEdit(changeNum) {
- return this.send('POST',
- this.getChangeActionURL(changeNum, null,
- '/edit:rebase'
- ));
+ return this.getChangeURLAndSend(changeNum, 'POST', null, '/edit:rebase');
},
deleteChangeEdit(changeNum) {
- return this.send('DELETE',
- this.getChangeActionURL(changeNum, null,
- '/edit'
- ));
+ return this.getChangeURLAndSend(changeNum, 'DELETE', null, '/edit');
},
restoreFileInChangeEdit(changeNum, restore_path) {
- return this.send('POST',
- this.getChangeActionURL(changeNum, null, '/edit'),
- {restore_path}
- );
+ const p = {restore_path};
+ return this.getChangeURLAndSend(changeNum, 'POST', null, '/edit', p);
},
renameFileInChangeEdit(changeNum, old_path, new_path) {
- return this.send('POST',
- this.getChangeActionURL(changeNum, null, '/edit'),
- {old_path},
- {new_path}
- );
+ const p = {old_path, new_path};
+ return this.getChangeURLAndSend(changeNum, 'POST', null, '/edit', p);
},
deleteFileInChangeEdit(changeNum, path) {
- return this.send('DELETE',
- this.getChangeActionURL(changeNum, null,
- '/edit/' + encodeURIComponent(path)
- ));
+ const e = '/edit/' + encodeURIComponent(path);
+ return this.getChangeURLAndSend(changeNum, 'DELETE', null, e);
},
saveChangeEdit(changeNum, path, contents) {
- return this.send('PUT',
- this.getChangeActionURL(changeNum, null,
- '/edit/' + encodeURIComponent(path)
- ),
- contents
- );
+ const e = '/edit/' + encodeURIComponent(path);
+ return this.getChangeURLAndSend(changeNum, 'PUT', null, e, contents);
},
// Deprecated, prefer to use putChangeCommitMessage instead.
saveChangeCommitMessageEdit(changeNum, message) {
- const url = this.getChangeActionURL(changeNum, null, '/edit:message');
- return this.send('PUT', url, {message});
+ const p = {message};
+ return this.getChangeURLAndSend(changeNum, 'PUT', null, '/edit:message',
+ p);
},
publishChangeEdit(changeNum) {
- return this.send('POST',
- this.getChangeActionURL(changeNum, null, '/edit:publish'));
+ return this.getChangeURLAndSend(changeNum, 'POST', null,
+ '/edit:publish');
},
putChangeCommitMessage(changeNum, message) {
- const url = this.getChangeActionURL(changeNum, null, '/message');
- return this.send('PUT', url, {message});
+ const p = {message};
+ return this.getChangeURLAndSend(changeNum, 'PUT', null, '/message', p);
},
saveChangeStarred(changeNum, starred) {
@@ -1004,7 +996,6 @@
getDiff(changeNum, basePatchNum, patchNum, path,
opt_errFn, opt_cancelCondition) {
- const url = this._getDiffFetchURL(changeNum, patchNum, path);
const params = {
context: 'ALL',
intraline: null,
@@ -1013,13 +1004,10 @@
if (basePatchNum != PARENT_PATCH_NUM) {
params.base = basePatchNum;
}
+ const endpoint = `/files/${encodeURIComponent(path)}/diff`;
- return this.fetchJSON(url, opt_errFn, opt_cancelCondition, params);
- },
-
- _getDiffFetchURL(changeNum, patchNum, path) {
- return this._changeBaseURL(changeNum, patchNum) + '/files/' +
- encodeURIComponent(path) + '/diff';
+ return this._getChangeURLAndFetch(changeNum, endpoint, patchNum,
+ opt_errFn, opt_cancelCondition, params);
},
getDiffComments(changeNum, opt_basePatchNum, opt_patchNum, opt_path) {
@@ -1076,11 +1064,20 @@
_getDiffComments(changeNum, endpoint, opt_basePatchNum,
opt_patchNum, opt_path) {
- if (!opt_basePatchNum && !opt_patchNum && !opt_path) {
- return this.fetchJSON(
- this._getDiffCommentsFetchURL(changeNum, endpoint));
- }
+ /**
+ * Fetches the comments for a given patchNum.
+ * Helper function to make promises more legible.
+ *
+ * @param {string|number} patchNum
+ * @return {!Object} Diff comments response.
+ */
+ const fetchComments = patchNum => {
+ return this._getChangeURLAndFetch(changeNum, endpoint, patchNum);
+ };
+ if (!opt_basePatchNum && !opt_patchNum && !opt_path) {
+ return fetchComments();
+ }
function onlyParent(c) { return c.side == PARENT_PATCH_NUM; }
function withoutParent(c) { return c.side != PARENT_PATCH_NUM; }
function setPath(c) { c.path = opt_path; }
@@ -1088,16 +1085,13 @@
const promises = [];
let comments;
let baseComments;
- const url =
- this._getDiffCommentsFetchURL(changeNum, endpoint, opt_patchNum);
- promises.push(this.fetchJSON(url).then(response => {
+ let fetchPromise;
+ fetchPromise = fetchComments(opt_patchNum).then(response => {
comments = response[opt_path] || [];
-
- // TODO(kaspern): Implement this on in the backend so this can be
- // removed.
-
- // Sort comments by date so that parent ranges can be propagated in a
- // single pass.
+ // TODO(kaspern): Implement this on in the backend so this can
+ // be removed.
+ // Sort comments by date so that parent ranges can be propagated
+ // in a single pass.
comments = this._setRanges(comments);
if (opt_basePatchNum == PARENT_PATCH_NUM) {
@@ -1107,18 +1101,17 @@
comments = comments.filter(withoutParent);
comments.forEach(setPath);
- }));
+ });
+ promises.push(fetchPromise);
if (opt_basePatchNum != PARENT_PATCH_NUM) {
- const baseURL = this._getDiffCommentsFetchURL(changeNum, endpoint,
- opt_basePatchNum);
- promises.push(this.fetchJSON(baseURL).then(response => {
- baseComments = (response[opt_path] || []).filter(withoutParent);
-
+ fetchPromise = fetchComments(opt_basePatchNum).then(response => {
+ baseComments = (response[opt_path] || [])
+ .filter(withoutParent);
baseComments = this._setRanges(baseComments);
-
baseComments.forEach(setPath);
- }));
+ });
+ promises.push(fetchPromise);
}
return Promise.all(promises).then(() => {
@@ -1130,7 +1123,8 @@
},
_getDiffCommentsFetchURL(changeNum, endpoint, opt_patchNum) {
- return this._changeBaseURL(changeNum, opt_patchNum) + endpoint;
+ return this._changeBaseURL(changeNum, opt_patchNum)
+ .then(url => url + endpoint);
},
saveDiffDraft(changeNum, patchNum, draft) {
@@ -1161,9 +1155,9 @@
},
_sendDiffDraftRequest(method, changeNum, patchNum, draft) {
- let url = this.getChangeActionURL(changeNum, patchNum, '/drafts');
+ let endpoint = '/drafts';
if (draft.id) {
- url += '/' + draft.id;
+ endpoint += '/' + draft.id;
}
let body;
if (method === 'PUT') {
@@ -1174,7 +1168,8 @@
this._pendingRequests[Requests.SEND_DIFF_DRAFT] = [];
}
- const promise = this.send(method, url, body);
+ const promise = this.getChangeURLAndSend(changeNum, method, patchNum,
+ endpoint, body);
this._pendingRequests[Requests.SEND_DIFF_DRAFT].push(promise);
return promise;
},
@@ -1200,11 +1195,10 @@
getChangeFileContents(changeId, patchNum, path, opt_parentIndex) {
const parent = typeof opt_parentIndex === 'number' ?
'?parent=' + opt_parentIndex : '';
- return this._fetchB64File(
- '/changes/' + encodeURIComponent(changeId) +
- '/revisions/' + encodeURIComponent(patchNum) +
- '/files/' + encodeURIComponent(path) +
- '/content' + parent);
+ return this._changeBaseUrl(changeId, patchNum).then(url => {
+ url = `${url}/files/${encodeURIComponent(path)}/content${parent}`;
+ return this._fetchB64File(url);
+ });
},
getImagesForDiff(changeNum, diff, patchRange) {
@@ -1249,22 +1243,36 @@
});
},
- _changeBaseURL(changeNum, opt_patchNum) {
- let v = '/changes/' + changeNum;
- if (opt_patchNum) {
- v += '/revisions/' + opt_patchNum;
- }
- return v;
+ /**
+ * @param {string} changeNum
+ * @param {number|string=} opt_patchNum
+ * @param {string=} opt_project
+ * @return {!Promise<string>}
+ */
+ _changeBaseURL(changeNum, opt_patchNum, opt_project) {
+ // TODO(kaspern): For full slicer migration, app should warn with a call
+ // stack every time _changeBaseURL is called without a project.
+ const projectPromise = opt_project ?
+ Promise.resolve(opt_project) :
+ this._getFromProjectLookup(changeNum);
+ return projectPromise.then(project => {
+ let url = `/changes/${encodeURIComponent(project)}~${changeNum}`;
+ if (opt_patchNum) {
+ url += `/revisions/${opt_patchNum}`;
+ }
+ return url;
+ });
},
setChangeTopic(changeNum, topic) {
- return this.send('PUT', '/changes/' + encodeURIComponent(changeNum) +
- '/topic', {topic}).then(this.getResponseObject);
+ const p = {topic};
+ return this.getChangeURLAndSend(changeNum, 'PUT', null, '/topic', p)
+ .then(this.getResponseObject);
},
setChangeHashtag(changeNum, hashtag) {
- return this.send('POST', '/changes/' + encodeURIComponent(changeNum) +
- '/hashtags', hashtag).then(this.getResponseObject);
+ return this.getChangeURLAndSend(changeNum, 'POST', null, '/hashtags',
+ hashtag).then(this.getResponseObject);
},
deleteAccountHttpPassword() {
@@ -1299,15 +1307,15 @@
return this.send('DELETE', '/accounts/self/sshkeys/' + id);
},
- deleteVote(changeID, account, label) {
- return this.send('DELETE', '/changes/' + changeID +
- '/reviewers/' + account + '/votes/' + encodeURIComponent(label));
+ deleteVote(changeNum, account, label) {
+ const e = `/reviewers/${account}/votes/${encodeURIComponent(label)}`;
+ return this.getChangeURLAndSend(changeNum, 'DELETE', null, e);
},
setDescription(changeNum, patchNum, desc) {
- return this.send('PUT',
- this.getChangeActionURL(changeNum, patchNum, '/description'),
- {description: desc});
+ const p = {description: desc};
+ return this.getChangeURLAndSend(changeNum, 'PUT', patchNum,
+ '/description', p);
},
confirmEmail(token) {
@@ -1321,14 +1329,12 @@
},
setAssignee(changeNum, assignee) {
- return this.send('PUT',
- this.getChangeActionURL(changeNum, null, '/assignee'),
- {assignee});
+ const p = {assignee};
+ return this.getChangeURLAndSend(changeNum, 'PUT', null, '/assignee', p);
},
deleteAssignee(changeNum) {
- return this.send('DELETE',
- this.getChangeActionURL(changeNum, null, '/assignee'));
+ return this.getChangeURLAndSend(changeNum, 'DELETE', null, '/assignee');
},
probePath(path) {
@@ -1343,8 +1349,7 @@
if (opt_message) {
payload.message = opt_message;
}
- const url = this.getChangeActionURL(changeNum, null, '/wip');
- return this.send('POST', url, payload)
+ return this.getChangeURLAndSend(changeNum, 'POST', null, '/wip', payload)
.then(response => {
if (response.status === 204) {
return 'Change marked as Work In Progress.';
@@ -1353,16 +1358,15 @@
},
startReview(changeNum, opt_body, opt_errFn) {
- return this.send(
- 'POST', this.getChangeActionURL(changeNum, null, '/ready'),
+ return this.getChangeURLAndSend(changeNum, 'POST', null, '/ready',
opt_body, opt_errFn);
},
deleteComment(changeNum, patchNum, commentID, reason) {
- const url = this.changeBaseURL(changeNum, patchNum) +
- '/comments/' + commentID + '/delete';
- return this.send('POST', url, {reason}).then(response =>
- this.getResponseObject(response));
+ const endpoint = `/comments/${commentID}/delete`;
+ const payload = {reason};
+ return this.getChangeURLAndSend(changeNum, 'POST', patchNum, endpoint,
+ payload).then(this.getResponseObject);
},
/**
@@ -1372,6 +1376,7 @@
* @return {Promise<Object>} The change
*/
getChange(changeNum) {
+ // Cannot use _changeBaseURL, as this function is used by _projectLookup.
return this.fetchJSON(`/changes/${changeNum}`);
},
@@ -1405,5 +1410,44 @@
return change.project;
});
},
+
+ /**
+ * Alias for _changeBaseURL.then(send).
+ *
+ * @param {string|number} changeNum
+ * @param {string} method
+ * @param {string} endpoint
+ * @param {string|number=} opt_patchNum
+ * @param {!Object=} opt_payload
+ * @param {function(?Response, string)=} opt_errFn
+ * @return {!Promise<!Object>}
+ */
+ getChangeURLAndSend(changeNum, method, opt_patchNum, endpoint, opt_payload,
+ opt_errFn, opt_ctx, opt_contentType) {
+ return this._changeBaseURL(changeNum, opt_patchNum).then(url => {
+ return this.send(method, url + endpoint, opt_payload, opt_errFn,
+ opt_ctx, opt_contentType);
+ });
+ },
+
+ /**
+ * Alias for _changeBaseURL.then(fetchJSON).
+ *
+ * @param {string|number} changeNum
+ * @param {string} endpoint
+ * @param {string|number=} opt_patchNum
+ * @param {function(?Response, string)=} opt_errFn
+ * @param {!function()=} opt_cancelCondition
+ * @param {!Object=} opt_params
+ * @param {!Object=} opt_options
+ * @return {!Promise<!Object>}
+ */
+ _getChangeURLAndFetch(changeNum, endpoint, opt_patchNum, opt_errFn,
+ opt_cancelCondition, opt_params, opt_options) {
+ return this._changeBaseURL(changeNum, opt_patchNum).then(url => {
+ return this.fetchJSON(url + endpoint, opt_errFn, opt_cancelCondition,
+ opt_params, opt_options);
+ });
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 2cda640..eac758f 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -269,8 +269,10 @@
});
test('differing patch diff comments are properly grouped', done => {
+ sandbox.stub(element, '_getFromProjectLookup')
+ .returns(Promise.resolve('test'));
sandbox.stub(element, 'fetchJSON', url => {
- if (url == '/changes/42/revisions/1') {
+ if (url === '/changes/test~42/revisions/1') {
return Promise.resolve({
'/COMMIT_MSG': [],
'sieve.go': [
@@ -285,7 +287,7 @@
},
],
});
- } else if (url == '/changes/42/revisions/2') {
+ } else if (url === '/changes/test~42/revisions/2') {
return Promise.resolve({
'/COMMIT_MSG': [],
'sieve.go': [
@@ -606,8 +608,7 @@
test('_sendDiffDraft pending requests tracked', () => {
const obj = element._pendingRequests;
- sandbox.stub(element, 'send', () => mockPromise());
- sandbox.stub(element, 'getChangeActionURL');
+ sandbox.stub(element, 'getChangeURLAndSend', () => mockPromise());
assert.notOk(element.hasPendingDiffDrafts());
element._sendDiffDraftRequest(null, null, null, {});
@@ -627,6 +628,7 @@
});
test('saveChangeEdit', done => {
+ element._projectLookup = {1: 'test'};
const change_num = '1';
const file_name = 'index.php';
const file_contents = '<?php';
@@ -636,17 +638,16 @@
sandbox.stub(element, 'getResponseObject')
.returns(Promise.resolve([change_num, file_name, file_contents]));
element._cache['/changes/' + change_num + '/edit/' + file_name] = {};
- element.saveChangeEdit(change_num, file_name, file_contents).then(
- () => {
- assert.isTrue(element.send.calledWith('PUT',
- '/changes/' + change_num + '/edit/' + file_name,
- file_contents));
- done();
- }
- );
+ element.saveChangeEdit(change_num, file_name, file_contents).then(() => {
+ assert.isTrue(element.send.calledWith('PUT',
+ '/changes/test~1/edit/' + file_name,
+ file_contents));
+ done();
+ });
});
test('putChangeCommitMessage', done => {
+ element._projectLookup = {1: 'test'};
const change_num = '1';
const message = 'this is a commit message';
sandbox.stub(element, 'send').returns(
@@ -655,42 +656,42 @@
sandbox.stub(element, 'getResponseObject')
.returns(Promise.resolve([change_num, message]));
element._cache['/changes/' + change_num + '/message'] = {};
- element.putChangeCommitMessage(change_num, message).then(
- () => {
- assert.isTrue(element.send.calledWith('PUT',
- '/changes/' + change_num + '/message', {message}));
- done();
- }
- );
+ element.putChangeCommitMessage(change_num, message).then(() => {
+ assert.isTrue(element.send.calledWith('PUT',
+ '/changes/test~1/message', {message}));
+ done();
+ });
});
test('startWorkInProgress', () => {
- sandbox.stub(element, 'send').returns(Promise.resolve('ok'));
+ sandbox.stub(element, 'getChangeURLAndSend')
+ .returns(Promise.resolve('ok'));
element.startWorkInProgress('42');
- assert.isTrue(element.send.calledWith(
- 'POST', '/changes/42/wip', {}));
+ assert.isTrue(element.getChangeURLAndSend.calledWith(
+ '42', 'POST', null, '/wip', {}));
element.startWorkInProgress('42', 'revising...');
- assert.isTrue(element.send.calledWith(
- 'POST', '/changes/42/wip', {message: 'revising...'}));
+ assert.isTrue(element.getChangeURLAndSend.calledWith(
+ '42', 'POST', null, '/wip', {message: 'revising...'}));
});
test('startReview', () => {
- sandbox.stub(element, 'send').returns(Promise.resolve({}));
+ sandbox.stub(element, 'getChangeURLAndSend')
+ .returns(Promise.resolve({}));
element.startReview('42', {message: 'Please review.'});
- assert.isTrue(element.send.calledWith(
- 'POST', '/changes/42/ready', {message: 'Please review.'}));
+ assert.isTrue(element.getChangeURLAndSend.calledWith(
+ '42', 'POST', null, '/ready', {message: 'Please review.'}));
});
test('deleteComment', done => {
- sandbox.stub(element, 'send').returns(Promise.resolve());
+ sandbox.stub(element, 'getChangeURLAndSend').returns(Promise.resolve());
sandbox.stub(element, 'getResponseObject').returns('some response');
element.deleteComment('foo', 'bar', '01234', 'removal reason')
.then(response => {
assert.equal(response, 'some response');
done();
});
- assert.isTrue(element.send.calledWith(
- 'POST', '/changes/foo/revisions/bar/comments/01234/delete',
+ assert.isTrue(element.getChangeURLAndSend.calledWith(
+ 'foo', 'POST', 'bar', '/comments/01234/delete',
{reason: 'removal reason'}));
});
@@ -763,10 +764,9 @@
test('_getChangeDetail passes params to ETags decorator', () => {
const changeNum = 4321;
+ element._projectLookup[changeNum] = 'test';
const params = {foo: 'bar'};
- const expectedUrl = element._urlWithParams(
- element.getChangeActionURL(changeNum, null, '/detail'),
- params);
+ const expectedUrl = '/changes/test~4321/detail?foo=bar';
sandbox.stub(element._etags, 'getOptions');
sandbox.stub(element._etags, 'collect');
return element._getChangeDetail(changeNum, params).then(() => {
@@ -858,5 +858,23 @@
assert.equal(element._projectLookup[1], 'test');
});
});
+
+ test('_getChangeURLAndFetch', () => {
+ element._projectLookup = {1: 'test'};
+ const fetchStub = sandbox.stub(element, 'fetchJSON')
+ .returns(Promise.resolve());
+ return element._getChangeURLAndFetch(1, '/test', 1).then(() => {
+ assert.isTrue(fetchStub.calledWith('/changes/test~1/revisions/1/test'));
+ });
+ });
+
+ test('getChangeURLAndSend', () => {
+ element._projectLookup = {1: 'test'};
+ const sendStub = sandbox.stub(element, 'send').returns(Promise.resolve());
+ return element.getChangeURLAndSend(1, 'POST', 1, '/test').then(() => {
+ assert.isTrue(sendStub.calledWith('POST',
+ '/changes/test~1/revisions/1/test'));
+ });
+ });
});
</script>