Merge branch 'origin/stable-3.4' into stable-3.5
* origin/stable-3.4:
Filter out the files owners based on the owners' code-review value
Change-Id: I0f590874bdbfac256daa2642543e5a723952e202
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java b/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
index 42c6586..02581de 100644
--- a/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
@@ -16,8 +16,11 @@
package com.googlesource.gerrit.owners.restapi;
import com.google.common.collect.Maps;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.client.ListChangesOption;
@@ -25,6 +28,7 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -64,6 +68,12 @@
private final ChangeData.Factory changeDataFactory;
private final PathOwnersEntriesCache cache;
+ static final String MISSING_CODE_REVIEW_LABEL =
+ String.format(
+ "Cannot calculate file onwers state when %s label is not configured",
+ LabelId.CODE_REVIEW);
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
@Inject
GetFilesOwners(
Accounts accounts,
@@ -88,6 +98,20 @@
public Response<FilesOwnersResponse> apply(RevisionResource revision)
throws AuthException, BadRequestException, ResourceConflictException, Exception {
Change change = revision.getChange();
+ Short codeReviewMaxValue =
+ revision
+ .getChangeResource()
+ .getChangeData()
+ .getLabelTypes()
+ .byLabel(LabelId.CODE_REVIEW)
+ .map(LabelType::getMaxPositive)
+ .orElseThrow(
+ () -> {
+ logger.atInfo().log(
+ "Project %s has no Code-Review label configured hence getting file owners is not possible.",
+ revision.getProject());
+ return new ResourceNotFoundException(MISSING_CODE_REVIEW_LABEL);
+ });
int id = revision.getChangeResource().getChange().getChangeId();
Project.NameKey project = change.getProject();
@@ -110,24 +134,55 @@
project.get(),
cache);
+ Map<String, Set<GroupOwner>> fileExpandedOwners =
+ Maps.transformValues(
+ owners.getFileOwners(),
+ ids ->
+ ids.stream()
+ .map(this::getOwnerFromAccountId)
+ .flatMap(Optional::stream)
+ .collect(Collectors.toSet()));
+
Map<String, Set<GroupOwner>> fileToOwners =
pluginSettings.expandGroups()
- ? Maps.transformValues(
- owners.getFileOwners(),
- ids ->
- ids.stream()
- .map(this::getOwnerFromAccountId)
- .flatMap(Optional::stream)
- .collect(Collectors.toSet()))
+ ? fileExpandedOwners
: Maps.transformValues(
owners.getFileGroupOwners(),
groupNames ->
groupNames.stream().map(GroupOwner::new).collect(Collectors.toSet()));
- return Response.ok(new FilesOwnersResponse(getLabels(id), fileToOwners));
+ Map<Integer, Map<String, Integer>> ownersLabels = getLabels(id);
+
+ Map<String, Set<GroupOwner>> filesWithPendingOwners =
+ Maps.filterEntries(
+ fileToOwners,
+ (fileOwnerEntry) ->
+ !isApprovedByOwner(
+ fileExpandedOwners.get(fileOwnerEntry.getKey()),
+ ownersLabels,
+ codeReviewMaxValue));
+
+ return Response.ok(new FilesOwnersResponse(ownersLabels, filesWithPendingOwners));
}
}
+ private boolean isApprovedByOwner(
+ Set<GroupOwner> fileOwners,
+ Map<Integer, Map<String, Integer>> ownersLabels,
+ short codeReviewMaxValue) {
+ return fileOwners.stream()
+ .filter(owner -> owner instanceof Owner)
+ .map(owner -> ((Owner) owner).getId())
+ .map(ownerId -> codeReviewLabelValue(ownersLabels, ownerId))
+ .anyMatch(value -> value.filter(v -> v == codeReviewMaxValue).isPresent());
+ }
+
+ private Optional<Integer> codeReviewLabelValue(
+ Map<Integer, Map<String, Integer>> ownersLabels, int ownerId) {
+ return Optional.ofNullable(ownersLabels.get(ownerId))
+ .flatMap(m -> Optional.ofNullable(m.get(LabelId.CODE_REVIEW)));
+ }
+
/**
* This method returns ta Map representing the "owners_labels" object of the response. When
* serialized the Map, has to to return the following JSON: the following JSON:
diff --git a/owners/src/main/resources/Documentation/rest-api.md b/owners/src/main/resources/Documentation/rest-api.md
index 4fb4285..68fb463 100644
--- a/owners/src/main/resources/Documentation/rest-api.md
+++ b/owners/src/main/resources/Documentation/rest-api.md
@@ -1,7 +1,7 @@
# Rest API
-The @PLUGIN@ exposes a Rest API endpoint to list the owners associated to each file and, for each owner,
-its associated labels and votes:
+The @PLUGIN@ exposes a Rest API endpoint to list the owners associated to each file that
+needs a review, and, for each owner, its current labels and votes:
```bash
GET /changes/{change-id}/revisions/{revision-id}/owners~files-owners
@@ -29,4 +29,7 @@
}
}
-```
\ No newline at end of file
+```
+
+> __NOTE__: The API does not work in the case when custom label is in
+> rules.pl configuration as described in [the config.md docs](https://gerrit.googlesource.com/plugins/owners/+/refs/heads/stable-3.4/owners/src/main/resources/Documentation/config.md#example-3-owners-file-without-matchers-and-custom-owner_approves-label)
\ No newline at end of file
diff --git a/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersIT.java b/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersIT.java
index 53d13c9..1c551ec 100644
--- a/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersIT.java
+++ b/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersIT.java
@@ -16,8 +16,8 @@
package com.googlesource.gerrit.owners.restapi;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit.Result;
@@ -25,6 +25,8 @@
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.config.GlobalPluginConfig;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.Project.NameKey;
import com.google.gerrit.entities.RefNames;
@@ -32,7 +34,9 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.googlesource.gerrit.owners.entities.FilesOwnersResponse;
import com.googlesource.gerrit.owners.entities.GroupOwner;
import com.googlesource.gerrit.owners.entities.Owner;
@@ -99,7 +103,21 @@
}
@Test
- public void shouldReturnOwnersLabels() throws Exception {
+ public void shouldReturnOwnersLabelsWhenNotApprovedByOwners() throws Exception {
+ addOwnerFileToRoot(true);
+ String changeId = createChange().getChangeId();
+
+ Response<FilesOwnersResponse> resp =
+ assertResponseOk(ownersApi.apply(parseCurrentRevisionResource(changeId)));
+
+ assertThat(resp.value().files)
+ .containsExactly("a.txt", Sets.newHashSet(new Owner(admin.fullName(), admin.id().get())));
+
+ assertThat(resp.value().ownersLabels).isEmpty();
+ }
+
+ @Test
+ public void shouldReturnEmptyResponseWhenApprovedByOwners() throws Exception {
addOwnerFileToRoot(true);
String changeId = createChange().getChangeId();
approve(changeId);
@@ -107,8 +125,7 @@
Response<FilesOwnersResponse> resp =
assertResponseOk(ownersApi.apply(parseCurrentRevisionResource(changeId)));
- assertThat(resp.value().ownersLabels)
- .containsExactly(admin.id().get(), ImmutableMap.builder().put("Code-Review", 2).build());
+ assertThat(resp.value().files).isEmpty();
}
@Test
@@ -126,6 +143,20 @@
@Test
@GlobalPluginConfig(pluginName = "owners", name = "owners.expandGroups", value = "false")
+ public void shouldReturnEmptyResponseWhenApprovedByOwnersWithUnexpandedFileOwners()
+ throws Exception {
+ addOwnerFileToRoot(true);
+ String changeId = createChange().getChangeId();
+ approve(changeId);
+
+ Response<FilesOwnersResponse> resp =
+ assertResponseOk(ownersApi.apply(parseCurrentRevisionResource(changeId)));
+
+ assertThat(resp.value().files).isEmpty();
+ }
+
+ @Test
+ @GlobalPluginConfig(pluginName = "owners", name = "owners.expandGroups", value = "false")
public void shouldReturnResponseWithUnexpandedFileMatchersOwners() throws Exception {
addOwnerFileWithMatchersToRoot(true);
String changeId = createChange().getChangeId();
@@ -177,6 +208,30 @@
assertNotInheritFromProject(allProjects);
}
+ @Test
+ @UseLocalDisk
+ public void shouldThrowExceptionWhenCodeReviewLabelIsNotConfigured() throws Exception {
+ addOwnerFileToProjectConfig(project, false);
+ replaceCodeReviewWithLabel(
+ TestLabels.label(
+ "Foo", TestLabels.value(1, "Foo is fine"), TestLabels.value(-1, "Foo is not fine")));
+ String changeId = createChange().getChangeId();
+
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> ownersApi.apply(parseCurrentRevisionResource(changeId)));
+ assertThat(thrown).hasMessageThat().isEqualTo(GetFilesOwners.MISSING_CODE_REVIEW_LABEL);
+ }
+
+ private void replaceCodeReviewWithLabel(LabelType label) throws Exception {
+ try (ProjectConfigUpdate u = updateProject(allProjects)) {
+ u.getConfig().getLabelSections().remove(LabelId.CODE_REVIEW);
+ u.getConfig().upsertLabelType(label);
+ u.save();
+ }
+ }
+
private static <T> Response<T> assertResponseOk(Response<T> response) {
assertThat(response.statusCode()).isEqualTo(HttpServletResponse.SC_OK);
return response;