/**
* Copyright 2014 ArcBees Inc.
*
* 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.arcbees.pullrequest;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.arcbees.vcs.VcsApi;
import com.arcbees.vcs.VcsApiFactories;
import com.arcbees.vcs.VcsConstants;
import com.arcbees.vcs.VcsPropertiesHelper;
import com.arcbees.vcs.model.Comment;
import com.arcbees.vcs.model.Commit;
import com.arcbees.vcs.model.CommitStatus;
import com.arcbees.vcs.model.PullRequest;
import com.arcbees.vcs.model.PullRequestTarget;
import com.arcbees.vcs.util.JsonCustomDataStorage;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import jetbrains.buildServer.buildTriggers.BuildTriggerDescriptor;
import jetbrains.buildServer.messages.Status;
import jetbrains.buildServer.serverSide.Branch;
import jetbrains.buildServer.serverSide.CustomDataStorage;
import jetbrains.buildServer.serverSide.SBuildType;
import jetbrains.buildServer.serverSide.SRunningBuild;
import jetbrains.buildServer.serverSide.WebLinks;
public class PullRequestStatusHandler {
private static final Logger LOGGER = Logger.getLogger(PullRequestStatusHandler.class.getName());
private final VcsApiFactories vcsApiFactories;
private final VcsConstants vcsConstants;
private final Constants constants;
private final WebLinks webLinks;
public PullRequestStatusHandler(VcsApiFactories vcsApiFactories,
VcsConstants vcsConstants,
Constants constants,
WebLinks webLinks) {
this.vcsApiFactories = vcsApiFactories;
this.vcsConstants = vcsConstants;
this.constants = constants;
this.webLinks = webLinks;
}
public void handle(SRunningBuild build, BuildTriggerDescriptor trigger, BuildStatus buildStatus)
throws IOException {
LOGGER.log(Level.INFO, "Handling build status - Build Status: {0}, Branch: {1}, isSuccessful: {2}",
new Object[]{buildStatus, build.getBranch().getName(), build.getBuildStatus().isSuccessful()});
Branch branch = build.getBranch();
if (branch != null) {
SBuildType buildType = build.getBuildType();
PullRequestPropertiesHelper pullRequestPropertiesHelper =
new PullRequestPropertiesHelper(trigger.getProperties(), vcsConstants, constants);
VcsApi vcsApi = vcsApiFactories.create(pullRequestPropertiesHelper);
PullRequest pullRequest = vcsApi.getPullRequestForBranch(branch.getName());
JsonCustomDataStorage<PullRequestBuild> dataStorage = getJsonDataStorage(buildType, trigger);
PullRequestBuild pullRequestBuild =
getPullRequestBuild(pullRequestPropertiesHelper, pullRequest, dataStorage);
CommitStatus commitStatus = getCommitStatus(build.getBuildStatus(), buildStatus);
Comment comment = updateStatus(build, vcsApi, pullRequest, pullRequestBuild, commitStatus);
if (pullRequestPropertiesHelper.getApproveOnSuccessKey()) {
updateApproval(vcsApi, pullRequest, commitStatus);
}
pullRequestBuild = new PullRequestBuild(pullRequest, build.getBuildStatus(), comment);
dataStorage.putValue(getPullRequestKey(pullRequestPropertiesHelper, pullRequest), pullRequestBuild);
}
}
private CommitStatus getCommitStatus(Status status, BuildStatus buildStatus) {
switch (buildStatus) {
case STARTING:
return CommitStatus.PENDING;
case FINISHED:
if (status.isSuccessful()) {
return CommitStatus.SUCCESS;
} else {
return CommitStatus.FAILURE;
}
default:
return CommitStatus.ERROR;
}
}
private void updateApproval(VcsApi vcsApi, PullRequest pullRequest, CommitStatus commitStatus) throws IOException {
try {
if (CommitStatus.SUCCESS.equals(commitStatus)) {
vcsApi.approvePullRequest(pullRequest.getId());
} else {
vcsApi.deletePullRequestApproval(pullRequest.getId());
}
} catch (UnsupportedOperationException e) {
}
}
private Comment updateStatus(SRunningBuild build,
VcsApi vcsApi,
PullRequest pullRequest,
PullRequestBuild pullRequestBuild,
CommitStatus commitStatus) throws IOException {
try {
String statusMessage = getStatusMessage(build, commitStatus);
vcsApi.updateStatus(getSourceCommitHash(pullRequest), statusMessage, commitStatus, getTargetUrl(build),
build);
return null;
} catch (UnsupportedOperationException e) {
if (!CommitStatus.PENDING.equals(commitStatus)) {
return postOrUpdateComment(build, vcsApi, pullRequest, pullRequestBuild);
} else {
return null;
}
}
}
private String getStatusMessage(SRunningBuild build,
CommitStatus commitStatus) {
switch (commitStatus) {
case ERROR:
case FAILURE:
case SUCCESS:
String buildDescription = Strings.nullToEmpty(build.getStatusDescriptor().getText());
if (!buildDescription.isEmpty()) {
buildDescription = " : " + buildDescription;
}
return build.getFullName() + buildDescription;
case PENDING:
return constants.getBuildStarted() + build.getFullName();
default:
return "";
}
}
private Comment postOrUpdateComment(SRunningBuild build,
VcsApi vcsApi,
PullRequest pullRequest,
PullRequestBuild pullRequestBuild) throws IOException {
Comment comment = pullRequestBuild == null ? null : pullRequestBuild.getLastComment();
if (comment != null) {
deleteOldComment(vcsApi, pullRequest.getId(), comment);
}
comment = vcsApi.postComment(pullRequest.getId(), getComment(build));
return comment;
}
private void deleteOldComment(VcsApi vcsApi,
int pullRequestId,
Comment oldComment) throws IOException {
vcsApi.deleteComment(pullRequestId, oldComment.getCommentId());
}
private PullRequestBuild getPullRequestBuild(VcsPropertiesHelper vcsPropertiesHelper,
PullRequest pullRequest,
JsonCustomDataStorage<PullRequestBuild> dataStorage) {
String pullRequestKey = getPullRequestKey(vcsPropertiesHelper.getRepositoryOwner(),
vcsPropertiesHelper.getRepositoryName(), pullRequest);
return dataStorage.getValue(pullRequestKey);
}
private JsonCustomDataStorage<PullRequestBuild> getJsonDataStorage(SBuildType buildType,
BuildTriggerDescriptor trigger) {
String storageId = getStorageId(trigger);
CustomDataStorage customDataStorage = buildType.getCustomDataStorage(storageId);
return JsonCustomDataStorage.create(customDataStorage, PullRequestBuild.class);
}
private String getPullRequestKey(VcsPropertiesHelper helper, PullRequest pullRequest) {
return getPullRequestKey(helper.getRepositoryOwner(), helper.getRepositoryName(), pullRequest);
}
private String getPullRequestKey(String repositoryOwner, String repositoryName, PullRequest pullRequest) {
return vcsConstants.getPullRequestKey() + repositoryOwner + "_" + repositoryName + "_" + pullRequest.getId();
}
private String getStorageId(BuildTriggerDescriptor triggerDescriptor) {
return triggerDescriptor.getBuildTriggerService().getClass().getName() + "_"
+ getParametersSignature(triggerDescriptor);
}
private String getParametersSignature(BuildTriggerDescriptor triggerDescriptor) {
Map<String, String> propsMap = triggerDescriptor.getParameters();
List<String> keys = Lists.newArrayList(propsMap.keySet());
Collections.sort(keys);
StringBuilder signature = new StringBuilder();
signature.append(triggerDescriptor.getType());
for (String key : keys) {
signature.append(key).append('=').append(propsMap.get(key));
}
return signature.toString();
}
private String getComment(SRunningBuild build) {
return getComment(build.getBuildStatus()) + "(" + getTargetUrl(build) + ")";
}
private String getTargetUrl(SRunningBuild build) {
return webLinks.getViewResultsUrl(build);
}
private String getComment(Status status) {
if (status.isSuccessful()) {
return constants.getBuildSuccess();
} else {
return constants.getBuildFailure();
}
}
private String getSourceCommitHash(PullRequest pullRequest) {
PullRequestTarget source = pullRequest.getSource();
Commit sourceCommit = source.getCommit();
return sourceCommit.getHash();
}
}