| // 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 static java.util.stream.Collectors.toList; |
| |
| import com.google.common.collect.Streams; |
| import com.google.common.flogger.FluentLogger; |
| import com.google.gerrit.exceptions.StorageException; |
| import com.google.gerrit.extensions.restapi.BadRequestException; |
| import com.google.gerrit.extensions.restapi.Response; |
| import com.google.gerrit.extensions.restapi.RestReadView; |
| import com.google.gerrit.extensions.webui.UiAction; |
| import com.google.gerrit.reviewdb.client.Change; |
| import com.google.gerrit.reviewdb.client.Change.Status; |
| import com.google.gerrit.server.CurrentUser; |
| import com.google.gerrit.server.IdentifiedUser; |
| import com.google.gerrit.server.account.AccountCache; |
| import com.google.gerrit.server.account.Emails; |
| import com.google.gerrit.server.change.ChangeResource; |
| import com.google.gerrit.server.change.RevisionResource; |
| import com.google.gerrit.server.config.PluginConfigFactory; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.gerrit.server.patch.PatchListCache; |
| import com.google.gerrit.server.permissions.PermissionBackend; |
| import com.google.gerrit.server.project.ProjectCache; |
| import com.google.gerrit.server.project.ProjectState; |
| import com.google.gerrit.server.query.change.ChangeData; |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** Create and return OWNERS info when "Find Owners" button is clicked. */ |
| class Action implements RestReadView<RevisionResource>, UiAction<RevisionResource> { |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); |
| |
| private final AccountCache accountCache; |
| private final Emails emails; |
| private final ChangeData.Factory changeDataFactory; |
| private final GitRepositoryManager repoManager; |
| private final PermissionBackend permissionBackend; |
| private final Provider<CurrentUser> userProvider; |
| private final ProjectCache projectCache; |
| private final Config config; |
| |
| static class Parameters { |
| Boolean debug; // REST API "debug" parameter, or null |
| Boolean nocache; // REST API "nocache" parameter, or null |
| Integer patchset; // REST API "patchset" parameter, or null |
| } |
| |
| @Inject |
| Action( |
| PermissionBackend permissionBackend, |
| PluginConfigFactory configFactory, |
| Provider<CurrentUser> userProvider, |
| ChangeData.Factory changeDataFactory, |
| AccountCache accountCache, |
| PatchListCache patchListCache, |
| Emails emails, |
| GitRepositoryManager repoManager, |
| ProjectCache projectCache) { |
| this.permissionBackend = permissionBackend; |
| this.userProvider = userProvider; |
| this.changeDataFactory = changeDataFactory; |
| this.accountCache = accountCache; |
| this.emails = emails; |
| this.repoManager = repoManager; |
| this.projectCache = projectCache; |
| this.config = new Config(configFactory, null, accountCache, patchListCache, emails); |
| } |
| |
| private String getUserName() { |
| if (userProvider != null && userProvider.get().getUserName().isPresent()) { |
| return userProvider.get().getUserName().get(); |
| } |
| return "?"; |
| } |
| |
| private List<OwnerInfo> getOwners(OwnersDb db, Collection<String> files) { |
| Map<String, OwnerWeights> weights = new HashMap<>(); |
| db.findOwners(files, weights, new ArrayList<>()); |
| List<OwnerInfo> result = new ArrayList<>(); |
| Set<String> emails = new HashSet<>(); |
| for (String key : OwnerWeights.sortKeys(weights)) { |
| if (!emails.contains(key)) { |
| result.add(new OwnerInfo(key, weights.get(key).getLevelCounts())); |
| emails.add(key); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public Response<RestResult> apply(RevisionResource rev) throws IOException, BadRequestException { |
| return apply(rev.getChangeResource(), new Parameters()); |
| } |
| |
| // Used by integration tests, because they do not have ReviewDb Provider. |
| public Response<RestResult> apply(ChangeResource rsrc, Parameters params) |
| throws BadRequestException { |
| ChangeData changeData = changeDataFactory.create(rsrc.getChange()); |
| return getChangeData(params, changeData); |
| } |
| |
| /** Returns reviewer emails got from ChangeData. */ |
| static List<String> getReviewers(ChangeData changeData, AccountCache accountCache) { |
| try { |
| // Reviewers may have no preferred email, skip them if the preferred email is not set. |
| return changeData.reviewers().all().stream() |
| .map(accountCache::get) |
| .flatMap(Streams::stream) |
| .map(a -> a.getAccount().preferredEmail()) |
| .filter(Objects::nonNull) |
| .collect(toList()); |
| } catch (StorageException e) { |
| logger.atSevere().withCause(e).log("Exception for %s", Config.getChangeId(changeData)); |
| return new ArrayList<>(); |
| } |
| } |
| |
| /** Returns the current patchset number or the given patchsetNum if it is valid. */ |
| static int getValidPatchsetNum(ChangeData changeData, Integer patchsetNum) |
| throws BadRequestException { |
| int patchset = changeData.currentPatchSet().id().get(); |
| if (patchsetNum != null) { |
| if (patchsetNum < 1 || patchsetNum > patchset) { |
| throw new BadRequestException( |
| "Invalid patchset parameter: " |
| + patchsetNum |
| + "; must be 1" |
| + ((1 != patchset) ? (" to " + patchset) : "")); |
| } |
| return patchsetNum; |
| } |
| return patchset; |
| } |
| |
| /** REST API to return owners info of a change. */ |
| public Response<RestResult> getChangeData(Parameters params, ChangeData changeData) |
| throws BadRequestException { |
| int patchset = getValidPatchsetNum(changeData, params.patchset); |
| ProjectState projectState = projectCache.get(changeData.project()); |
| Boolean useCache = params.nocache == null || !params.nocache; |
| OwnersDb db = |
| Cache.getInstance(config, repoManager) |
| .get( |
| useCache, |
| permissionBackend, |
| projectState, |
| accountCache, |
| emails, |
| repoManager, |
| changeData, |
| patchset); |
| Collection<String> changedFiles = changeData.currentFilePaths(); |
| Map<String, Set<String>> file2Owners = db.findOwners(changedFiles); |
| |
| Boolean addDebugMsg = (params.debug != null) ? params.debug : config.getAddDebugMsg(); |
| RestResult obj = new RestResult(config, projectState, changeData, addDebugMsg); |
| obj.patchset = patchset; |
| obj.ownerRevision = db.revision; |
| if (addDebugMsg) { |
| obj.dbgmsgs.user = getUserName(); |
| obj.dbgmsgs.errors = db.errors; |
| obj.dbgmsgs.path2owners = Util.makeSortedMap(db.path2Owners); |
| obj.dbgmsgs.owner2paths = Util.makeSortedMap(db.owner2Paths); |
| obj.dbgmsgs.logs = db.logs; |
| } |
| |
| obj.file2owners = Util.makeSortedMap(file2Owners); |
| obj.reviewers = getReviewers(changeData, accountCache); |
| obj.owners = getOwners(db, changedFiles); |
| obj.files = new ArrayList<>(changedFiles); |
| return Response.ok(obj); |
| } |
| |
| @Override |
| public Description getDescription(RevisionResource resource) { |
| Change change = resource.getChangeResource().getChange(); |
| ChangeData changeData = null; |
| try { |
| changeData = changeDataFactory.create(change); |
| if (changeData.change().getDest().branch() == null) { |
| if (!Checker.isExemptFromOwnerApproval(changeData)) { |
| logger.atSevere().log("Cannot get branch of change: %d", changeData.getId().get()); |
| } |
| return null; // no "Find Owners" button |
| } |
| Status status = resource.getChange().getStatus(); |
| // Commit message is not used to enable/disable "Find Owners". |
| boolean needFindOwners = |
| userProvider != null |
| && userProvider.get() instanceof IdentifiedUser |
| && status != Status.ABANDONED |
| && status != Status.MERGED; |
| return new Description() |
| .setLabel("Find Owners") |
| .setTitle("Find owners to add to Reviewers list") |
| .setVisible(needFindOwners); |
| } catch (StorageException e) { |
| logger.atSevere().withCause(e).log("Exception for %s", Config.getChangeId(changeData)); |
| throw new IllegalStateException(e); |
| } |
| } |
| } |