Use JSON arrays in REST API output format.
* Do not combine multiple owner/reviewer emails into one string.
* Do not encode multiple OwnerWeights counter into a string like "n1+n2+n3".
* Change the "reviewers" list to a simple list of emails,
without owner weight counters.
* Change the "owners" list to a list of OwnerInfo,
which contains "email" and "weights" attributes.
* Put OwnerWeights counters into an array.
* Change path2owners and owner2paths to
sorted maps of string to array of string.
* Change document, Java code, and JavaScript code to use the new JSON format.
Change-Id: Ia42c10364d19f7843409dee421d003ae1cb9ea72
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/Action.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/Action.java
index 834994e..13a1cc9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/Action.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/Action.java
@@ -92,14 +92,14 @@
return "?";
}
- private List<String> getOwners(OwnersDb db, Collection<String> files) {
+ private List<OwnerInfo> getOwners(OwnersDb db, Collection<String> files) {
Map<String, OwnerWeights> weights = new HashMap<>();
db.findOwners(files, weights);
- List<String> result = new ArrayList<>();
+ List<OwnerInfo> result = new ArrayList<>();
Set<String> emails = new HashSet<>();
for (String key : OwnerWeights.sortKeys(weights)) {
if (!emails.contains(key)) {
- result.add(key + " " + weights.get(key).encodeLevelCounts());
+ result.add(new OwnerInfo(key, weights.get(key).getLevelCounts()));
emails.add(key);
}
}
@@ -136,7 +136,7 @@
try {
for (Account.Id id : changeData.reviewers().all()) {
Account account = accountCache.get(id).getAccount();
- result.add(account.getPreferredEmail() + " []");
+ result.add(account.getPreferredEmail());
}
} catch (OrmException e) {
log.error("Exception", e);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnerInfo.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnerInfo.java
new file mode 100644
index 0000000..72894d0
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnerInfo.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.findowners;
+
+import java.util.List;
+
+/** Keeps owner email and weights. */
+public class OwnerInfo {
+ OwnerInfo(String e, List<Integer> w) {
+ email = e;
+ weights = w;
+ }
+
+ String email;
+ List<Integer> weights;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnerWeights.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnerWeights.java
index d57304b..233f1d2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnerWeights.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnerWeights.java
@@ -14,6 +14,7 @@
package com.googlesource.gerrit.plugins.findowners;
+import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -64,6 +65,11 @@
return "[" + countL1 + "+" + countL2 + "+" + countL3 + "]";
}
+ /** Return file counters as a list of integers. */
+ List<Integer> getLevelCounts() {
+ return ImmutableList.of(countL1, countL2, countL3);
+ }
+
OwnerWeights(String file, int level) {
addFile(file, level);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/RestResult.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/RestResult.java
index b0b227c..f91ecbe 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/RestResult.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/RestResult.java
@@ -35,9 +35,9 @@
String ownerRevision;
DebugMessages dbgmsgs;
- SortedMap<String, String> file2owners = new TreeMap<>();
+ SortedMap<String, List<String>> file2owners = new TreeMap<>();
List<String> reviewers = new ArrayList<>();
- List<String> owners = new ArrayList<>();
+ List<OwnerInfo> owners = new ArrayList<>();
List<String> files = new ArrayList<>();
RestResult(int voteLevel, boolean addDebugMsg) {
@@ -54,7 +54,7 @@
String user;
String project;
String branch;
- SortedMap<String, String> path2owners;
- SortedMap<String, String> owner2paths;
+ SortedMap<String, List<String>> path2owners;
+ SortedMap<String, List<String>> owner2paths;
};
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/Util.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/Util.java
index 76a0e53..1888fa1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/Util.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/Util.java
@@ -17,6 +17,7 @@
import com.google.common.collect.Ordering;
import java.io.File;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
@@ -46,10 +47,10 @@
return (s != null) && (s.equals("1") || s.equalsIgnoreCase("yes") || Boolean.parseBoolean(s));
}
- static SortedMap<String, String> makeSortedMap(Map<String, Set<String>> map) {
- SortedMap<String, String> result = new TreeMap<>();
+ static SortedMap<String, List<String>> makeSortedMap(Map<String, Set<String>> map) {
+ SortedMap<String, List<String>> result = new TreeMap<>();
for (String key : Ordering.natural().sortedCopy(map.keySet())) {
- result.put(key, String.join(" ", Ordering.natural().sortedCopy(map.get(key))));
+ result.put(key, Ordering.natural().sortedCopy(map.get(key)));
}
return result;
}
diff --git a/src/main/resources/Documentation/rest-api.md b/src/main/resources/Documentation/rest-api.md
index 34152d3..7676059 100644
--- a/src/main/resources/Documentation/rest-api.md
+++ b/src/main/resources/Documentation/rest-api.md
@@ -52,28 +52,29 @@
* **project**: the change's project name.
- * **branch**: the change's destination brach name.
+ * **branch**: the change's destination branch name.
* **path2owners**:
- a map from directory path or file glob to a string of owner emails
- separated by space. Note that `*` is a special owner email address.
+ a map from directory path or file glob to an array of owner emails.
+ Note that `*` is a special owner email address.
It means that there is no owner and anyone can be the owner.
Included directories are those affected by the change revision.
* **owner2paths**:
- a map from owner email to directory path or file glob.
+ a map from owner email to an array of directory path or file glob.
This is opposite to the path2owners map.
-* **file2owners**: a map from each file in the change patchset to
- the file owner emails, separated by space.
+* **file2owners**: a map from each file path in the change patchset to
+ an array of the file's owner emails.
-* **reviewers**: an array of current reviewer emails followed by
- optional extra information that should be ignored for now.
+* **reviewers**: an array of current reviewer emails.
-* **owners**: an array of owner emails followed by the owner weights,
- `[n1+n2+n3]`, which are the number of level 1, 2, 3+ controlled files.
- This list of owners are the keys in the owner2paths map.
- The array is sorted by owner weights.
+* **owners**: an array of owner info objects.
+ Each owner info object has "email" and "weights" attributes.
+ The weights attribute is an array of integers like [n1, n2, n3],
+ which are the number of level 1, 2, 3+ controlled files.
+ The email attributes are the keys in the owner2paths map.
+ This owners array is sorted by owner weights.
Users should try to pick owners with more weights to review a change.
* **files**: an alphabetically sorted files changed
diff --git a/src/main/resources/static/find-owners.js b/src/main/resources/static/find-owners.js
index 94d303f..8122474 100644
--- a/src/main/resources/static/find-owners.js
+++ b/src/main/resources/static/find-owners.js
@@ -176,7 +176,7 @@
}
function showFilesAndOwners(result, args) {
var sortedOwners = result.owners.map(
- function(line) { return line.split(' ')[0]; });
+ function(ownerInfo) { return ownerInfo.email; });
var groups = {};
// group name ==> {needReviewer, needApproval, owners}
var groupSize = {};
@@ -249,7 +249,7 @@
div.appendChild(nobr);
}
keys.forEach(function(key) {
- var owners = groups[key].owners;
+ var owners = groups[key].owners; // string of owner emails
var numFiles = groupSize[key];
var item = HTML_BULLET + ' <b>' + key + '</b>' +
((numFiles > 1) ? (' (' + numFiles + ' files):') : ':');
@@ -269,8 +269,11 @@
div.innerHTML = '';
div.style.display = 'inline';
div.appendChild(strElement(title));
- result.owners.sort().forEach(function(owner) {
- var email = owner.split(' ')[0];
+ function compareOwnerInfo(o1, o2) {
+ return o1.email.localeCompare(o2.email);
+ }
+ result.owners.sort(compareOwnerInfo).forEach(function(ownerInfo) {
+ var email = ownerInfo.email;
var vote = reviewerVote[email];
if ((email in reviewerVote) && vote != 0) {
email += ' <font color="' +
@@ -309,8 +312,8 @@
('minOwnerVoteLevel' in result ?
result['minOwnerVoteLevel'] : 1);
Object.keys(result.file2owners).sort().forEach(function(name) {
- var owners = result.file2owners[name];
- var splitOwners = owners.split(' ');
+ var splitOwners = result.file2owners[name];
+ var owners = splitOwners.join(' ');
if (owners in owners2group) {
groupSize[owners2group[owners]] += 1;
} else {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java b/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java
index c8b3b3e..11274c6 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java
@@ -79,15 +79,20 @@
assertThat(getOwnersResponse(c2)).contains("owners:[], files:[ t.c ]");
// Change of "t.c" file has owner after c1 is submitted.
approveSubmit(c1);
- assertThat(getOwnersResponse(c2)).contains("owners:[ a@a[1+0+0], x@x[1+0+0] ], files:[ t.c ]");
+ String ownerA = ownerJson("a@a");
+ String ownerB = ownerJson("b@b");
+ String ownerC = ownerJson("c@c");
+ String ownerX = ownerJson("x@x");
+ String ownersAX = "owners:[ " + ownerA + ", " + ownerX + " ]";
+ assertThat(getOwnersResponse(c2)).contains(ownersAX + ", files:[ t.c ]");
// A submitted change gets owners info from current repository.
- assertThat(getOwnersResponse(c1))
- .contains("owners:[ a@a[1+0+0], x@x[1+0+0] ], files:[ OWNERS ]");
+ assertThat(getOwnersResponse(c1)).contains(ownersAX + ", files:[ OWNERS ]");
// Check all fields in response.
String expectedTail =
- "path2owners:{ ./:a@ax@x }, owner2paths:{ a@a:./, x@x:./ } }"
- + ", file2owners:{ ./t.c:a@ax@x }, reviewers:[], owners:[ "
- + "a@a[1+0+0], x@x[1+0+0] ], files:[ t.c ] }";
+ "path2owners:{ ./:[ a@a, x@x ] }, owner2paths:{ a@a:[ ./ ], x@x:[ ./ ] } }"
+ + ", file2owners:{ ./t.c:[ a@a, x@x ] }, reviewers:[], "
+ + ownersAX
+ + ", files:[ t.c ] }";
assertThat(getOwnersDebugResponse(c2)).contains(expectedTail);
}
@@ -97,11 +102,15 @@
addFile("add OWNERS", "OWNERS", "per-file *.c=x@x\na@a\nc@c\nb@b\n");
// Add "t.c" file, which has per-file owner x@x, not a@a, b@b, c@c.
PushOneCommit.Result c2 = createChange("add t.c", "t.c", "Hello!");
- assertThat(getOwnersResponse(c2)).contains("owners:[ x@x[1+0+0] ], files:[ t.c ]");
+ String ownerA = ownerJson("a@a");
+ String ownerB = ownerJson("b@b");
+ String ownerC = ownerJson("c@c");
+ String ownerX = ownerJson("x@x");
+ assertThat(getOwnersResponse(c2)).contains("owners:[ " + ownerX + " ], files:[ t.c ]");
// Add "t.txt" file, which has new owners.
PushOneCommit.Result c3 = createChange("add t.txt", "t.txt", "Test!");
assertThat(getOwnersResponse(c3))
- .contains("owners:[ a@a[1+0+0], b@b[1+0+0], c@c[1+0+0] ], files:[ t.txt ]");
+ .contains("owners:[ " + ownerA + ", " + ownerB + ", " + ownerC + " ], files:[ t.txt ]");
}
@Test
@@ -113,25 +122,30 @@
addFile("add d3/OWNERS", "d3/OWNERS", "b@b\nset noparent\n");
// Add "t.c" file, which is not owned by subdirectory owners.
PushOneCommit.Result c2 = createChange("add t.c", "t.c", "Hello!");
- assertThat(getOwnersResponse(c2)).contains("owners:[ x@x[1+0+0] ], files:[ t.c ]");
+ String ownerA = ownerJson("a@a");
+ String ownerX = ownerJson("x@x");
+ assertThat(getOwnersResponse(c2)).contains("owners:[ " + ownerX + " ], files:[ t.c ]");
// Add "d1/t.c" file, which is owned by ./d1 and root owners.
PushOneCommit.Result c3 = createChange("add d1/t.c", "d1/t.c", "Hello!");
+ String ownerX010 = ownerJson("x@x", 0, 1, 0);
assertThat(getOwnersResponse(c3))
- .contains("owners:[ a@a[1+0+0], x@x[0+1+0] ], files:[ d1/t.c ]");
+ .contains("owners:[ " + ownerA + ", " + ownerX010 + " ], files:[ d1/t.c ]");
// Add "d2/t.c" file, which is owned by ./d2 and root owners.
PushOneCommit.Result c4 = createChange("add d2/t.c", "d2/t.c", "Hello!");
+ String ownerY = ownerJson("y@y");
assertThat(getOwnersResponse(c4))
- .contains("owners:[ y@y[1+0+0], x@x[0+1+0] ], files:[ d2/t.c ]");
+ .contains("owners:[ " + ownerY + ", " + ownerX010 + " ], files:[ d2/t.c ]");
// Add "d2/d1/t.c" file, which is owned by ./d2 and root owners.
PushOneCommit.Result c5 = createChange("add d2/d1/t.c", "d2/d1/t.c", "Hello!");
assertThat(getOwnersResponse(c5))
- .contains("owners:[ y@y[1+0+0], x@x[0+1+0] ], files:[ d2/d1/t.c ]");
+ .contains("owners:[ " + ownerY + ", " + ownerX010 + " ], files:[ d2/d1/t.c ]");
// Add "d3/t.c" file, which is owned only by ./d3 owners due to "set noparent".
PushOneCommit.Result c6 = createChange("add d3/t.c", "d3/t.c", "Hello!");
- assertThat(getOwnersResponse(c6)).contains("owners:[ b@b[1+0+0] ], files:[ d3/t.c ]");
+ String ownerB = ownerJson("b@b");
+ assertThat(getOwnersResponse(c6)).contains("owners:[ " + ownerB + " ], files:[ d3/t.c ]");
// Add "d3/d1/t.c" file, which is owned only by ./d3 owners due to "set noparent".
PushOneCommit.Result c7 = createChange("add d3/d1/t.c", "d3/d1/t.c", "Hello!");
- assertThat(getOwnersResponse(c7)).contains("owners:[ b@b[1+0+0] ], files:[ d3/d1/t.c ]");
+ assertThat(getOwnersResponse(c7)).contains("owners:[ " + ownerB + " ], files:[ d3/d1/t.c ]");
}
@Test
@@ -212,8 +226,10 @@
assertThat(Config.getOwnersFileName(pA)).isEqualTo("OWNERS");
assertThat(Config.getOwnersFileName(pB)).isEqualTo("OWNERS");
- assertThat(getOwnersResponse(cA)).contains("owners:[ x@x[1+0+0] ], files:[ tA.c ]");
- assertThat(getOwnersResponse(cB)).contains("owners:[ y@y[1+0+0] ], files:[ tB.c ]");
+ String ownerX = oneOwnerList("x@x");
+ String ownerY = oneOwnerList("y@y");
+ assertThat(getOwnersResponse(cA)).contains(ownerX + ", files:[ tA.c ]");
+ assertThat(getOwnersResponse(cB)).contains(ownerY + ", files:[ tB.c ]");
// Change owners file name to "OWNERS.alpha" and "OWNERS.beta"
switchProject(pA);
@@ -222,21 +238,23 @@
setProjectConfig("ownersFileName", "OWNERS.beta");
assertThat(Config.getOwnersFileName(pA)).isEqualTo("OWNERS.alpha");
assertThat(Config.getOwnersFileName(pB)).isEqualTo("OWNERS.beta");
- assertThat(getOwnersResponse(cA)).contains("owners:[ a@a[1+0+0] ], files:[ tA.c ]");
- assertThat(getOwnersResponse(cB)).contains("owners:[ b@b[1+0+0] ], files:[ tB.c ]");
+ String ownerA = oneOwnerList("a@a");
+ String ownerB = oneOwnerList("b@b");
+ assertThat(getOwnersResponse(cA)).contains(ownerA + ", files:[ tA.c ]");
+ assertThat(getOwnersResponse(cB)).contains(ownerB + ", files:[ tB.c ]");
// Change back to OWNERS in Project_A
switchProject(pA);
setProjectConfig("ownersFileName", "OWNERS");
assertThat(Config.getOwnersFileName(pA)).isEqualTo("OWNERS");
- assertThat(getOwnersResponse(cA)).contains("owners:[ x@x[1+0+0] ], files:[ tA.c ]");
- assertThat(getOwnersResponse(cB)).contains("owners:[ b@b[1+0+0] ], files:[ tB.c ]");
+ assertThat(getOwnersResponse(cA)).contains(ownerX + ", files:[ tA.c ]");
+ assertThat(getOwnersResponse(cB)).contains(ownerB + ", files:[ tB.c ]");
// Change back to OWNERS.alpha in Project_B, but there is no OWNERS.alpha
switchProject(pB);
setProjectConfig("ownersFileName", "OWNERS.alpha");
assertThat(Config.getOwnersFileName(pB)).isEqualTo("OWNERS.alpha");
- assertThat(getOwnersResponse(cA)).contains("owners:[ x@x[1+0+0] ], files:[ tA.c ]");
+ assertThat(getOwnersResponse(cA)).contains(ownerX + ", files:[ tA.c ]");
assertThat(getOwnersResponse(cB)).contains("owners:[], files:[ tB.c ]");
// Do not accept empty string or all-white-spaces for ownersFileName.
@@ -279,6 +297,18 @@
assertThat(cache.size()).isEqualTo(0);
}
+ private String oneOwnerList(String email) {
+ return "owners:[ " + ownerJson(email) + " ]";
+ }
+
+ private String ownerJson(String email) {
+ return "{ email:" + email + ", weights:[ 1, 0, 0 ] }";
+ }
+
+ private String ownerJson(String email, int w1, int w2, int w3) {
+ return "{ email:" + email + ", weights:[ " + w1 + ", " + w2 + ", " + w3 + " ] }";
+ }
+
private ChangeInfo newChangeInfo(String subject) throws Exception {
// should be called with different subject
ChangeInput in = new ChangeInput();